@@ -181,10 +177,11 @@ export default function SvgPreview() {
{
- const size = fullWidth() * PIXELS_PER_MODULE;
+ const width = fullWidth() * PIXELS_PER_MODULE;
+ const height = fullHeight() * PIXELS_PER_MODULE;
const canvas = document.createElement("canvas");
- canvas.width = size;
- canvas.height = size;
+ canvas.width = width;
+ canvas.height = height;
const ctx = canvas.getContext("2d");
if (svgOptions.bgImgFile != null) {
@@ -192,13 +189,13 @@ export default function SvgPreview() {
const bgImg = new Image();
bgImg.src = bgSrc();
await bgImg.decode();
- ctx!.drawImage(bgImg, 0, 0, size, size);
+ ctx!.drawImage(bgImg, 0, 0, width, height);
}
const svgImg = new Image();
svgImg.src = `data:image/svg+xml;base64,${btoa(svgResult().svg)}`;
await svgImg.decode();
- ctx!.drawImage(svgImg, 0, 0, size, size);
+ ctx!.drawImage(svgImg, 0, 0, width, height);
if (svgOptions.fgImgFile != null) {
ctx!.imageSmoothingEnabled = !svgOptions.pixelateFgImg;
@@ -207,8 +204,9 @@ export default function SvgPreview() {
await logoImg.decode();
// TODO logoSize
const logoSize = (fullWidth() / 4) * PIXELS_PER_MODULE;
- const offset = (size - logoSize) / 2;
- ctx!.drawImage(logoImg, offset, offset, logoSize, logoSize);
+ const xOffset = (width - logoSize) / 2;
+ const yOffset = (height - logoSize) / 2;
+ ctx!.drawImage(logoImg, xOffset, yOffset, logoSize, logoSize);
}
download(
diff --git a/src/lib/PaintContext.tsx b/src/lib/PaintContext.tsx
new file mode 100644
index 0000000..36d869c
--- /dev/null
+++ b/src/lib/PaintContext.tsx
@@ -0,0 +1,110 @@
+import {
+ createContext,
+ createEffect,
+ createSignal,
+ untrack,
+ useContext,
+ type Accessor,
+ type JSX,
+ type Setter,
+} from "solid-js";
+import { useQrContext } from "./QrContext";
+import { useSvgContext } from "./SvgContext";
+import { Margin, QrOptions, Version, get_matrix, type QrError } from "fuqr";
+
+export type BoxSelection = {
+ top: number;
+ bot: number;
+ left: number;
+ right: number;
+};
+
+export const PaintContext = createContext<{
+ selections: Accessor;
+ setSelectionsInPlace: Setter;
+ scaleX: Accessor;
+ setScaleXInPlace: Setter;
+ scaleY: Accessor;
+ setScaleYInPlace: Setter;
+}>();
+
+export function PaintContextProvider(props: { children: JSX.Element }) {
+ const { inputQr, outputQr, setOutputQr } = useQrContext();
+ const { svgOptions } = useSvgContext();
+
+ const [selections, setSelectionsInPlace] = createSignal([], {
+ equals: false,
+ });
+
+ const [scaleX, setScaleXInPlace] = createSignal([], {
+ equals: false,
+ });
+ const [scaleY, setScaleYInPlace] = createSignal([], {
+ equals: false,
+ });
+
+ createEffect(() => {
+ try {
+ // NOTE: Version and Margin cannot be reused, so must be created each time
+ const qrOptions = new QrOptions()
+ .min_version(new Version(inputQr.minVersion))
+ .min_ecl(inputQr.minEcl)
+ .mask(inputQr.mask!) // null makes more sense than undefined
+ .mode(inputQr.mode!) // null makes more sense than undefined
+ .margin(new Margin(10))
+ .margin(
+ new Margin(0)
+ .setTop(inputQr.margin.top)
+ .setRight(inputQr.margin.right)
+ .setBottom(inputQr.margin.bottom)
+ .setLeft(inputQr.margin.left)
+ );
+
+ let m = get_matrix(inputQr.text, qrOptions);
+ setOutputQr({
+ text: inputQr.text,
+ version: m.version["0"],
+ ecl: m.ecl,
+ mode: m.mode,
+ mask: m.mask,
+ margin: {
+ top: m.margin.top,
+ right: m.margin.right,
+ bottom: m.margin.bottom,
+ left: m.margin.left,
+ },
+ });
+ const matrixLength = m.width() * m.height();
+ if (matrixLength === untrack(scaleX).length) return;
+
+ setSelectionsInPlace([]);
+ setScaleXInPlace(Array(matrixLength).fill(100));
+ setScaleYInPlace(Array(matrixLength).fill(100));
+ } catch (e) {
+ setOutputQr(e as QrError);
+ }
+ });
+
+ return (
+
+ {props.children}
+
+ );
+}
+
+export function usePaintContext() {
+ const context = useContext(PaintContext);
+ if (!context) {
+ throw new Error("usePaintContext: used outside PaintContextProvider");
+ }
+ return context;
+}
diff --git a/src/lib/QrContext.tsx b/src/lib/QrContext.tsx
index f01f3da..5c494af 100644
--- a/src/lib/QrContext.tsx
+++ b/src/lib/QrContext.tsx
@@ -2,10 +2,11 @@ import {
createContext,
createSignal,
useContext,
+ type Accessor,
type JSX,
type Setter,
} from "solid-js";
-import { ECL, Mode, Mask } from "fuqr";
+import { ECL, Mode, Mask, QrError } from "fuqr";
import { createStore, type SetStoreFunction } from "solid-js/store";
type InputQr = {
@@ -14,21 +15,35 @@ type InputQr = {
minEcl: ECL;
mode: Mode | null;
mask: Mask | null;
+ margin: {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+ };
};
-type OutputQr = {
+export type OutputQr = {
text: string;
+ /** Stored as value b/c Version is a ptr which becomes null after use */
version: number;
ecl: ECL;
mode: Mode;
mask: Mask;
+ /** Stored as value b/c Margin is a ptr which becomes null after use */
+ margin: {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+ };
};
export const QrContext = createContext<{
inputQr: InputQr;
setInputQr: SetStoreFunction;
- outputQr: OutputQr | null;
- setOutputQr: Setter;
+ outputQr: Accessor;
+ setOutputQr: Setter;
}>();
export function QrContextProvider(props: { children: JSX.Element }) {
@@ -38,14 +53,20 @@ export function QrContextProvider(props: { children: JSX.Element }) {
minEcl: ECL.Low,
mode: null,
mask: null,
+ margin: {
+ top: 2,
+ right: 2,
+ bottom: 2,
+ left: 2,
+ },
});
- const [outputQr, setOutputQr] = createSignal(null);
+ const [outputQr, setOutputQr] = createSignal(
+ QrError.InvalidEncoding
+ );
return (
-
+
{props.children}
);
diff --git a/src/lib/SvgContext.tsx b/src/lib/SvgContext.tsx
index a723202..71d41fd 100644
--- a/src/lib/SvgContext.tsx
+++ b/src/lib/SvgContext.tsx
@@ -2,7 +2,6 @@ import { createContext, useContext, type JSX } from "solid-js";
import { createStore, type SetStoreFunction } from "solid-js/store";
type SvgOptions = {
- margin: number;
bgColor: string;
fgColor: string;
bgImgFile: File | null;
@@ -18,7 +17,6 @@ export const SvgContext = createContext<{
export function SvgContextProvider(props: { children: JSX.Element }) {
const [svgOptions, setSvgOptions] = createStore({
- margin: 2,
bgColor: "#ffffff",
fgColor: "#000000",
bgImgFile: null,
@@ -28,12 +26,7 @@ export function SvgContextProvider(props: { children: JSX.Element }) {
});
return (
-
+
{props.children}
);
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 11cfcc2..249c4f8 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -2,10 +2,15 @@ import { Switch, Match, createSignal } from "solid-js";
import { clientOnly } from "@solidjs/start";
import init from "fuqr";
import { Editor } from "~/components/Editor";
+import SvgPreview from "~/components/qr/SvgPreview";
+import { PaintContextProvider } from "~/lib/PaintContext";
+import { SvgContextProvider } from "~/lib/SvgContext";
-const QRCode = clientOnly(async () => {
+const QrContextProvider = clientOnly(async () => {
await init();
- return import("../components/qr/SvgPreview");
+ return {
+ default: (await import("../lib/QrContext")).QrContextProvider,
+ };
});
// const MODULE_NAMES = [
@@ -24,18 +29,24 @@ enum Stage {
export default function Home() {
const [stage, setStage] = createSignal(Stage.Create);
return (
-
-
-
-
-
- {/* */}
-
-
+
+
+
+
+
+
+
+
+ {/* */}
+
+
+
+
+
);
}