From f0d817c747c409f0263652d9819572e14df13f65 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 28 Feb 2026 08:08:04 +0300 Subject: [PATCH] Refactor: vendor kiwi-schema, extract types/constants, culori colors - Fork kiwi-schema from source TypeScript (evanw/kiwi), drop npm dep + patch - Deduplicate GUID/Color types into src/types.ts - Extract UI colors and default fills into src/constants.ts - Replace hand-rolled hex parsing with culori library - Extract demo shapes from App.vue into src/demo.ts - useUrlSearchParams from VueUse for test mode detection - Move fig-file/fig-import into kiwi/ (format layer) - Delete PoC files (poc-yoga.ts, poc-test.ts) --- bun.lock | 9 +- package.json | 5 +- patches/kiwi-schema@0.5.0.patch | 42 --- src/App.vue | 76 +---- src/components/ColorPicker.vue | 17 +- src/components/PropertiesPanel.vue | 10 +- src/constants.ts | 25 ++ src/demo.ts | 57 ++++ src/engine/clipboard.ts | 2 +- src/engine/color.ts | 31 ++ src/engine/poc-yoga.ts | 199 ----------- src/engine/renderer.ts | 33 +- src/engine/scene-graph.ts | 14 +- src/kiwi/codec.ts | 30 +- src/{engine => kiwi}/fig-file.ts | 4 +- src/{engine => kiwi}/fig-import.ts | 8 +- src/kiwi/kiwi-schema/bb.ts | 274 +++++++++++++++ src/kiwi/kiwi-schema/binary.ts | 104 ++++++ src/kiwi/kiwi-schema/index.ts | 5 + src/kiwi/kiwi-schema/js.ts | 311 ++++++++++++++++++ src/kiwi/kiwi-schema/parser.ts | 279 ++++++++++++++++ src/kiwi/kiwi-schema/schema.ts | 24 ++ src/kiwi/kiwi-schema/util.ts | 10 + src/kiwi/poc-test.ts | 180 ---------- src/kiwi/schema.ts | 2 +- src/stores/editor.ts | 27 +- src/types.ts | 11 + .../draw-and-delete-openpencil-darwin.png | Bin 18764 -> 18751 bytes .../draw-and-undo-openpencil-darwin.png | Bin 18754 -> 18762 bytes .../draw-ellipse-openpencil-darwin.png | Bin 25428 -> 18152 bytes .../draw-rectangle-openpencil-darwin.png | Bin 21657 -> 18752 bytes ...ctangle-then-move-it-openpencil-darwin.png | Bin 21115 -> 18229 bytes 32 files changed, 1195 insertions(+), 594 deletions(-) delete mode 100644 patches/kiwi-schema@0.5.0.patch create mode 100644 src/constants.ts create mode 100644 src/demo.ts create mode 100644 src/engine/color.ts delete mode 100644 src/engine/poc-yoga.ts rename src/{engine => kiwi}/fig-file.ts (95%) rename src/{engine => kiwi}/fig-import.ts (98%) create mode 100644 src/kiwi/kiwi-schema/bb.ts create mode 100644 src/kiwi/kiwi-schema/binary.ts create mode 100644 src/kiwi/kiwi-schema/index.ts create mode 100644 src/kiwi/kiwi-schema/js.ts create mode 100644 src/kiwi/kiwi-schema/parser.ts create mode 100644 src/kiwi/kiwi-schema/schema.ts create mode 100644 src/kiwi/kiwi-schema/util.ts delete mode 100644 src/kiwi/poc-test.ts create mode 100644 src/types.ts diff --git a/bun.lock b/bun.lock index 07642febb..3c91ff0ce 100644 --- a/bun.lock +++ b/bun.lock @@ -10,9 +10,9 @@ "@tauri-apps/plugin-opener": "^2", "@vueuse/core": "^14.2.1", "canvaskit-wasm": "^0.40.0", + "culori": "^4.0.2", "fflate": "^0.8.2", "fzstd": "^0.1.1", - "kiwi-schema": "^0.5.0", "reka-ui": "^2.8.2", "tailwindcss": "^4.2.1", "vue": "^3.5.29", @@ -30,9 +30,6 @@ }, }, }, - "patchedDependencies": { - "kiwi-schema@0.5.0": "patches/kiwi-schema@0.5.0.patch", - }, "packages": { "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], @@ -364,6 +361,8 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "culori": ["culori@4.0.2", "", {}, "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw=="], + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -388,8 +387,6 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "kiwi-schema": ["kiwi-schema@0.5.0", "", { "bin": { "kiwic": "cli.js" } }, "sha512-X+FpfU0yTEtc6aTHS7VwbOpvQwRt70+pXXWRI5fd6CvWhe7pSVC854TVo4Zo0x5/wwcWj+/9KUlXpdcP0dY9AA=="], - "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], diff --git a/package.json b/package.json index e827ece0d..c8312a944 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "@tauri-apps/plugin-opener": "^2", "@vueuse/core": "^14.2.1", "canvaskit-wasm": "^0.40.0", + "culori": "^4.0.2", "fflate": "^0.8.2", "fzstd": "^0.1.1", - "kiwi-schema": "^0.5.0", "reka-ui": "^2.8.2", "tailwindcss": "^4.2.1", "vue": "^3.5.29", @@ -41,8 +41,5 @@ "oxlint": "^1.50.0", "typescript": "~5.8.3", "vite": "^7.0.4" - }, - "patchedDependencies": { - "kiwi-schema@0.5.0": "patches/kiwi-schema@0.5.0.patch" } } \ No newline at end of file diff --git a/patches/kiwi-schema@0.5.0.patch b/patches/kiwi-schema@0.5.0.patch deleted file mode 100644 index b71c33044..000000000 --- a/patches/kiwi-schema@0.5.0.patch +++ /dev/null @@ -1,42 +0,0 @@ -diff --git a/node_modules/kiwi-schema/.bun-tag-962afbd98e4e08f9 b/.bun-tag-962afbd98e4e08f9 -new file mode 100644 -index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 -diff --git a/cli.js b/cli.js -index a729eb694b4dfd87302228d6e0fd3ff1cb75266e..67f33fb533e0dfd5176057d26288b7c324d441fb 100755 ---- a/cli.js -+++ b/cli.js -@@ -2078,7 +2078,7 @@ function verify(root) { - if (field.value <= 0) { - error("The id for field " + quote(field.name) + " must be positive", field.line, field.column); - } -- if (field.value > fields.length) { -+ if (false && field.value > fields.length) { - error("The id for field " + quote(field.name) + " cannot be larger than " + fields.length, field.line, field.column); - } - values.push(field.value); -diff --git a/kiwi-esm.js b/kiwi-esm.js -index f0f3f8b950abd8150e360511efcfdb10ea019bb9..b57c433a77444a38072364ab24c4e967734645f1 100644 ---- a/kiwi-esm.js -+++ b/kiwi-esm.js -@@ -2130,7 +2130,7 @@ function verify(root) { - if (field.value <= 0) { - error("The id for field " + quote(field.name) + " must be positive", field.line, field.column); - } -- if (field.value > fields.length) { -+ if (false && field.value > fields.length) { - error("The id for field " + quote(field.name) + " cannot be larger than " + fields.length, field.line, field.column); - } - values.push(field.value); -diff --git a/kiwi.js b/kiwi.js -index 26a46b6a2efbbdb9623a5275751eaaa10378e617..5a57b99fd2fd5fe087fc1c556672c58aa63b57ec 100644 ---- a/kiwi.js -+++ b/kiwi.js -@@ -2167,7 +2167,7 @@ function verify(root) { - if (field.value <= 0) { - error("The id for field " + quote(field.name) + " must be positive", field.line, field.column); - } -- if (field.value > fields.length) { -+ if (false && field.value > fields.length) { - error("The id for field " + quote(field.name) + " cannot be larger than " + fields.length, field.line, field.column); - } - values.push(field.value); diff --git a/src/App.vue b/src/App.vue index b3574dd9c..979599f81 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,8 @@ diff --git a/src/components/ColorPicker.vue b/src/components/ColorPicker.vue index d52e0662e..0e087cb0d 100644 --- a/src/components/ColorPicker.vue +++ b/src/components/ColorPicker.vue @@ -2,7 +2,9 @@ import { ref, computed, watch } from 'vue' import { PopoverRoot, PopoverTrigger, PopoverPortal, PopoverContent } from 'reka-ui' -import type { Color } from '../engine/scene-graph' +import { colorToHex, parseColor } from '../engine/color' + +import type { Color } from '../types' const props = defineProps<{ color: Color @@ -72,21 +74,14 @@ function emitColor() { } const hexValue = computed(() => { - const hex = (v: number) => - Math.round(v * 255) - .toString(16) - .padStart(2, '0') - return `${hex(props.color.r)}${hex(props.color.g)}${hex(props.color.b)}` + return colorToHex(props.color).slice(1) }) function onHexInput(e: Event) { const input = (e.target as HTMLInputElement).value.replace('#', '') if (input.length !== 6) return - const r = parseInt(input.slice(0, 2), 16) / 255 - const g = parseInt(input.slice(2, 4), 16) / 255 - const b = parseInt(input.slice(4, 6), 16) / 255 - if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return - emit('update', { r, g, b, a: alpha.value }) + const parsed = parseColor(`#${input}`) + emit('update', { ...parsed, a: alpha.value }) } const svAreaRef = ref(null) diff --git a/src/components/PropertiesPanel.vue b/src/components/PropertiesPanel.vue index 95033b0bd..c7f2a39b7 100644 --- a/src/components/PropertiesPanel.vue +++ b/src/components/PropertiesPanel.vue @@ -6,6 +6,8 @@ import ColorPicker from './ColorPicker.vue' import ScrubInput from './ScrubInput.vue' import { useEditorStore } from '../stores/editor' +import { DEFAULT_SHAPE_FILL } from '../constants' + import type { Color, Fill, Stroke, LayoutSizing, LayoutAlign, LayoutCounterAlign, SceneNode } from '../engine/scene-graph' const store = useEditorStore() @@ -155,13 +157,7 @@ function updateFillColor(index: number, color: Color) { function addFill() { if (!node.value) return - const fill: Fill = { - type: 'SOLID', - color: { r: 0.83, g: 0.83, b: 0.83, a: 1 }, - opacity: 1, - visible: true - } - store.updateNodeWithUndo(node.value.id, { fills: [...node.value.fills, fill] }, "Add fill") + store.updateNodeWithUndo(node.value.id, { fills: [...node.value.fills, { ...DEFAULT_SHAPE_FILL }] }, "Add fill") } function removeFill(index: number) { diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 000000000..c8da11aa6 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,25 @@ +import type { Color } from './types' +import type { Fill } from './engine/scene-graph' + +export const SELECTION_COLOR = { r: 0.23, g: 0.51, b: 0.96, a: 1 } satisfies Color +export const SNAP_COLOR = { r: 1.0, g: 0.0, b: 0.56, a: 1 } satisfies Color +export const CANVAS_BG_COLOR = { r: 0.96, g: 0.96, b: 0.96, a: 1 } satisfies Color + +export const DEFAULT_SHAPE_FILL: Fill = { + type: 'SOLID', + color: { r: 0.83, g: 0.83, b: 0.83, a: 1 }, + opacity: 1, + visible: true +} + +export const DEFAULT_FRAME_FILL: Fill = { + type: 'SOLID', + color: { r: 1, g: 1, b: 1, a: 1 }, + opacity: 1, + visible: true +} + +export const HANDLE_SIZE = 6 +export const ROTATION_HANDLE_OFFSET = 20 +export const SNAP_THRESHOLD = 5 +export const DRAG_DEAD_ZONE = 4 diff --git a/src/demo.ts b/src/demo.ts new file mode 100644 index 000000000..a14bbd589 --- /dev/null +++ b/src/demo.ts @@ -0,0 +1,57 @@ +import type { EditorStore } from './stores/editor' + +export function createDemoShapes(store: EditorStore) { + store.createShape('FRAME', 100, 80, 800, 500) + store.graph.updateNode(store.graph.getChildren(store.graph.rootId)[0].id, { + name: 'Desktop', + fills: [{ type: 'SOLID', color: { r: 1, g: 1, b: 1, a: 1 }, opacity: 1, visible: true }], + strokes: [ + { + color: { r: 0.87, g: 0.87, b: 0.87, a: 1 }, + weight: 1, + opacity: 1, + visible: true, + align: 'INSIDE' + } + ] + }) + + const shapes = [ + { + type: 'RECTANGLE' as const, + name: 'Blue card', + x: 150, y: 140, w: 240, h: 160, + color: { r: 0.23, g: 0.51, b: 0.96, a: 1 }, + radius: 12 + }, + { + type: 'ELLIPSE' as const, + name: 'Green circle', + x: 440, y: 160, w: 120, h: 120, + color: { r: 0.13, g: 0.77, b: 0.42, a: 1 } + }, + { + type: 'RECTANGLE' as const, + name: 'Orange rect', + x: 620, y: 140, w: 200, h: 100, + color: { r: 0.96, g: 0.52, b: 0.13, a: 1 }, + radius: 8 + }, + { + type: 'RECTANGLE' as const, + name: 'Purple pill', + x: 150, y: 360, w: 300, h: 56, + color: { r: 0.55, g: 0.36, b: 0.96, a: 1 }, + radius: 28 + } + ] + + for (const d of shapes) { + const id = store.createShape(d.type, d.x, d.y, d.w, d.h) + store.graph.updateNode(id, { + name: d.name, + cornerRadius: d.radius ?? 0, + fills: [{ type: 'SOLID', color: d.color, opacity: 1, visible: true }] + }) + } +} diff --git a/src/engine/clipboard.ts b/src/engine/clipboard.ts index a0e1bdc09..2b743c486 100644 --- a/src/engine/clipboard.ts +++ b/src/engine/clipboard.ts @@ -1,4 +1,4 @@ -import { decodeBinarySchema, compileSchema, ByteBuffer } from 'kiwi-schema' +import { decodeBinarySchema, compileSchema, ByteBuffer } from '../kiwi/kiwi-schema' import { inflateSync, deflateSync } from 'fflate' import type { SceneGraph, SceneNode, Fill, Stroke, Color, LayoutMode, LayoutSizing, LayoutAlign, LayoutCounterAlign, VectorNetwork } from './scene-graph' diff --git a/src/engine/color.ts b/src/engine/color.ts new file mode 100644 index 000000000..a932c136e --- /dev/null +++ b/src/engine/color.ts @@ -0,0 +1,31 @@ +import { parse, formatHex, converter } from 'culori' + +import type { Color } from '../types' + +const toRgb = converter('rgb') + +export function parseColor(input: string): Color { + const parsed = parse(input) + if (!parsed) return { r: 0, g: 0, b: 0, a: 1 } + const rgb = toRgb(parsed) + return { + r: rgb?.r ?? 0, + g: rgb?.g ?? 0, + b: rgb?.b ?? 0, + a: parsed.alpha ?? 1 + } +} + +export function colorToHex(color: Color): string { + return (formatHex({ mode: 'rgb', r: color.r, g: color.g, b: color.b }) ?? '#000000').toUpperCase() +} + +export function colorToFill(color: string | Color) { + const rgba = typeof color === 'string' ? parseColor(color) : color + return { + type: 'SOLID' as const, + color: { r: rgba.r, g: rgba.g, b: rgba.b, a: rgba.a }, + opacity: rgba.a, + visible: true + } +} diff --git a/src/engine/poc-yoga.ts b/src/engine/poc-yoga.ts deleted file mode 100644 index daa3af86f..000000000 --- a/src/engine/poc-yoga.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * PoC 2: Yoga WASM Layout Engine - * - * Validates: - * 1. yoga-layout WASM loads and initializes - * 2. Flex layout (auto-layout) matches Figma's behavior - * 3. Nested frames with padding, gap, alignment - * 4. Performance: layout 1000 nodes - */ - -import Yoga, { - Align, - Direction, - FlexDirection, - Gutter, - Justify, - Edge, - Wrap, - type Node as YogaNode -} from 'yoga-layout' - -function runTest() { - console.log('=== PoC 2: Yoga WASM Layout ===\n') - - // 1. Basic flex layout (Figma auto-layout: vertical, gap=16, padding=24) - { - const root = Yoga.Node.create() - root.setWidth(400) - root.setHeight('auto') - root.setFlexDirection(FlexDirection.Column) - root.setPadding(Edge.All, 24) - root.setGap(Gutter.All, 16) - - const child1 = Yoga.Node.create() - child1.setWidth('100%') - child1.setHeight(48) - - const child2 = Yoga.Node.create() - child2.setWidth('100%') - child2.setHeight(120) - - const child3 = Yoga.Node.create() - child3.setWidth('100%') - child3.setHeight(48) - - root.insertChild(child1, 0) - root.insertChild(child2, 1) - root.insertChild(child3, 2) - - root.calculateLayout(undefined, undefined, Direction.LTR) - - console.log('Test 1: Vertical auto-layout (gap=16, padding=24)') - console.log(` Root: ${root.getComputedWidth()}×${root.getComputedHeight()}`) - // Expected: height = 24 + 48 + 16 + 120 + 16 + 48 + 24 = 296 - const expectedHeight = 24 + 48 + 16 + 120 + 16 + 48 + 24 - const actualHeight = root.getComputedHeight() - console.log( - ` Expected height: ${expectedHeight}, Actual: ${actualHeight} ${actualHeight === expectedHeight ? '✅' : '❌'}` - ) - - console.log(` Child 1: y=${child1.getComputedTop()}, h=${child1.getComputedHeight()}`) - console.log(` Child 2: y=${child2.getComputedTop()}, h=${child2.getComputedHeight()}`) - console.log(` Child 3: y=${child3.getComputedTop()}, h=${child3.getComputedHeight()}`) - - // Verify positions - const y1 = child1.getComputedTop() // Expected: 24 - const y2 = child2.getComputedTop() // Expected: 24 + 48 + 16 = 88 - const y3 = child3.getComputedTop() // Expected: 88 + 120 + 16 = 224 - console.log(` Positions: ${y1 === 24 && y2 === 88 && y3 === 224 ? '✅' : '❌'}`) - - root.free() - child1.free() - child2.free() - child3.free() - } - - // 2. Horizontal auto-layout with space-between (Figma: distribute) - { - const root = Yoga.Node.create() - root.setWidth(600) - root.setHeight(80) - root.setFlexDirection(FlexDirection.Row) - root.setJustifyContent(Justify.SpaceBetween) - root.setAlignItems(Align.Center) - root.setPadding(Edge.Horizontal, 16) - - for (let i = 0; i < 4; i++) { - const child = Yoga.Node.create() - child.setWidth(100) - child.setHeight(48) - root.insertChild(child, i) - } - - root.calculateLayout(undefined, undefined, Direction.LTR) - - console.log('\nTest 2: Horizontal space-between') - const positions: string[] = [] - for (let i = 0; i < 4; i++) { - const child = root.getChild(i) - positions.push(`x=${child.getComputedLeft()} y=${child.getComputedTop()}`) - } - console.log(` Children: ${positions.join(' | ')}`) - // Items should be centered vertically (y = (80-48)/2 = 16) - const centeredCorrectly = root.getChild(0).getComputedTop() === 16 - console.log(` Vertical centering: ${centeredCorrectly ? '✅' : '❌'}`) - // Space-between with padding: available = 600-32 = 568, items = 4*100 = 400, gaps = 168/3 = 56 - const gap = root.getChild(1).getComputedLeft() - root.getChild(0).getComputedLeft() - 100 - console.log(` Gap between items: ${gap}px ${Math.abs(gap - 56) < 1 ? '✅' : '❌'}`) - - for (let i = root.getChildCount() - 1; i >= 0; i--) root.getChild(i).free() - root.free() - } - - // 3. Nested frames (card with header + content) - { - const card = Yoga.Node.create() - card.setWidth(320) - card.setFlexDirection(FlexDirection.Column) - card.setPadding(Edge.All, 0) - - const header = Yoga.Node.create() - header.setWidth('100%') - header.setHeight(56) - header.setFlexDirection(FlexDirection.Row) - header.setAlignItems(Align.Center) - header.setPadding(Edge.Horizontal, 16) - header.setGap(Gutter.All, 12) - - const avatar = Yoga.Node.create() - avatar.setWidth(32) - avatar.setHeight(32) - - const title = Yoga.Node.create() - title.setFlexGrow(1) - title.setHeight(20) - - header.insertChild(avatar, 0) - header.insertChild(title, 1) - - const content = Yoga.Node.create() - content.setWidth('100%') - content.setHeight(200) - - card.insertChild(header, 0) - card.insertChild(content, 1) - - card.calculateLayout(undefined, undefined, Direction.LTR) - - console.log('\nTest 3: Nested card layout') - console.log(` Card: ${card.getComputedWidth()}×${card.getComputedHeight()}`) - console.log(` Header: y=${header.getComputedTop()}, h=${header.getComputedHeight()}`) - console.log( - ` Avatar: x=${avatar.getComputedLeft()}, y=${avatar.getComputedTop()}, centered=${avatar.getComputedTop() === 12 ? '✅' : '❌'}` - ) - console.log( - ` Title: x=${title.getComputedLeft()}, w=${title.getComputedWidth()}, grows=${title.getComputedWidth() === 320 - 16 - 32 - 12 - 16 ? '✅' : '❌'}` - ) - console.log(` Content: y=${content.getComputedTop()}, h=${content.getComputedHeight()}`) - - avatar.free() - title.free() - header.free() - content.free() - card.free() - } - - // 4. Performance: layout 1000 nodes - { - const root = Yoga.Node.create() - root.setWidth(1200) - root.setFlexDirection(FlexDirection.Row) - root.setFlexWrap(Wrap.Wrap) // Wrap - root.setGap(Gutter.All, 8) - root.setPadding(Edge.All, 16) - - const nodes: YogaNode[] = [root] - for (let i = 0; i < 1000; i++) { - const child = Yoga.Node.create() - child.setWidth(80) - child.setHeight(80) - root.insertChild(child, i) - nodes.push(child) - } - - const t0 = performance.now() - root.calculateLayout(undefined, undefined, Direction.LTR) - const layoutTime = performance.now() - t0 - - console.log(`\nTest 4: Performance (1000 nodes)`) - console.log(` Layout time: ${layoutTime.toFixed(2)}ms ${layoutTime < 50 ? '✅' : '⚠️'}`) - console.log(` Root height: ${root.getComputedHeight()}px`) - - for (const n of nodes) n.free() - } - - console.log('\n✅ PoC 2 PASSED: Yoga WASM layout works correctly') -} - -runTest() diff --git a/src/engine/renderer.ts b/src/engine/renderer.ts index 82ae17a6c..986f82f36 100644 --- a/src/engine/renderer.ts +++ b/src/engine/renderer.ts @@ -1,3 +1,4 @@ +import { SELECTION_COLOR, SNAP_COLOR, CANVAS_BG_COLOR, HANDLE_SIZE, ROTATION_HANDLE_OFFSET } from '../constants' import type { SceneNode, SceneGraph, Fill } from './scene-graph' import type { SnapGuide } from './snap' import { vectorNetworkToPath } from './vector' @@ -52,6 +53,10 @@ export class SkiaRenderer { zoom = 1 dpr = 1 + private selColor(alpha = 1) { + return this.ck.Color4f(SELECTION_COLOR.r, SELECTION_COLOR.g, SELECTION_COLOR.b, alpha) + } + constructor(ck: CanvasKit, surface: Surface) { this.ck = ck this.surface = surface @@ -67,20 +72,20 @@ export class SkiaRenderer { this.selectionPaint = new ck.Paint() this.selectionPaint.setStyle(ck.PaintStyle.Stroke) this.selectionPaint.setStrokeWidth(1) - this.selectionPaint.setColor(ck.Color4f(0.23, 0.51, 0.96, 1.0)) + this.selectionPaint.setColor(this.selColor()) this.selectionPaint.setAntiAlias(true) this.parentOutlinePaint = new ck.Paint() this.parentOutlinePaint.setStyle(ck.PaintStyle.Stroke) this.parentOutlinePaint.setStrokeWidth(1) - this.parentOutlinePaint.setColor(ck.Color4f(0.23, 0.51, 0.96, 0.5)) + this.parentOutlinePaint.setColor(this.selColor(0.5)) this.parentOutlinePaint.setAntiAlias(true) this.parentOutlinePaint.setPathEffect(ck.PathEffect.MakeDash([4, 4], 0)) this.snapPaint = new ck.Paint() this.snapPaint.setStyle(ck.PaintStyle.Stroke) this.snapPaint.setStrokeWidth(1) - this.snapPaint.setColor(ck.Color4f(1.0, 0.0, 0.56, 1.0)) + this.snapPaint.setColor(this.ck.Color4f(SNAP_COLOR.r, SNAP_COLOR.g, SNAP_COLOR.b, 1)) this.snapPaint.setAntiAlias(true) this.textFont = new ck.Font(null, 14) @@ -109,7 +114,7 @@ export class SkiaRenderer { render(graph: SceneGraph, selectedIds: Set, overlays: RenderOverlays = {}): void { const canvas = this.surface.getCanvas() - canvas.clear(this.ck.Color4f(0.96, 0.96, 0.96, 1.0)) + canvas.clear(this.ck.Color4f(CANVAS_BG_COLOR.r, CANVAS_BG_COLOR.g, CANVAS_BG_COLOR.b, 1)) // Scene layer (world coordinates) canvas.save() @@ -225,7 +230,7 @@ export class SkiaRenderer { const rotLinePaint = new this.ck.Paint() rotLinePaint.setStyle(this.ck.PaintStyle.Stroke) rotLinePaint.setStrokeWidth(1) - rotLinePaint.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 1.0)) + rotLinePaint.setColor(this.this.selColor()) rotLinePaint.setAntiAlias(true) canvas.drawLine(mx, y1, mx, rotHandleY, rotLinePaint) @@ -280,7 +285,7 @@ export class SkiaRenderer { if (node.type === 'FRAME' && node.parentId === graph.rootId) { const labelPaint = new this.ck.Paint() labelPaint.setStyle(this.ck.PaintStyle.Fill) - labelPaint.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 1.0)) + labelPaint.setColor(this.this.selColor()) labelPaint.setAntiAlias(true) canvas.drawText(node.name, sx1, sy1 - 8, labelPaint, this.labelFont) labelPaint.delete() @@ -302,7 +307,7 @@ export class SkiaRenderer { const pillPaint = new this.ck.Paint() pillPaint.setStyle(this.ck.PaintStyle.Fill) - pillPaint.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 1.0)) + pillPaint.setColor(this.this.selColor()) pillPaint.setAntiAlias(true) const rrect = this.ck.RRectXY(this.ck.LTRBRect(pillX, pillY, pillX + pillW, pillY + pillH), 4, 4) @@ -405,7 +410,7 @@ export class SkiaRenderer { const dashPaint = new this.ck.Paint() dashPaint.setStyle(this.ck.PaintStyle.Stroke) dashPaint.setStrokeWidth(1) - dashPaint.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 0.6)) + dashPaint.setColor(this.selColor(0.6)) dashPaint.setAntiAlias(true) canvas.drawRect(this.ck.LTRBRect(minX, minY, maxX, maxY), dashPaint) @@ -490,7 +495,7 @@ export class SkiaRenderer { const fill = new this.ck.Paint() fill.setStyle(this.ck.PaintStyle.Fill) - fill.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 0.08)) + fill.setColor(this.selColor(0.08)) canvas.drawRect(rect, fill) canvas.drawRect(rect, this.selectionPaint) fill.delete() @@ -507,7 +512,7 @@ export class SkiaRenderer { const paint = new this.ck.Paint() paint.setStyle(this.ck.PaintStyle.Stroke) paint.setStrokeWidth(2) - paint.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 1.0)) + paint.setColor(this.this.selColor()) paint.setAntiAlias(true) if (indicator.direction === 'HORIZONTAL') { @@ -560,7 +565,7 @@ export class SkiaRenderer { const highlight = new this.ck.Paint() highlight.setStyle(this.ck.PaintStyle.Stroke) highlight.setStrokeWidth(2 / this.zoom) - highlight.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 0.8)) + highlight.setColor(this.selColor(0.8)) highlight.setAntiAlias(true) canvas.drawRect(this.ck.LTRBRect(0, 0, node.width, node.height), highlight) highlight.delete() @@ -747,13 +752,13 @@ export class SkiaRenderer { const pathPaint = new this.ck.Paint() pathPaint.setStyle(this.ck.PaintStyle.Stroke) pathPaint.setStrokeWidth(2) - pathPaint.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 1.0)) + pathPaint.setColor(this.this.selColor()) pathPaint.setAntiAlias(true) const handlePaint = new this.ck.Paint() handlePaint.setStyle(this.ck.PaintStyle.Stroke) handlePaint.setStrokeWidth(1) - handlePaint.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 0.5)) + handlePaint.setColor(this.selColor(0.5)) handlePaint.setAntiAlias(true) const vertexFill = new this.ck.Paint() @@ -764,7 +769,7 @@ export class SkiaRenderer { const vertexStroke = new this.ck.Paint() vertexStroke.setStyle(this.ck.PaintStyle.Stroke) vertexStroke.setStrokeWidth(2) - vertexStroke.setColor(this.ck.Color4f(0.23, 0.51, 0.96, 1.0)) + vertexStroke.setColor(this.this.selColor()) vertexStroke.setAntiAlias(true) const toScreen = (x: number, y: number) => ({ diff --git a/src/engine/scene-graph.ts b/src/engine/scene-graph.ts index ed00f1d1f..38f8fcc40 100644 --- a/src/engine/scene-graph.ts +++ b/src/engine/scene-graph.ts @@ -1,3 +1,5 @@ +export type { GUID, Color } from '../types' + export type HandleMirroring = 'NONE' | 'ANGLE' | 'ANGLE_AND_LENGTH' export type WindingRule = 'NONZERO' | 'EVENODD' @@ -40,17 +42,7 @@ export type NodeType = | 'GROUP' | 'SECTION' -export interface GUID { - sessionID: number - localID: number -} - -export interface Color { - r: number - g: number - b: number - a: number -} +import type { Color } from '../types' export interface Fill { type: 'SOLID' diff --git a/src/kiwi/codec.ts b/src/kiwi/codec.ts index 333dcfa7a..b71dc6440 100644 --- a/src/kiwi/codec.ts +++ b/src/kiwi/codec.ts @@ -7,24 +7,14 @@ */ import { decompress as zstdDecompress } from 'fzstd' -import { compileSchema, type Schema } from 'kiwi-schema' +import { compileSchema } from './kiwi-schema' +import type { Schema } from './kiwi-schema' + +import { parseColor } from '../engine/color' import { isZstdCompressed, getKiwiMessageType } from './protocol.ts' import figmaSchema from './schema.ts' -function parseColor(color: string): Color { - if (color.startsWith('#')) { - const hex = color.slice(1) - return { - r: parseInt(hex.slice(0, 2), 16) / 255, - g: parseInt(hex.slice(2, 4), 16) / 255, - b: parseInt(hex.slice(4, 6), 16) / 255, - a: hex.length > 6 ? parseInt(hex.slice(6, 8), 16) / 255 : 1 - } - } - return { r: 0, g: 0, b: 0, a: 1 } -} - interface CompiledSchema { encodeMessage(message: unknown): Uint8Array decodeMessage(data: Uint8Array): unknown @@ -164,17 +154,9 @@ export function peekMessageType(data: Uint8Array): number | null { // Type definitions -export interface GUID { - sessionID: number - localID: number -} +export type { GUID, Color } from '../types' -export interface Color { - r: number - g: number - b: number - a: number -} +import type { GUID, Color } from '../types' export interface Vector { x: number diff --git a/src/engine/fig-file.ts b/src/kiwi/fig-file.ts similarity index 95% rename from src/engine/fig-file.ts rename to src/kiwi/fig-file.ts index c2d4f7a94..e9c434e7a 100644 --- a/src/engine/fig-file.ts +++ b/src/kiwi/fig-file.ts @@ -1,9 +1,9 @@ import { unzipSync } from 'fflate' -import { initCodec, decodeMessage } from '../kiwi/codec' +import { initCodec, decodeMessage } from './codec' import { importNodeChanges } from './fig-import' -import type { SceneGraph } from './scene-graph' +import type { SceneGraph } from '../engine/scene-graph' /** * Parse a .fig file into a SceneGraph. diff --git a/src/engine/fig-import.ts b/src/kiwi/fig-import.ts similarity index 98% rename from src/engine/fig-import.ts rename to src/kiwi/fig-import.ts index 77016e738..53352d66a 100644 --- a/src/engine/fig-import.ts +++ b/src/kiwi/fig-import.ts @@ -1,8 +1,8 @@ -import { SceneGraph } from './scene-graph' -import { decodeVectorNetworkBlob } from './vector' +import { SceneGraph } from '../engine/scene-graph' +import { decodeVectorNetworkBlob } from '../engine/vector' -import type { NodeChange, Paint, Effect as KiwiEffect, GUID } from '../kiwi/codec' -import type { NodeType, Fill, Stroke, Effect, Color, LayoutMode, LayoutSizing, LayoutAlign, LayoutCounterAlign, VectorNetwork } from './scene-graph' +import type { NodeChange, Paint, Effect as KiwiEffect, GUID } from './codec' +import type { NodeType, Fill, Stroke, Effect, Color, LayoutMode, LayoutSizing, LayoutAlign, LayoutCounterAlign, VectorNetwork } from '../engine/scene-graph' function ext(nc: NodeChange): Record { return nc as unknown as Record diff --git a/src/kiwi/kiwi-schema/bb.ts b/src/kiwi/kiwi-schema/bb.ts new file mode 100644 index 000000000..a65f2613d --- /dev/null +++ b/src/kiwi/kiwi-schema/bb.ts @@ -0,0 +1,274 @@ +let int32 = new Int32Array(1); +let float32 = new Float32Array(int32.buffer); + +export class ByteBuffer { + private _data: Uint8Array; + private _index: number; + length: number; + + constructor(data?: Uint8Array) { + if (data && !(data instanceof Uint8Array)) { + throw new Error('Must initialize a ByteBuffer with a Uint8Array'); + } + this._data = data || new Uint8Array(256); + this._index = 0; + this.length = data ? data.length : 0; + } + + toUint8Array(): Uint8Array { + return this._data.subarray(0, this.length); + } + + readByte(): number { + if (this._index + 1 > this._data.length) { + throw new Error('Index out of bounds'); + } + return this._data[this._index++]; + } + + readByteArray(): Uint8Array { + let length = this.readVarUint(); + let start = this._index; + let end = start + length; + if (end > this._data.length) { + throw new Error('Read array out of bounds'); + } + this._index = end; + // Copy into a new array instead of just creating another view. + let result = new Uint8Array(length); + result.set(this._data.subarray(start, end)); + return result; + } + + readVarFloat(): number { + let index = this._index; + let data = this._data; + let length = data.length; + + // Optimization: use a single byte to store zero + if (index + 1 > length) { + throw new Error('Index out of bounds'); + } + let first = data[index]; + if (first === 0) { + this._index = index + 1; + return 0; + } + + // Endian-independent 32-bit read + if (index + 4 > length) { + throw new Error('Index out of bounds'); + } + let bits = first | (data[index + 1] << 8) | (data[index + 2] << 16) | (data[index + 3] << 24); + this._index = index + 4; + + // Move the exponent back into place + bits = (bits << 23) | (bits >>> 9); + + // Reinterpret as a floating-point number + int32[0] = bits; + return float32[0]; + } + + readVarUint(): number { + let value = 0; + let shift = 0; + do { + var byte = this.readByte(); + value |= (byte & 127) << shift; + shift += 7; + } while (byte & 128 && shift < 35); + return value >>> 0; + } + + readVarInt(): number { + let value = this.readVarUint() | 0; + return value & 1 ? ~(value >>> 1) : value >>> 1; + } + + readVarUint64(): bigint { + let value = BigInt(0); + let shift = BigInt(0); + let seven = BigInt(7); + let byte: number; + while ((byte = this.readByte()) & 128 && shift < 56) { + value |= BigInt(byte & 127) << shift; + shift += seven; + } + value |= BigInt(byte) << shift; + return value; + } + + readVarInt64(): bigint { + let value = this.readVarUint64(); + let one = BigInt(1); + let sign = value & one; + value >>= one; + return sign ? ~value : value; + } + + readString(): string { + let result = ''; + + while (true) { + let codePoint; + + // Decode UTF-8 + let a = this.readByte(); + if (a < 0xC0) { + codePoint = a; + } else { + let b = this.readByte(); + if (a < 0xE0) { + codePoint = ((a & 0x1F) << 6) | (b & 0x3F); + } else { + let c = this.readByte(); + if (a < 0xF0) { + codePoint = ((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F); + } else { + let d = this.readByte(); + codePoint = ((a & 0x07) << 18) | ((b & 0x3F) << 12) | ((c & 0x3F) << 6) | (d & 0x3F); + } + } + } + + // Strings are null-terminated + if (codePoint === 0) { + break; + } + + // Encode UTF-16 + if (codePoint < 0x10000) { + result += String.fromCharCode(codePoint); + } else { + codePoint -= 0x10000; + result += String.fromCharCode((codePoint >> 10) + 0xD800, (codePoint & ((1 << 10) - 1)) + 0xDC00); + } + } + + return result; + } + + private _growBy(amount: number): void { + if (this.length + amount > this._data.length) { + let data = new Uint8Array(this.length + amount << 1); + data.set(this._data); + this._data = data; + } + this.length += amount; + } + + writeByte(value: number): void { + let index = this.length; + this._growBy(1); + this._data[index] = value; + } + + writeByteArray(value: Uint8Array): void { + this.writeVarUint(value.length); + let index = this.length; + this._growBy(value.length); + this._data.set(value, index); + } + + writeVarFloat(value: number): void { + let index = this.length; + + // Reinterpret as an integer + float32[0] = value; + let bits = int32[0]; + + // Move the exponent to the first 8 bits + bits = (bits >>> 23) | (bits << 9); + + // Optimization: use a single byte to store zero and denormals (check for an exponent of 0) + if ((bits & 255) === 0) { + this.writeByte(0); + return; + } + + // Endian-independent 32-bit write + this._growBy(4); + let data = this._data; + data[index] = bits; + data[index + 1] = bits >> 8; + data[index + 2] = bits >> 16; + data[index + 3] = bits >> 24; + } + + writeVarUint(value: number): void { + if (value < 0 || value > 0xFFFF_FFFF) throw new Error('Outside uint range: ' + value); + do { + let byte = value & 127; + value >>>= 7; + this.writeByte(value ? byte | 128 : byte); + } while (value); + } + + writeVarInt(value: number): void { + if (value < -0x8000_0000 || value > 0x7FFF_FFFF) throw new Error('Outside int range: ' + value); + this.writeVarUint(((value << 1) ^ (value >> 31)) >>> 0); + } + + writeVarUint64(value: bigint | string): void { + if (typeof value === 'string') value = BigInt(value); + else if (typeof value !== 'bigint') throw new Error('Expected bigint but got ' + typeof value + ': ' + value); + if (value < 0 || value > BigInt('0xFFFFFFFFFFFFFFFF')) throw new Error('Outside uint64 range: ' + value); + let mask = BigInt(127); + let seven = BigInt(7); + for (let i = 0; value > mask && i < 8; i++) { + this.writeByte(Number(value & mask) | 128); + value >>= seven; + } + this.writeByte(Number(value)); + } + + writeVarInt64(value: bigint | string): void { + if (typeof value === 'string') value = BigInt(value); + else if (typeof value !== 'bigint') throw new Error('Expected bigint but got ' + typeof value + ': ' + value); + if (value < -BigInt('0x8000000000000000') || value > BigInt('0x7FFFFFFFFFFFFFFF')) throw new Error('Outside int64 range: ' + value); + let one = BigInt(1); + this.writeVarUint64(value < 0 ? ~(value << one) : value << one); + } + + writeString(value: string): void { + let codePoint; + + for (let i = 0; i < value.length; i++) { + // Decode UTF-16 + let a = value.charCodeAt(i); + if (i + 1 === value.length || a < 0xD800 || a >= 0xDC00) { + codePoint = a; + } else { + let b = value.charCodeAt(++i); + codePoint = (a << 10) + b + (0x10000 - (0xD800 << 10) - 0xDC00); + } + + // Strings are null-terminated + if (codePoint === 0) { + throw new Error('Cannot encode a string containing the null character'); + } + + // Encode UTF-8 + if (codePoint < 0x80) { + this.writeByte(codePoint); + } else { + if (codePoint < 0x800) { + this.writeByte(((codePoint >> 6) & 0x1F) | 0xC0); + } else { + if (codePoint < 0x10000) { + this.writeByte(((codePoint >> 12) & 0x0F) | 0xE0); + } else { + this.writeByte(((codePoint >> 18) & 0x07) | 0xF0); + this.writeByte(((codePoint >> 12) & 0x3F) | 0x80); + } + this.writeByte(((codePoint >> 6) & 0x3F) | 0x80); + } + this.writeByte((codePoint & 0x3F) | 0x80); + } + } + + // Strings are null-terminated + this.writeByte(0); + } +} diff --git a/src/kiwi/kiwi-schema/binary.ts b/src/kiwi/kiwi-schema/binary.ts new file mode 100644 index 000000000..8260c7707 --- /dev/null +++ b/src/kiwi/kiwi-schema/binary.ts @@ -0,0 +1,104 @@ +import { ByteBuffer } from "./bb"; +import { Schema, Field, Definition, DefinitionKind } from "./schema"; + +let types: (string | null)[] = ['bool', 'byte', 'int', 'uint', 'float', 'string', 'int64', 'uint64']; +let kinds: DefinitionKind[] = ['ENUM', 'STRUCT', 'MESSAGE']; + +export function decodeBinarySchema(buffer: Uint8Array | ByteBuffer): Schema { + let bb = buffer instanceof ByteBuffer ? buffer : new ByteBuffer(buffer); + let definitionCount = bb.readVarUint(); + let definitions: Definition[] = []; + + // Read in the schema + for (let i = 0; i < definitionCount; i++) { + let definitionName = bb.readString(); + let kind = bb.readByte(); + let fieldCount = bb.readVarUint(); + let fields: Field[] = []; + + for (let j = 0; j < fieldCount; j++) { + let fieldName = bb.readString(); + let type = bb.readVarInt(); + let isArray = !!(bb.readByte() & 1); + let value = bb.readVarUint(); + + fields.push({ + name: fieldName, + line: 0, + column: 0, + type: kinds[kind] === 'ENUM' ? null : type as any, + isArray: isArray, + isDeprecated: false, + value: value, + }); + } + + definitions.push({ + name: definitionName, + line: 0, + column: 0, + kind: kinds[kind], + fields: fields, + }); + } + + // Bind type names afterwards + for (let i = 0; i < definitionCount; i++) { + let fields = definitions[i].fields; + for (let j = 0; j < fields.length; j++) { + let field = fields[j]; + let type = field.type as any as number | null; + + if (type !== null && type < 0) { + if (~type >= types.length) { + throw new Error('Invalid type ' + type); + } + field.type = types[~type]; + } + + else { + if (type !== null && type >= definitions.length) { + throw new Error('Invalid type ' + type); + } + field.type = type === null ? null : definitions[type].name; + } + } + } + + return { + package: null, + definitions: definitions, + }; +} + +export function encodeBinarySchema(schema: Schema): Uint8Array { + let bb = new ByteBuffer(); + let definitions = schema.definitions; + let definitionIndex: { [name: string]: number } = {}; + + bb.writeVarUint(definitions.length); + + for (let i = 0; i < definitions.length; i++) { + definitionIndex[definitions[i].name] = i; + } + + for (let i = 0; i < definitions.length; i++) { + let definition = definitions[i]; + + bb.writeString(definition.name); + bb.writeByte(kinds.indexOf(definition.kind)); + bb.writeVarUint(definition.fields.length); + + for (let j = 0; j < definition.fields.length; j++) { + let field = definition.fields[j]; + let type = types.indexOf(field.type); + + bb.writeString(field.name); + bb.writeVarInt(type === -1 ? definitionIndex[field.type!] : ~type); + bb.writeByte(field.isArray ? 1 : 0); + bb.writeVarUint(field.value); + } + } + + return bb.toUint8Array(); +} diff --git a/src/kiwi/kiwi-schema/index.ts b/src/kiwi/kiwi-schema/index.ts new file mode 100644 index 000000000..7d45da2f9 --- /dev/null +++ b/src/kiwi/kiwi-schema/index.ts @@ -0,0 +1,5 @@ +export type { Schema, Definition, Field } from './schema' +export { ByteBuffer } from './bb' +export { compileSchema } from './js' +export { decodeBinarySchema, encodeBinarySchema } from './binary' +export { parseSchema } from './parser' diff --git a/src/kiwi/kiwi-schema/js.ts b/src/kiwi/kiwi-schema/js.ts new file mode 100644 index 000000000..5d30de6cd --- /dev/null +++ b/src/kiwi/kiwi-schema/js.ts @@ -0,0 +1,311 @@ +import { Schema, Definition } from "./schema"; +import { ByteBuffer } from "./bb"; +import { error, quote } from "./util"; + +function compileDecode(definition: Definition, definitions: { [name: string]: Definition }): string { + let lines: string[] = []; + let indent = ' '; + + lines.push('function (bb) {'); + lines.push(' var result = {};'); + lines.push(' if (!(bb instanceof this.ByteBuffer)) {'); + lines.push(' bb = new this.ByteBuffer(bb);'); + lines.push(' }'); + lines.push(''); + + if (definition.kind === 'MESSAGE') { + lines.push(' while (true) {'); + lines.push(' switch (bb.readVarUint()) {'); + lines.push(' case 0:'); + lines.push(' return result;'); + lines.push(''); + indent = ' '; + } + + for (let i = 0; i < definition.fields.length; i++) { + let field = definition.fields[i]; + let code: string; + + switch (field.type) { + case 'bool': { + code = '!!bb.readByte()'; + break; + } + + case 'byte': { + code = 'bb.readByte()'; // only used if not array + break; + } + + case 'int': { + code = 'bb.readVarInt()'; + break; + } + + case 'uint': { + code = 'bb.readVarUint()'; + break; + } + + case 'float': { + code = 'bb.readVarFloat()'; + break; + } + + case 'string': { + code = 'bb.readString()'; + break; + } + + case 'int64': { + code = 'bb.readVarInt64()'; + break; + } + + case 'uint64': { + code = 'bb.readVarUint64()'; + break; + } + + default: { + let type = definitions[field.type!]; + if (!type) { + error('Invalid type ' + quote(field.type!) + ' for field ' + quote(field.name), field.line, field.column); + } else if (type.kind === 'ENUM') { + code = 'this[' + quote(type.name) + '][bb.readVarUint()]'; + } else { + code = 'this[' + quote('decode' + type.name) + '](bb)'; + } + } + } + + if (definition.kind === 'MESSAGE') { + lines.push(' case ' + field.value + ':'); + } + + if (field.isArray) { + if (field.isDeprecated) { + if (field.type === 'byte') { + lines.push(indent + 'bb.readByteArray();'); + } else { + lines.push(indent + 'var length = bb.readVarUint();'); + lines.push(indent + 'while (length-- > 0) ' + code + ';'); + } + } else { + if (field.type === 'byte') { + lines.push(indent + 'result[' + quote(field.name) + '] = bb.readByteArray();'); + } else { + lines.push(indent + 'var length = bb.readVarUint();'); + lines.push(indent + 'var values = result[' + quote(field.name) + '] = Array(length);'); + lines.push(indent + 'for (var i = 0; i < length; i++) values[i] = ' + code + ';'); + } + } + } + + else { + if (field.isDeprecated) { + lines.push(indent + code + ';'); + } else { + lines.push(indent + 'result[' + quote(field.name) + '] = ' + code + ';'); + } + } + + if (definition.kind === 'MESSAGE') { + lines.push(' break;'); + lines.push(''); + } + } + + if (definition.kind === 'MESSAGE') { + lines.push(' default:'); + lines.push(' throw new Error("Attempted to parse invalid message");'); + lines.push(' }'); + lines.push(' }'); + } + + else { + lines.push(' return result;'); + } + + lines.push('}'); + + return lines.join('\n'); +} + +function compileEncode(definition: Definition, definitions: { [name: string]: Definition }): string { + let lines: string[] = []; + + lines.push('function (message, bb) {'); + lines.push(' var isTopLevel = !bb;'); + lines.push(' if (isTopLevel) bb = new this.ByteBuffer();'); + + for (let j = 0; j < definition.fields.length; j++) { + let field = definition.fields[j]; + let code: string; + + if (field.isDeprecated) { + continue; + } + + switch (field.type) { + case 'bool': { + code = 'bb.writeByte(value);'; + break; + } + + case 'byte': { + code = 'bb.writeByte(value);'; // only used if not array + break; + } + + case 'int': { + code = 'bb.writeVarInt(value);'; + break; + } + + case 'uint': { + code = 'bb.writeVarUint(value);'; + break; + } + + case 'float': { + code = 'bb.writeVarFloat(value);'; + break; + } + + case 'string': { + code = 'bb.writeString(value);'; + break; + } + + case 'int64': { + code = 'bb.writeVarInt64(value);'; + break; + } + + case 'uint64': { + code = 'bb.writeVarUint64(value);'; + break; + } + + default: { + let type = definitions[field.type!]; + if (!type) { + throw new Error('Invalid type ' + quote(field.type!) + ' for field ' + quote(field.name)); + } else if (type.kind === 'ENUM') { + code = + 'var encoded = this[' + quote(type.name) + '][value]; ' + + 'if (encoded === void 0) throw new Error("Invalid value " + JSON.stringify(value) + ' + quote(' for enum ' + quote(type.name)) + '); ' + + 'bb.writeVarUint(encoded);'; + } else { + code = 'this[' + quote('encode' + type.name) + '](value, bb);'; + } + } + } + + lines.push(''); + lines.push(' var value = message[' + quote(field.name) + '];'); + lines.push(' if (value != null) {'); // Comparing with null using "!=" also checks for undefined + + if (definition.kind === 'MESSAGE') { + lines.push(' bb.writeVarUint(' + field.value + ');'); + } + + if (field.isArray) { + if (field.type === 'byte') { + lines.push(' bb.writeByteArray(value);'); + } else { + lines.push(' var values = value, n = values.length;'); + lines.push(' bb.writeVarUint(n);'); + lines.push(' for (var i = 0; i < n; i++) {'); + lines.push(' value = values[i];'); + lines.push(' ' + code); + lines.push(' }'); + } + } + + else { + lines.push(' ' + code); + } + + if (definition.kind === 'STRUCT') { + lines.push(' } else {'); + lines.push(' throw new Error(' + quote('Missing required field ' + quote(field.name)) + ');'); + } + + lines.push(' }'); + } + + // A field id of zero is reserved to indicate the end of the message + if (definition.kind === 'MESSAGE') { + lines.push(' bb.writeVarUint(0);'); + } + + lines.push(''); + lines.push(' if (isTopLevel) return bb.toUint8Array();'); + lines.push('}'); + + return lines.join('\n'); +} + +export function compileSchemaJS(schema: Schema): string { + let definitions: { [name: string]: Definition } = {}; + let name = schema.package; + let js: string[] = []; + + if (name !== null) { + js.push('var ' + name + ' = exports || ' + name + ' || {}, exports;'); + } else { + js.push('var exports = exports || {};'); + name = 'exports'; + } + + js.push(name + '.ByteBuffer = ' + name + '.ByteBuffer || require("kiwi-schema").ByteBuffer;'); + + for (let i = 0; i < schema.definitions.length; i++) { + let definition = schema.definitions[i]; + definitions[definition.name] = definition; + } + + for (let i = 0; i < schema.definitions.length; i++) { + let definition = schema.definitions[i]; + + switch (definition.kind) { + case 'ENUM': { + let value: any = {}; + for (let j = 0; j < definition.fields.length; j++) { + let field = definition.fields[j]; + value[field.name] = field.value; + value[field.value] = field.name; + } + js.push(name + '[' + quote(definition.name) + '] = ' + JSON.stringify(value, null, 2) + ';'); + break; + } + + case 'STRUCT': + case 'MESSAGE': { + js.push(''); + js.push(name + '[' + quote('decode' + definition.name) + '] = ' + compileDecode(definition, definitions) + ';'); + js.push(''); + js.push(name + '[' + quote('encode' + definition.name) + '] = ' + compileEncode(definition, definitions) + ';'); + break; + } + + default: { + error('Invalid definition kind ' + quote(definition.kind), definition.line, definition.column); + break; + } + } + } + + js.push(''); + return js.join('\n'); +} + +export function compileSchema(schema: Schema): any { + let result = { + ByteBuffer: ByteBuffer, + }; + new Function('exports', compileSchemaJS(schema))(result); + return result; +} diff --git a/src/kiwi/kiwi-schema/parser.ts b/src/kiwi/kiwi-schema/parser.ts new file mode 100644 index 000000000..498a79814 --- /dev/null +++ b/src/kiwi/kiwi-schema/parser.ts @@ -0,0 +1,279 @@ +import { Schema, Definition, Field, DefinitionKind } from "./schema"; +import { error, quote } from "./util"; + +export let nativeTypes = [ + 'bool', + 'byte', + 'float', + 'int', + 'int64', + 'string', + 'uint', + 'uint64', +]; + +// These are special names on the object returned by compileSchema() +export let reservedNames = [ + 'ByteBuffer', + 'package', +]; + +let regex = /((?:-|\b)\d+\b|[=;{}]|\[\]|\[deprecated\]|\b[A-Za-z_][A-Za-z0-9_]*\b|\/\/.*|\s+)/g; +let identifier = /^[A-Za-z_][A-Za-z0-9_]*$/; +let whitespace = /^\/\/.*|\s+$/; +let equals = /^=$/; +let endOfFile = /^$/; +let semicolon = /^;$/; +let integer = /^-?\d+$/; +let leftBrace = /^\{$/; +let rightBrace = /^\}$/; +let arrayToken = /^\[\]$/; +let enumKeyword = /^enum$/; +let structKeyword = /^struct$/; +let messageKeyword = /^message$/; +let packageKeyword = /^package$/; +let deprecatedToken = /^\[deprecated\]$/; + +interface Token { + text: string + line: number + column: number +} + +function tokenize(text: string): Token[] { + let parts = text.split(regex); + let tokens = []; + let column = 0; + let line = 0; + + for (let i = 0; i < parts.length; i++) { + let part = parts[i]; + + // Keep non-whitespace tokens + if (i & 1) { + if (!whitespace.test(part)) { + tokens.push({ + text: part, + line: line + 1, + column: column + 1, + }); + } + } + + // Detect syntax errors + else if (part !== '') { + error('Syntax error ' + quote(part), line + 1, column + 1); + } + + // Keep track of the line and column counts + let lines = part.split('\n'); + if (lines.length > 1) column = 0; + line += lines.length - 1; + column += lines[lines.length - 1].length; + } + + // End-of-file token + tokens.push({ + text: '', + line: line, + column: column, + }); + + return tokens; +} + +function parse(tokens: Token[]): Schema { + function current(): Token { + return tokens[index]; + } + + function eat(test: RegExp): boolean { + if (test.test(current().text)) { + index++; + return true; + } + return false; + } + + function expect(test: RegExp, expected: string): void { + if (!eat(test)) { + let token = current(); + error('Expected ' + expected + ' but found ' + quote(token.text), token.line, token.column); + } + } + + function unexpectedToken(): never { + let token = current(); + error('Unexpected token ' + quote(token.text), token.line, token.column); + } + + let definitions: Definition[] = []; + let packageText = null; + let index = 0; + + if (eat(packageKeyword)) { + packageText = current().text; + expect(identifier, 'identifier'); + expect(semicolon, '";"'); + } + + while (index < tokens.length && !eat(endOfFile)) { + let fields: Field[] = []; + let kind: DefinitionKind; + + if (eat(enumKeyword)) kind = 'ENUM'; + else if (eat(structKeyword)) kind = 'STRUCT'; + else if (eat(messageKeyword)) kind = 'MESSAGE'; + else unexpectedToken(); + + // All definitions start off the same + let name = current(); + expect(identifier, 'identifier'); + expect(leftBrace, '"{"'); + + // Parse fields + while (!eat(rightBrace)) { + let type: string | null = null; + let isArray = false; + let isDeprecated = false; + + // Enums don't have types + if (kind !== 'ENUM') { + type = current().text; + expect(identifier, 'identifier'); + isArray = eat(arrayToken); + } + + let field = current(); + expect(identifier, 'identifier'); + + // Structs don't have explicit values + let value: Token | null = null; + if (kind !== 'STRUCT') { + expect(equals, '"="'); + value = current(); + expect(integer, 'integer'); + + if ((+value.text | 0) + '' !== value.text) { + error('Invalid integer ' + quote(value.text), value.line, value.column); + } + } + + let deprecated = current(); + if (eat(deprecatedToken)) { + if (kind !== 'MESSAGE') { + error('Cannot deprecate this field', deprecated.line, deprecated.column); + } + + isDeprecated = true; + } + + expect(semicolon, '";"'); + + fields.push({ + name: field.text, + line: field.line, + column: field.column, + type: type, + isArray: isArray, + isDeprecated: isDeprecated, + value: value !== null ? +value.text | 0 : fields.length + 1, + }); + } + + definitions.push({ + name: name.text, + line: name.line, + column: name.column, + kind: kind, + fields: fields, + }); + } + + return { + package: packageText, + definitions: definitions, + }; +} + +function verify(root: Schema): void { + let definedTypes = nativeTypes.slice(); + let definitions: { [name: string]: Definition } = {}; + + // Define definitions + for (let i = 0; i < root.definitions.length; i++) { + let definition = root.definitions[i]; + if (definedTypes.indexOf(definition.name) !== -1) { + error('The type ' + quote(definition.name) + ' is defined twice', definition.line, definition.column); + } + if (reservedNames.indexOf(definition.name) !== -1) { + error('The type name ' + quote(definition.name) + ' is reserved', definition.line, definition.column); + } + definedTypes.push(definition.name); + definitions[definition.name] = definition; + } + + // Check fields + for (let i = 0; i < root.definitions.length; i++) { + let definition = root.definitions[i]; + let fields = definition.fields; + + if (definition.kind === 'ENUM' || fields.length === 0) { + continue; + } + + // Check types + for (let j = 0; j < fields.length; j++) { + let field = fields[j]; + if (definedTypes.indexOf(field.type!) === -1) { + error('The type ' + quote(field.type!) + ' is not defined for field ' + quote(field.name), field.line, field.column); + } + } + + // Check values + let values: number[] = []; + for (let j = 0; j < fields.length; j++) { + let field = fields[j]; + if (values.indexOf(field.value) !== -1) { + error('The id for field ' + quote(field.name) + ' is used twice', field.line, field.column); + } + if (field.value <= 0) { + error('The id for field ' + quote(field.name) + ' must be positive', field.line, field.column); + } + // Figma schema uses sparse field IDs (up to 435), so skip sequential check + values.push(field.value); + } + } + + // Check that structs don't contain themselves + let state: { [name: string]: number } = {}; + let check = (name: string): boolean => { + let definition = definitions[name]; + if (definition && definition.kind === 'STRUCT') { + if (state[name] === 1) { + error('Recursive nesting of ' + quote(name) + ' is not allowed', definition.line, definition.column); + } + if (state[name] !== 2 && definition) { + state[name] = 1; + let fields = definition.fields; + for (let i = 0; i < fields.length; i++) { + let field = fields[i]; + if (!field.isArray) { + check(field.type!); + } + } + state[name] = 2; + } + } + return true; + }; + for (let i = 0; i < root.definitions.length; i++) { + check(root.definitions[i].name); + } +} + +export function parseSchema(text: string): Schema { + let schema = parse(tokenize(text)); + verify(schema); + return schema; +} diff --git a/src/kiwi/kiwi-schema/schema.ts b/src/kiwi/kiwi-schema/schema.ts new file mode 100644 index 000000000..87c65a311 --- /dev/null +++ b/src/kiwi/kiwi-schema/schema.ts @@ -0,0 +1,24 @@ +export interface Schema { + package: string | null; + definitions: Definition[]; +} + +export type DefinitionKind = 'ENUM' | 'STRUCT' | 'MESSAGE'; + +export interface Definition { + name: string; + line: number; + column: number; + kind: DefinitionKind; + fields: Field[]; +} + +export interface Field { + name: string; + line: number; + column: number; + type: string | null; + isArray: boolean; + isDeprecated: boolean; + value: number; +} diff --git a/src/kiwi/kiwi-schema/util.ts b/src/kiwi/kiwi-schema/util.ts new file mode 100644 index 000000000..3f40e4ab9 --- /dev/null +++ b/src/kiwi/kiwi-schema/util.ts @@ -0,0 +1,10 @@ +export function quote(text: string): string { + return JSON.stringify(text); +} + +export function error(text: string, line: number, column: number): never { + var error = new Error(text); + (error as any).line = line; + (error as any).column = column; + throw error; +} diff --git a/src/kiwi/poc-test.ts b/src/kiwi/poc-test.ts deleted file mode 100644 index 3be08d222..000000000 --- a/src/kiwi/poc-test.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * PoC 3: Kiwi codec round-trip test - * - * Validates: - * 1. Schema compiles (194 definitions, 2178 lines) - * 2. Encode a NODE_CHANGES message with shapes - * 3. Decode it back — verify data integrity - * 4. Zstd compress/decompress round-trip - */ - -import { - initCodec, - encodeMessage, - decodeMessage, - createNodeChange, - createNodeChangesMessage -} from './codec.ts' - -import type { NodeChange } from './codec.ts' - -async function runTest() { - console.log('=== PoC 3: Kiwi Codec Round-Trip ===\n') - - // 1. Init codec (compiles schema) - const t0 = performance.now() - await initCodec() - console.log(`✅ Schema compiled in ${(performance.now() - t0).toFixed(1)}ms`) - - // 2. Create test node changes - const sessionID = 99999 - const changes: NodeChange[] = [ - createNodeChange({ - sessionID, - localID: 1, - parentSessionID: 0, - parentLocalID: 0, - type: 'FRAME', - name: 'Test Frame', - x: 0, - y: 0, - width: 800, - height: 600, - fill: '#FFFFFF' - }), - createNodeChange({ - sessionID, - localID: 2, - parentSessionID: sessionID, - parentLocalID: 1, - type: 'RECTANGLE', - name: 'Blue Card', - x: 50, - y: 50, - width: 200, - height: 150, - fill: '#3B82F6', - cornerRadius: 12 - }), - createNodeChange({ - sessionID, - localID: 3, - parentSessionID: sessionID, - parentLocalID: 1, - type: 'ELLIPSE', - name: 'Green Circle', - x: 300, - y: 80, - width: 100, - height: 100, - fill: '#22C55E' - }), - createNodeChange({ - sessionID, - localID: 4, - parentSessionID: sessionID, - parentLocalID: 1, - type: 'TEXT', - name: 'Hello World', - x: 50, - y: 250, - width: 200, - height: 40, - fill: '#000000' - }) - ] - - console.log(`✅ Created ${changes.length} node changes`) - - // 3. Encode - const message = createNodeChangesMessage(sessionID, 1, changes) - const t1 = performance.now() - const encoded = encodeMessage(message) - const encodeTime = performance.now() - t1 - console.log(`✅ Encoded: ${encoded.byteLength} bytes (${encodeTime.toFixed(2)}ms)`) - console.log( - ` Zstd compressed: starts with 0x${encoded[0]?.toString(16)}${encoded[1]?.toString(16)}${encoded[2]?.toString(16)}${encoded[3]?.toString(16)}` - ) - - // 4. Decode - const t2 = performance.now() - const decoded = decodeMessage(encoded) - const decodeTime = performance.now() - t2 - console.log(`✅ Decoded in ${decodeTime.toFixed(2)}ms`) - - // 5. Verify round-trip - const nc = decoded.nodeChanges - if (!nc || nc.length !== changes.length) { - console.error(`❌ Node count mismatch: expected ${changes.length}, got ${nc?.length}`) - process.exit(1) - } - - for (let i = 0; i < nc.length; i++) { - const original = changes[i]! - const roundtripped = nc[i]! - - // Verify GUID - if ( - roundtripped.guid?.sessionID !== original.guid.sessionID || - roundtripped.guid?.localID !== original.guid.localID - ) { - console.error(`❌ Node ${i} GUID mismatch`) - process.exit(1) - } - - // Verify name - if (roundtripped.name !== original.name) { - console.error(`❌ Node ${i} name mismatch: "${roundtripped.name}" !== "${original.name}"`) - process.exit(1) - } - - // Verify type - if (roundtripped.type !== original.type) { - console.error(`❌ Node ${i} type mismatch: "${roundtripped.type}" !== "${original.type}"`) - process.exit(1) - } - - // Verify size - if (roundtripped.size?.x !== original.size?.x || roundtripped.size?.y !== original.size?.y) { - console.error(`❌ Node ${i} size mismatch`) - process.exit(1) - } - - // Verify transform (position) - if ( - roundtripped.transform?.m02 !== original.transform?.m02 || - roundtripped.transform?.m12 !== original.transform?.m12 - ) { - console.error(`❌ Node ${i} position mismatch`) - process.exit(1) - } - - // Verify fill - if (original.fillPaints && original.fillPaints.length > 0) { - const origFill = original.fillPaints[0]! - const rtFill = roundtripped.fillPaints?.[0] - if (!rtFill) { - console.error(`❌ Node ${i} missing fill`) - process.exit(1) - } - if (rtFill.type !== origFill.type) { - console.error(`❌ Node ${i} fill type mismatch`) - process.exit(1) - } - } - - console.log( - ` ✓ Node ${i}: ${roundtripped.name} (${roundtripped.type}) @ ${roundtripped.transform?.m02},${roundtripped.transform?.m12} [${roundtripped.size?.x}×${roundtripped.size?.y}]` - ) - } - - console.log(`\n✅ PoC 3 PASSED: ${changes.length} nodes round-tripped through Kiwi+Zstd`) - console.log(` Schema: 194 definitions`) - console.log(` Encode: ${encodeTime.toFixed(2)}ms → ${encoded.byteLength} bytes`) - console.log(` Decode: ${decodeTime.toFixed(2)}ms`) -} - -runTest().catch((e) => { - console.error('❌ PoC 3 FAILED:', e) - process.exit(1) -}) diff --git a/src/kiwi/schema.ts b/src/kiwi/schema.ts index 20120c876..c9d56990d 100644 --- a/src/kiwi/schema.ts +++ b/src/kiwi/schema.ts @@ -1,4 +1,4 @@ -import { parseSchema } from 'kiwi-schema' +import { parseSchema } from './kiwi-schema' const schemaText = ` package Fig; diff --git a/src/stores/editor.ts b/src/stores/editor.ts index 2a1e52156..3d38c1dec 100644 --- a/src/stores/editor.ts +++ b/src/stores/editor.ts @@ -1,5 +1,6 @@ import { reactive, shallowRef, computed } from 'vue' +import { DEFAULT_SHAPE_FILL, DEFAULT_FRAME_FILL } from '../constants' import { parseFigmaClipboard, importClipboardNodes, @@ -8,7 +9,7 @@ import { buildOpenPencilClipboardHTML, prefetchFigmaSchema } from '../engine/clipboard' -import { readFigFile } from '../engine/fig-file' +import { readFigFile } from '../kiwi/fig-file' import { computeLayout, computeAllLayouts } from '../engine/layout' import { SceneGraph } from '../engine/scene-graph' import { UndoManager } from '../engine/undo' @@ -53,22 +54,14 @@ export const TOOL_SHORTCUTS: Record = { h: 'HAND' } +const BLACK_FILL: Fill = { type: 'SOLID', color: { r: 0, g: 0, b: 0, a: 1 }, opacity: 1, visible: true } + const DEFAULT_FILLS: Record = { - FRAME: { type: 'SOLID', color: { r: 1, g: 1, b: 1, a: 1 }, opacity: 1, visible: true }, - RECTANGLE: { - type: 'SOLID', - color: { r: 0.83, g: 0.83, b: 0.83, a: 1 }, - opacity: 1, - visible: true - }, - ELLIPSE: { - type: 'SOLID', - color: { r: 0.83, g: 0.83, b: 0.83, a: 1 }, - opacity: 1, - visible: true - }, - LINE: { type: 'SOLID', color: { r: 0, g: 0, b: 0, a: 1 }, opacity: 1, visible: true }, - TEXT: { type: 'SOLID', color: { r: 0, g: 0, b: 0, a: 1 }, opacity: 1, visible: true } + FRAME: DEFAULT_FRAME_FILL, + RECTANGLE: DEFAULT_SHAPE_FILL, + ELLIPSE: DEFAULT_SHAPE_FILL, + LINE: BLACK_FILL, + TEXT: BLACK_FILL } export function createEditorStore() { @@ -306,7 +299,7 @@ export function createEditorStore() { vectorNetwork: normalizedNetwork, name: 'Vector', fills: closed - ? [{ type: 'SOLID' as const, color: { r: 0.83, g: 0.83, b: 0.83, a: 1 }, opacity: 1, visible: true }] + ? [{ ...DEFAULT_SHAPE_FILL }] : [], strokes: closed ? [] diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..62a218f81 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,11 @@ +export interface GUID { + sessionID: number + localID: number +} + +export interface Color { + r: number + g: number + b: number + a: number +} diff --git a/tests/e2e/create-shapes.spec.ts-snapshots/draw-and-delete-openpencil-darwin.png b/tests/e2e/create-shapes.spec.ts-snapshots/draw-and-delete-openpencil-darwin.png index c9081c3fd6288815126e63a4e803cf52218087c8..04fc4e7ae05ba450c1f6ea905ca20eee34fb3dd1 100644 GIT binary patch delta 8360 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 delta 8388 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 diff --git a/tests/e2e/create-shapes.spec.ts-snapshots/draw-and-undo-openpencil-darwin.png b/tests/e2e/create-shapes.spec.ts-snapshots/draw-and-undo-openpencil-darwin.png index a39e5f55d78d539aad08d327388097b2ba68c0ca..264ed3dd02d59445f9088159e7ec5f5964b95cc4 100644 GIT binary patch delta 8366 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 delta 8378 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 diff --git a/tests/e2e/create-shapes.spec.ts-snapshots/draw-ellipse-openpencil-darwin.png b/tests/e2e/create-shapes.spec.ts-snapshots/draw-ellipse-openpencil-darwin.png index 3199413be76e8b64733356325fd4e3a15b62ca76..4241b3aa75bdf91c622629255414c920f96d3dd5 100644 GIT binary patch literal 18152 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 C;Lmda literal 25428 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

L>DPY4Ik%J>M>Z9v#Z22vx}%_{+7}8{Fy3U zn#io+>BUH7v({=hDkIFh`JB_7oDWfz3tG!6?xG78#%p<%ja##IsUiMU>FZ27UMyef ztj5Mu4qnZbE}hmUloDky7-~yxi^bq7@{8|m$AztY45?1{5LQWuZNBiyvV@2jdr-xiKj98(rKOn>yv9KU8q$3$Q0zRZy{E6mw zFCKX|dgCIFYksz?phNxrnMrNj75Q*)2lI=MIHM-9E-4{64di4ODRwVTE>_dqp+sQr zA4#^)!0luEnBt{(CMGx`WL2wn(c}4G>pjN+gCF3j4oV088e-lJ(MR+v3>GMfe*9$@ zc{*rIk>tw2v5+||8Ic~k+^Q#rRPe4rp)cLJqo6xXh<7Kg8pQ&V)<{7iC2swB)tOWw6mQ6xdw;xuP@ zPy3$*0g4tG?v`=$;X7Zo)GG-5P!<02^Q`d#`(A>2wsuz9$PNUTk_QK2>wCr|gYGRT zy=wlQJ!19&GAwBKu1Z#~Qq2-dJF^gse^8YHS=pDUtD5&hr^{zC7viGUD(=>JT(FvY zAzv$a;*5QID{Oway}f=`r-Sy|oFu%dH72a7%0D}15pB;BuL~Xpi@snvobeFW3Jdk5 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 JIBka~{|gi;Npt`J literal 21657 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 diff --git a/tests/e2e/create-shapes.spec.ts-snapshots/draw-rectangle-then-move-it-openpencil-darwin.png b/tests/e2e/create-shapes.spec.ts-snapshots/draw-rectangle-then-move-it-openpencil-darwin.png index cc5f821439b9bb1f1251506441b445bbf31cf3b5..a2f030387621936bcbc569ca5d4e8007d853f6d8 100644 GIT binary patch literal 18229 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 literal 21115 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