diff --git a/fuqr/pkg/fuqr.d.ts b/fuqr/pkg/fuqr.d.ts index 641ee23..b882ffc 100644 --- a/fuqr/pkg/fuqr.d.ts +++ b/fuqr/pkg/fuqr.d.ts @@ -39,14 +39,6 @@ export enum Toggle { } /** */ -export enum ECL { - Low = 0, - Medium = 1, - Quartile = 2, - High = 3, -} -/** -*/ export enum Mask { M0 = 0, M1 = 1, @@ -59,9 +51,11 @@ export enum Mask { } /** */ -export enum SvgError { - InvalidEncoding = 0, - ExceedsMaxCapacity = 1, +export enum ECL { + Low = 0, + Medium = 1, + Quartile = 2, + High = 3, } /** */ @@ -72,23 +66,91 @@ export enum Mode { } /** */ +export enum QrError { + InvalidEncoding = 0, + ExceedsMaxCapacity = 1, +} +/** +*/ +export class Margin { + free(): void; +/** +* @param {number} margin +*/ + constructor(margin: number); +/** +* @param {number} top +* @returns {Margin} +*/ + setTop(top: number): Margin; +/** +* @param {number} right +* @returns {Margin} +*/ + setRight(right: number): Margin; +/** +* @param {number} bottom +* @returns {Margin} +*/ + setBottom(bottom: number): Margin; +/** +* @param {number} left +* @returns {Margin} +*/ + setLeft(left: number): Margin; +/** +* @param {number} y +* @returns {Margin} +*/ + y(y: number): Margin; +/** +* @param {number} x +* @returns {Margin} +*/ + x(x: number): Margin; +/** +*/ + bottom: number; +/** +*/ + left: number; +/** +*/ + right: number; +/** +*/ + top: number; +} +/** +*/ export class Matrix { free(): void; /** +* @returns {number} +*/ + width(): number; +/** +* @returns {number} +*/ + height(): number; +/** */ ecl: ECL; /** +*/ + margin: Margin; +/** */ mask: Mask; /** +*/ + mode: Mode; +/** */ value: any[]; /** */ version: Version; -/** -*/ - width: number; } /** */ @@ -117,6 +179,11 @@ export class QrOptions { * @returns {QrOptions} */ mask(mask?: Mask): QrOptions; +/** +* @param {Margin} margin +* @returns {QrOptions} +*/ + margin(margin: Margin): QrOptions; } /** */ @@ -146,20 +213,20 @@ export class SvgOptions { */ background(background: string): SvgOptions; /** -* @param {Float64Array} scale_matrix +* @param {Uint8Array} scale_matrix * @returns {SvgOptions} */ - scale_x_matrix(scale_matrix: Float64Array): SvgOptions; + scale_x_matrix(scale_matrix: Uint8Array): SvgOptions; /** -* @param {Float64Array} scale_matrix +* @param {Uint8Array} scale_matrix * @returns {SvgOptions} */ - scale_y_matrix(scale_matrix: Float64Array): SvgOptions; + scale_y_matrix(scale_matrix: Uint8Array): SvgOptions; /** -* @param {Float64Array} scale_matrix +* @param {Uint8Array} scale_matrix * @returns {SvgOptions} */ - scale_matrix(scale_matrix: Float64Array): SvgOptions; + scale_matrix(scale_matrix: Uint8Array): SvgOptions; /** * @param {Toggle} toggle * @returns {SvgOptions} @@ -203,23 +270,44 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl export interface InitOutput { readonly memory: WebAssembly.Memory; + readonly __wbg_margin_free: (a: number) => void; + readonly __wbg_get_margin_top: (a: number) => number; + readonly __wbg_set_margin_top: (a: number, b: number) => void; + readonly __wbg_get_margin_right: (a: number) => number; + readonly __wbg_set_margin_right: (a: number, b: number) => void; + readonly __wbg_get_margin_bottom: (a: number) => number; + readonly __wbg_set_margin_bottom: (a: number, b: number) => void; + readonly __wbg_get_margin_left: (a: number) => number; + readonly __wbg_set_margin_left: (a: number, b: number) => void; + readonly margin_new: (a: number) => number; + readonly margin_setTop: (a: number, b: number) => number; + readonly margin_setRight: (a: number, b: number) => number; + readonly margin_setBottom: (a: number, b: number) => number; + readonly margin_setLeft: (a: number, b: number) => number; + readonly margin_y: (a: number, b: number) => number; + readonly margin_x: (a: number, b: number) => number; readonly __wbg_matrix_free: (a: number) => void; readonly __wbg_get_matrix_value: (a: number, b: number) => void; readonly __wbg_set_matrix_value: (a: number, b: number, c: number) => void; - readonly __wbg_get_matrix_width: (a: number) => number; - readonly __wbg_set_matrix_width: (a: number, b: number) => void; + readonly __wbg_get_matrix_margin: (a: number) => number; + readonly __wbg_set_matrix_margin: (a: number, b: number) => void; + readonly __wbg_get_matrix_mode: (a: number) => number; + readonly __wbg_set_matrix_mode: (a: number, b: number) => void; readonly __wbg_get_matrix_version: (a: number) => number; readonly __wbg_set_matrix_version: (a: number, b: number) => void; readonly __wbg_get_matrix_ecl: (a: number) => number; readonly __wbg_set_matrix_ecl: (a: number, b: number) => void; readonly __wbg_get_matrix_mask: (a: number) => number; readonly __wbg_set_matrix_mask: (a: number, b: number) => void; + readonly matrix_width: (a: number) => number; + readonly matrix_height: (a: number) => number; readonly __wbg_qroptions_free: (a: number) => void; readonly qroptions_new: () => number; readonly qroptions_min_version: (a: number, b: number) => number; readonly qroptions_min_ecl: (a: number, b: number) => number; readonly qroptions_mode: (a: number, b: number) => number; readonly qroptions_mask: (a: number, b: number) => number; + readonly qroptions_margin: (a: number, b: number) => number; readonly __wbg_svgoptions_free: (a: number) => void; readonly svgoptions_new: () => number; readonly svgoptions_margin: (a: number, b: number) => number; diff --git a/fuqr/pkg/fuqr.js b/fuqr/pkg/fuqr.js index 3cf19f8..1c6011a 100644 --- a/fuqr/pkg/fuqr.js +++ b/fuqr/pkg/fuqr.js @@ -17,18 +17,6 @@ function addHeapObject(obj) { function getObject(idx) { return heap[idx]; } -function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; -} - -function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; -} - function isLikeNone(x) { return x === undefined || x === null; } @@ -51,6 +39,18 @@ function getInt32Memory0() { return cachedInt32Memory0; } +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -162,9 +162,9 @@ function passStringToWasm0(arg, malloc, realloc) { return ptr; } -function passArrayF64ToWasm0(arg, malloc) { - const ptr = malloc(arg.length * 8, 8) >>> 0; - getFloat64Memory0().set(arg, ptr / 8); +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8Memory0().set(arg, ptr / 1); WASM_VECTOR_LEN = arg.length; return ptr; } @@ -229,16 +229,158 @@ export const Module = Object.freeze({ DataOFF:0,"0":"DataOFF",DataON:1,"1":"Data export const Toggle = Object.freeze({ Background:0,"0":"Background",BackgroundPixels:1,"1":"BackgroundPixels",ForegroundPixels:2,"2":"ForegroundPixels", }); /** */ -export const ECL = Object.freeze({ Low:0,"0":"Low",Medium:1,"1":"Medium",Quartile:2,"2":"Quartile",High:3,"3":"High", }); -/** -*/ export const Mask = Object.freeze({ M0:0,"0":"M0",M1:1,"1":"M1",M2:2,"2":"M2",M3:3,"3":"M3",M4:4,"4":"M4",M5:5,"5":"M5",M6:6,"6":"M6",M7:7,"7":"M7", }); /** */ -export const SvgError = Object.freeze({ InvalidEncoding:0,"0":"InvalidEncoding",ExceedsMaxCapacity:1,"1":"ExceedsMaxCapacity", }); +export const ECL = Object.freeze({ Low:0,"0":"Low",Medium:1,"1":"Medium",Quartile:2,"2":"Quartile",High:3,"3":"High", }); /** */ export const Mode = Object.freeze({ Numeric:0,"0":"Numeric",Alphanumeric:1,"1":"Alphanumeric",Byte:2,"2":"Byte", }); +/** +*/ +export const QrError = Object.freeze({ InvalidEncoding:0,"0":"InvalidEncoding",ExceedsMaxCapacity:1,"1":"ExceedsMaxCapacity", }); + +const MarginFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_margin_free(ptr >>> 0)); +/** +*/ +export class Margin { + + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(Margin.prototype); + obj.__wbg_ptr = ptr; + MarginFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + MarginFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_margin_free(ptr); + } + /** + * @returns {number} + */ + get top() { + const ret = wasm.__wbg_get_margin_top(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @param {number} arg0 + */ + set top(arg0) { + wasm.__wbg_set_margin_top(this.__wbg_ptr, arg0); + } + /** + * @returns {number} + */ + get right() { + const ret = wasm.__wbg_get_margin_right(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @param {number} arg0 + */ + set right(arg0) { + wasm.__wbg_set_margin_right(this.__wbg_ptr, arg0); + } + /** + * @returns {number} + */ + get bottom() { + const ret = wasm.__wbg_get_margin_bottom(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @param {number} arg0 + */ + set bottom(arg0) { + wasm.__wbg_set_margin_bottom(this.__wbg_ptr, arg0); + } + /** + * @returns {number} + */ + get left() { + const ret = wasm.__wbg_get_margin_left(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @param {number} arg0 + */ + set left(arg0) { + wasm.__wbg_set_margin_left(this.__wbg_ptr, arg0); + } + /** + * @param {number} margin + */ + constructor(margin) { + const ret = wasm.margin_new(margin); + this.__wbg_ptr = ret >>> 0; + return this; + } + /** + * @param {number} top + * @returns {Margin} + */ + setTop(top) { + const ptr = this.__destroy_into_raw(); + const ret = wasm.margin_setTop(ptr, top); + return Margin.__wrap(ret); + } + /** + * @param {number} right + * @returns {Margin} + */ + setRight(right) { + const ptr = this.__destroy_into_raw(); + const ret = wasm.margin_setRight(ptr, right); + return Margin.__wrap(ret); + } + /** + * @param {number} bottom + * @returns {Margin} + */ + setBottom(bottom) { + const ptr = this.__destroy_into_raw(); + const ret = wasm.margin_setBottom(ptr, bottom); + return Margin.__wrap(ret); + } + /** + * @param {number} left + * @returns {Margin} + */ + setLeft(left) { + const ptr = this.__destroy_into_raw(); + const ret = wasm.margin_setLeft(ptr, left); + return Margin.__wrap(ret); + } + /** + * @param {number} y + * @returns {Margin} + */ + y(y) { + const ptr = this.__destroy_into_raw(); + const ret = wasm.margin_y(ptr, y); + return Margin.__wrap(ret); + } + /** + * @param {number} x + * @returns {Margin} + */ + x(x) { + const ptr = this.__destroy_into_raw(); + const ret = wasm.margin_x(ptr, x); + return Margin.__wrap(ret); + } +} const MatrixFinalization = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } @@ -291,17 +433,32 @@ export class Matrix { wasm.__wbg_set_matrix_value(this.__wbg_ptr, ptr0, len0); } /** - * @returns {number} + * @returns {Margin} */ - get width() { - const ret = wasm.__wbg_get_matrix_width(this.__wbg_ptr); - return ret >>> 0; + get margin() { + const ret = wasm.__wbg_get_matrix_margin(this.__wbg_ptr); + return Margin.__wrap(ret); } /** - * @param {number} arg0 + * @param {Margin} arg0 */ - set width(arg0) { - wasm.__wbg_set_matrix_width(this.__wbg_ptr, arg0); + set margin(arg0) { + _assertClass(arg0, Margin); + var ptr0 = arg0.__destroy_into_raw(); + wasm.__wbg_set_matrix_margin(this.__wbg_ptr, ptr0); + } + /** + * @returns {Mode} + */ + get mode() { + const ret = wasm.__wbg_get_matrix_mode(this.__wbg_ptr); + return ret; + } + /** + * @param {Mode} arg0 + */ + set mode(arg0) { + wasm.__wbg_set_matrix_mode(this.__wbg_ptr, arg0); } /** * @returns {Version} @@ -344,6 +501,20 @@ export class Matrix { set mask(arg0) { wasm.__wbg_set_matrix_mask(this.__wbg_ptr, arg0); } + /** + * @returns {number} + */ + width() { + const ret = wasm.matrix_width(this.__wbg_ptr); + return ret >>> 0; + } + /** + * @returns {number} + */ + height() { + const ret = wasm.matrix_height(this.__wbg_ptr); + return ret >>> 0; + } } const QrOptionsFinalization = (typeof FinalizationRegistry === 'undefined') @@ -417,6 +588,17 @@ export class QrOptions { const ret = wasm.qroptions_mask(ptr, isLikeNone(mask) ? 8 : mask); return QrOptions.__wrap(ret); } + /** + * @param {Margin} margin + * @returns {QrOptions} + */ + margin(margin) { + const ptr = this.__destroy_into_raw(); + _assertClass(margin, Margin); + var ptr0 = margin.__destroy_into_raw(); + const ret = wasm.qroptions_margin(ptr, ptr0); + return QrOptions.__wrap(ret); + } } const SvgOptionsFinalization = (typeof FinalizationRegistry === 'undefined') @@ -493,34 +675,34 @@ export class SvgOptions { return SvgOptions.__wrap(ret); } /** - * @param {Float64Array} scale_matrix + * @param {Uint8Array} scale_matrix * @returns {SvgOptions} */ scale_x_matrix(scale_matrix) { const ptr = this.__destroy_into_raw(); - const ptr0 = passArrayF64ToWasm0(scale_matrix, wasm.__wbindgen_malloc); + const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc); const len0 = WASM_VECTOR_LEN; const ret = wasm.svgoptions_scale_x_matrix(ptr, ptr0, len0); return SvgOptions.__wrap(ret); } /** - * @param {Float64Array} scale_matrix + * @param {Uint8Array} scale_matrix * @returns {SvgOptions} */ scale_y_matrix(scale_matrix) { const ptr = this.__destroy_into_raw(); - const ptr0 = passArrayF64ToWasm0(scale_matrix, wasm.__wbindgen_malloc); + const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc); const len0 = WASM_VECTOR_LEN; const ret = wasm.svgoptions_scale_y_matrix(ptr, ptr0, len0); return SvgOptions.__wrap(ret); } /** - * @param {Float64Array} scale_matrix + * @param {Uint8Array} scale_matrix * @returns {SvgOptions} */ scale_matrix(scale_matrix) { const ptr = this.__destroy_into_raw(); - const ptr0 = passArrayF64ToWasm0(scale_matrix, wasm.__wbindgen_malloc); + const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc); const len0 = WASM_VECTOR_LEN; const ret = wasm.svgoptions_scale_matrix(ptr, ptr0, len0); return SvgOptions.__wrap(ret); @@ -732,9 +914,6 @@ function __wbg_get_imports() { const ret = arg0; return addHeapObject(ret); }; - imports.wbg.__wbindgen_object_drop_ref = function(arg0) { - takeObject(arg0); - }; imports.wbg.__wbindgen_try_into_number = function(arg0) { let result; try { result = +getObject(arg0) } catch (e) { result = e } @@ -747,6 +926,9 @@ imports.wbg.__wbindgen_number_get = function(arg0, arg1) { getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); }; +imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); +}; imports.wbg.__wbg_new_abda76e883ba8a5f = function() { const ret = new Error(); return addHeapObject(ret); diff --git a/fuqr/pkg/fuqr_bg.wasm b/fuqr/pkg/fuqr_bg.wasm index 789b8ff..95a3081 100644 Binary files a/fuqr/pkg/fuqr_bg.wasm and b/fuqr/pkg/fuqr_bg.wasm differ diff --git a/fuqr/pkg/fuqr_bg.wasm.d.ts b/fuqr/pkg/fuqr_bg.wasm.d.ts index 92740fc..fd5d239 100644 --- a/fuqr/pkg/fuqr_bg.wasm.d.ts +++ b/fuqr/pkg/fuqr_bg.wasm.d.ts @@ -1,23 +1,44 @@ /* tslint:disable */ /* eslint-disable */ export const memory: WebAssembly.Memory; +export function __wbg_margin_free(a: number): void; +export function __wbg_get_margin_top(a: number): number; +export function __wbg_set_margin_top(a: number, b: number): void; +export function __wbg_get_margin_right(a: number): number; +export function __wbg_set_margin_right(a: number, b: number): void; +export function __wbg_get_margin_bottom(a: number): number; +export function __wbg_set_margin_bottom(a: number, b: number): void; +export function __wbg_get_margin_left(a: number): number; +export function __wbg_set_margin_left(a: number, b: number): void; +export function margin_new(a: number): number; +export function margin_setTop(a: number, b: number): number; +export function margin_setRight(a: number, b: number): number; +export function margin_setBottom(a: number, b: number): number; +export function margin_setLeft(a: number, b: number): number; +export function margin_y(a: number, b: number): number; +export function margin_x(a: number, b: number): number; export function __wbg_matrix_free(a: number): void; export function __wbg_get_matrix_value(a: number, b: number): void; export function __wbg_set_matrix_value(a: number, b: number, c: number): void; -export function __wbg_get_matrix_width(a: number): number; -export function __wbg_set_matrix_width(a: number, b: number): void; +export function __wbg_get_matrix_margin(a: number): number; +export function __wbg_set_matrix_margin(a: number, b: number): void; +export function __wbg_get_matrix_mode(a: number): number; +export function __wbg_set_matrix_mode(a: number, b: number): void; export function __wbg_get_matrix_version(a: number): number; export function __wbg_set_matrix_version(a: number, b: number): void; export function __wbg_get_matrix_ecl(a: number): number; export function __wbg_set_matrix_ecl(a: number, b: number): void; export function __wbg_get_matrix_mask(a: number): number; export function __wbg_set_matrix_mask(a: number, b: number): void; +export function matrix_width(a: number): number; +export function matrix_height(a: number): number; export function __wbg_qroptions_free(a: number): void; export function qroptions_new(): number; export function qroptions_min_version(a: number, b: number): number; export function qroptions_min_ecl(a: number, b: number): number; export function qroptions_mode(a: number, b: number): number; export function qroptions_mask(a: number, b: number): number; +export function qroptions_margin(a: number, b: number): number; export function __wbg_svgoptions_free(a: number): void; export function svgoptions_new(): number; export function svgoptions_margin(a: number, b: number): number; diff --git a/src/app.tsx b/src/app.tsx index aa0f382..4c42d8f 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -4,29 +4,23 @@ import "virtual:uno.css"; import { Router } from "@solidjs/router"; import { FileRoutes } from "@solidjs/start/router"; import { Suspense } from "solid-js"; -import { QrContextProvider } from "./lib/QrContext"; -import { SvgContextProvider } from "./lib/SvgContext"; export default function App() { return ( <> - - - {props.children}}> - - - - - + {props.children}}> + + + ); } diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 5c66ac5..0d6d067 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -19,10 +19,14 @@ import { NumberInput } from "./NumberInput"; import { Select } from "./Select"; import { Switch } from "./Switch"; import { useSvgContext } from "~/lib/SvgContext"; +import { usePaintContext } from "~/lib/PaintContext"; +import { qroptions_margin } from "../../fuqr/pkg/fuqr_bg.wasm"; export function Editor(props: any) { - const { inputQr, setInputQr } = useQrContext(); + const { inputQr, setInputQr, outputQr } = useQrContext(); const { svgOptions, setSvgOptions } = useSvgContext(); + const { selections, scaleX, scaleY, setScaleXInPlace, setScaleYInPlace } = + usePaintContext(); // const [logoSize, setLogoSize] = createSignal(25); @@ -67,15 +71,15 @@ export function Editor(props: any) { - + {/* setSvgOptions("margin", v)} + value={inputQr.margin.top} + setValue={(v) => setInputQr("margin", (prev) => prev.setTop(v))} /> - + */} */} + + { + if (!selections().length) return + setScaleXInPlace((prev) => { + let width = Math.sqrt(prev.length); + selections().forEach((sel) => { + for (let i = sel.top; i < sel.bot; i++) { + for (let j = sel.left; j < sel.right; j++) { + prev[i * width + j] = v; + } + } + }); + return prev; + }); + }} + /> + + + { + if (!selections().length) return + setScaleYInPlace((prev) => { + let width = Math.sqrt(prev.length); + selections().forEach((sel) => { + for (let i = sel.top; i < sel.bot; i++) { + for (let j = sel.left; j < sel.right; j++) { + prev[i * width + j] = v; + } + } + }); + return prev; + }); + }} + /> + ); diff --git a/src/components/NumberInput.tsx b/src/components/NumberInput.tsx index a8ca909..c52ea04 100644 --- a/src/components/NumberInput.tsx +++ b/src/components/NumberInput.tsx @@ -25,7 +25,12 @@ export function NumberInput(props: Props) { ) { return; } - props.setValue(value); + + if (value !== props.value) { + // TODO, revisit this for x scale/y scale slider + // console.log("safe Set", value); + props.setValue(value); + } }; const [focused, setFocused] = createSignal(false); diff --git a/src/components/RenderGrid.tsx b/src/components/RenderGrid.tsx index bbbceb6..eff1d42 100644 --- a/src/components/RenderGrid.tsx +++ b/src/components/RenderGrid.tsx @@ -1,12 +1,13 @@ import { createEffect, createSignal, onCleanup } from "solid-js"; import { type SetStoreFunction } from "solid-js/store"; +import { usePaintContext, type BoxSelection } from "~/lib/PaintContext"; type Props = { width: number; height: number; color: number; - grid: number[]; - setGrid: SetStoreFunction; + // grid: number[]; + // setGrid: SetStoreFunction; }; enum Mode { @@ -27,6 +28,8 @@ function clamp(a: number, min: number, max: number) { } export function RenderGrid(props: Props) { + const { selections, setSelectionsInPlace } = usePaintContext(); + let canvas: HTMLCanvasElement; let ctx: CanvasRenderingContext2D; @@ -45,17 +48,17 @@ export function RenderGrid(props: Props) { }; const setPixel = (x: number, y: number) => { - if (props.color === props.grid[y * props.width + x]) return; + // if (props.color === props.grid[y * props.width + x]) return; - props.setGrid(y * props.width + x, props.color!); - if (props.color) { - ctx.fillRect(x * UNIT, y * UNIT, UNIT, UNIT); - } else { - ctx.clearRect(x * UNIT, y * UNIT, UNIT, UNIT); - } + // props.setGrid(y * props.width + x, props.color!); + // if (props.color) { + // ctx.fillRect(x * UNIT, y * UNIT, UNIT, UNIT); + // } else { + // ctx.clearRect(x * UNIT, y * UNIT, UNIT, UNIT); + // } }; - let selection: Selection | null; + let selection: BoxSelection | null; function onMouseMove(e: MouseEvent) { const { x, y } = getPos(e); @@ -66,7 +69,7 @@ export function RenderGrid(props: Props) { selectCtx.clearRect(0, 0, props.width * UNIT, props.height * UNIT); selectCtx.fillStyle = "rgb(59, 130, 246)"; - selectedBoxes.forEach((sel) => { + selections().forEach((sel) => { selectCtx.fillRect( sel.left * UNIT, sel.top * UNIT, @@ -142,7 +145,11 @@ export function RenderGrid(props: Props) { const onMouseUp = () => { if (selection != null) { - selectedBoxes.push(selection); + setSelectionsInPlace((prev) => { + // @ts-expect-error i'm right unless somehow this isn't synchronous + prev.push(selection); + return prev; + }); } document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); @@ -157,19 +164,13 @@ export function RenderGrid(props: Props) { const [brushSize, setBrushSize] = createSignal(1); - let selectedBoxes: Selection[] = []; + // let selectedBoxes: Selection[] = []; let prevX: number; let prevY: number; let deselectIntent: boolean; - createEffect(() => { - props.width; - props.height; - selectedBoxes = []; - }); - return ( <> 0; + deselectIntent = selections().length > 0; if (deselectIntent) { selectCtx.clearRect( @@ -208,8 +209,8 @@ export function RenderGrid(props: Props) { props.width * UNIT, props.height * UNIT ); - selectedBoxes = []; - selection = null + setSelectionsInPlace([]); + selection = null; } } diff --git a/src/components/qr/SvgPreview.tsx b/src/components/qr/SvgPreview.tsx index accee71..b1ee8bf 100644 --- a/src/components/qr/SvgPreview.tsx +++ b/src/components/qr/SvgPreview.tsx @@ -1,9 +1,9 @@ import { QrOptions, SvgOptions, - Version, get_svg, - SvgError, + QrError, + Version, SvgResult, } from "fuqr"; @@ -17,45 +17,43 @@ import { MODE_KEY, MODE_NAMES, } from "~/lib/options"; -import { createStore } from "solid-js/store"; import { RenderGrid } from "../RenderGrid"; -import { useQrContext } from "~/lib/QrContext"; +import { useQrContext, type OutputQr } from "~/lib/QrContext"; import { useSvgContext } from "~/lib/SvgContext"; +import { usePaintContext } from "~/lib/PaintContext"; const PIXELS_PER_MODULE = 20; export default function SvgPreview() { - const { inputQr } = useQrContext(); + const { inputQr, outputQr, setOutputQr } = useQrContext(); const { svgOptions } = useSvgContext(); + const { scaleX, scaleY } = usePaintContext(); - const [grid, setGrid] = createStore(Array(177 * 177).fill(100)); const svgResult = createMemo(() => { - console.log("svgResult", inputQr); + // what if iterate func return list of coords + // combine with list of true/false for placement + // + let output = outputQr(); + if (typeof output === "number") { + // See component + // svgResult() only called when outputQr() is successful + return null as unknown as SvgResult; + } 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 + .min_version(new Version(output.version)) + .min_ecl(output.ecl) + .mask(output.mask!) // wasm-bindgen types None as `undefined`, but null works + .mode(output.mode!); // wasm-bindgen types None as `undefined`, but null works let svgOpts = new SvgOptions() - // .finder_pattern(props.finderPattern) - // .finder_roundness(props.finderRoundness) - // .margin(margin) .foreground(svgOptions.fgColor) .background(svgOptions.bgColor) - .scale_matrix(grid); + .scale_x_matrix(new Uint8Array(scaleX())) + .scale_y_matrix(new Uint8Array(scaleY())); - let t; - try { - t = get_svg(inputQr.text, qrOptions, svgOpts); - } catch (e) { - // doesn't play nicely with unions - // this requires one "as any" instead of 20 - t = e as SvgResult; - } - console.log(typeof t); - return t; + // infallible b/c outputQr contains successful options + return get_svg(inputQr.text, qrOptions, svgOpts); }); function download(href: string, name: string) { @@ -73,33 +71,31 @@ export default function SvgPreview() { const bgSrc = () => URL.createObjectURL(svgOptions.bgImgFile!); const logoSrc = () => URL.createObjectURL(svgOptions.fgImgFile!); - const fullWidth = () => - svgResult().version["0"] * 4 + 17 + 2 * svgOptions.margin; - - const checkerboardPixel = () => (1 / fullWidth()) * 100; + const fullWidth = () => { + const output = outputQr() as OutputQr; + return output.version * 4 + 17 + output.margin.left + output.margin.right; + }; + const fullHeight = () => { + const output = outputQr() as OutputQr; + return output.version * 4 + 17 + output.margin.top + output.margin.bottom; + }; const [color, setColor] = createSignal(0); - // createEffect(() => { - // console.log("effect", svgResult()); - // }); - return ( <> - + Data exceeds max capacity - + {`Input cannot be encoded in ${ // @ts-expect-error props.mode not null b/c InvalidEncoding implies mode - MODE_NAMES[props.mode + 1] + MODE_NAMES[inputQr.mode + 1] } mode`} @@ -112,7 +108,9 @@ export default function SvgPreview() { "background-image": "repeating-conic-gradient(#ddd 0% 25%, #aaa 25% 50%)", "background-position": "50%", - "background-size": `${checkerboardPixel()}% ${checkerboardPixel()}%`, + "background-size": `${(1 / fullWidth()) * 100}% ${ + (1 / fullHeight()) * 100 + }%`, }} > @@ -144,10 +142,8 @@ export default function SvgPreview() {
@@ -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 ( -
- - -
- -
- -
-
-
- {/* */} -
-
+ + + +
+ + +
+ +
+ +
+
+
+ {/* */} +
+
+
+
+
); }