From b498056c017ee912ecb5ac09dba30d79cf4f63cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Thu, 23 Oct 2025 17:52:53 +0200 Subject: [PATCH] :wrench: Fix text-related playgrounds (wasm) --- frontend/resources/wasm-playground/js/lib.js | 73 ++++++++++---------- frontend/text-editor/src/playground.js | 42 ++++++----- frontend/text-editor/src/playground/text.js | 18 ++--- 3 files changed, 68 insertions(+), 65 deletions(-) diff --git a/frontend/resources/wasm-playground/js/lib.js b/frontend/resources/wasm-playground/js/lib.js index b4bd846a6c..4120a59815 100644 --- a/frontend/resources/wasm-playground/js/lib.js +++ b/frontend/resources/wasm-playground/js/lib.js @@ -32,7 +32,7 @@ export function assignCanvas(canvas) { export function hexToU32ARGB(hex, opacity = 1) { const rgb = parseInt(hex.slice(1), 16); - const a = Math.floor(opacity * 0xFF); + const a = Math.floor(opacity * 0xff); const argb = (a << 24) | rgb; return argb >>> 0; } @@ -42,9 +42,9 @@ export function getRandomInt(min, max) { } export function getRandomColor() { - const r = getRandomInt(0, 256).toString(16).padStart(2, '0'); - const g = getRandomInt(0, 256).toString(16).padStart(2, '0'); - const b = getRandomInt(0, 256).toString(16).padStart(2, '0'); + const r = getRandomInt(0, 256).toString(16).padStart(2, "0"); + const g = getRandomInt(0, 256).toString(16).padStart(2, "0"); + const b = getRandomInt(0, 256).toString(16).padStart(2, "0"); return `#${r}${g}${b}`; } @@ -103,12 +103,12 @@ export function addShapeSolidStrokeFill(argb) { function serializePathAttrs(svgAttrs) { return Object.entries(svgAttrs).reduce((acc, [key, value]) => { - return acc + key + '\0' + value + '\0'; - }, ''); + return acc + key + "\0" + value + "\0"; + }, ""); } export function draw_star(x, y, width, height) { - const len = 11; // 1 MOVE + 9 LINE + 1 CLOSE + const len = 11; // 1 MOVE + 9 LINE + 1 CLOSE const ptr = allocBytes(len * 28); const heap = getHeapU32(); const dv = new DataView(heap.buffer); @@ -120,7 +120,7 @@ export function draw_star(x, y, width, height) { const star = []; for (let i = 0; i < 10; i++) { - const angle = Math.PI / 5 * i - Math.PI / 2; + const angle = (Math.PI / 5) * i - Math.PI / 2; const r = i % 2 === 0 ? outerRadius : innerRadius; const px = cx + r * Math.cos(angle); const py = cy + r * Math.sin(angle); @@ -149,7 +149,7 @@ export function draw_star(x, y, width, height) { Module._set_shape_path_content(); const str = serializePathAttrs({ - "fill": "none", + fill: "none", "stroke-linecap": "round", "stroke-linejoin": "round", }); @@ -158,7 +158,6 @@ export function draw_star(x, y, width, height) { Module.stringToUTF8(str, offset, size); Module._set_shape_path_attrs(3); } - export function setShapeChildren(shapeIds) { const offset = allocBytes(shapeIds.length * 16); @@ -227,18 +226,24 @@ export function setupInteraction(canvas) { } }); - canvas.addEventListener("mouseup", () => { isPanning = false; }); - canvas.addEventListener("mouseout", () => { isPanning = false; }); + canvas.addEventListener("mouseup", () => { + isPanning = false; + }); + canvas.addEventListener("mouseout", () => { + isPanning = false; + }); } export function addTextShape(x, y, fontSize, text) { const numLeaves = 1; // Single text leaf for simplicity const paragraphAttrSize = 48; - const leafAttrSize = 56; - const fillSize = 160; + // const leafAttrSize = 56; + const singleFillSize = 160; const textBuffer = new TextEncoder().encode(text); const textSize = textBuffer.byteLength; + const leafSize = 1340; // leaf attrs + 8 fills + // Calculate fills const fills = [ { @@ -247,12 +252,7 @@ export function addTextShape(x, y, fontSize, text) { opacity: getRandomFloat(0.5, 1.0), }, ]; - const totalFills = fills.length; - const totalFillsSize = totalFills * fillSize; - - // Calculate metadata and total buffer size - const metadataSize = paragraphAttrSize + leafAttrSize + totalFillsSize; - const totalSize = metadataSize + textSize; + const totalSize = paragraphAttrSize + leafSize + textSize; // Allocate buffer const bufferPtr = allocBytes(totalSize); @@ -282,34 +282,35 @@ export function addTextShape(x, y, fontSize, text) { const leafOffset = paragraphAttrSize; dview.setUint8(leafOffset, 0); // font-style: normal dview.setFloat32(leafOffset + 4, fontSize, true); // font-size - dview.setUint32(leafOffset + 8, 400, true); // font-weight: normal - dview.setUint32(leafOffset + 12, 0, true); // font-id (UUID part 1) - dview.setUint32(leafOffset + 16, 0, true); // font-id (UUID part 2) - dview.setUint32(leafOffset + 20, 0, true); // font-id (UUID part 3) - dview.setInt32(leafOffset + 24, 0, true); // font-id (UUID part 4) - dview.setInt32(leafOffset + 28, 0, true); // font-family hash - dview.setUint32(leafOffset + 32, 0, true); // font-variant-id (UUID part 1) - dview.setUint32(leafOffset + 36, 0, true); // font-variant-id (UUID part 2) - dview.setUint32(leafOffset + 40, 0, true); // font-variant-id (UUID part 3) - dview.setInt32(leafOffset + 44, 0, true); // font-variant-id (UUID part 4) - dview.setInt32(leafOffset + 48, textSize, true); // text-length - dview.setInt32(leafOffset + 52, totalFills, true); // total fills count + dview.setFloat32(leafOffset + 8, 0, true); // letter-spacing + dview.setUint32(leafOffset + 12, 400, true); // font-weight: normal + dview.setUint32(leafOffset + 16, 0, true); // font-id (UUID part 1) + dview.setUint32(leafOffset + 20, 0, true); // font-id (UUID part 2) + dview.setUint32(leafOffset + 24, 0, true); // font-id (UUID part 3) + dview.setInt32(leafOffset + 28, 0, true); // font-id (UUID part 4) + dview.setInt32(leafOffset + 32, 0, true); // font-family hash + dview.setUint32(leafOffset + 36, 0, true); // font-variant-id (UUID part 1) + dview.setUint32(leafOffset + 40, 0, true); // font-variant-id (UUID part 2) + dview.setUint32(leafOffset + 44, 0, true); // font-variant-id (UUID part 3) + dview.setInt32(leafOffset + 48, 0, true); // font-variant-id (UUID part 4) + dview.setInt32(leafOffset + 52, textSize, true); // text-length + dview.setInt32(leafOffset + 56, fills.length, true); // total fills count // Serialize fills - let fillOffset = leafOffset + leafAttrSize; + let fillOffset = leafOffset + 60; fills.forEach((fill) => { if (fill.type === "solid") { const argb = hexToU32ARGB(fill.color, fill.opacity); dview.setUint8(fillOffset, 0x00, true); // Fill type: solid dview.setUint32(fillOffset + 4, argb, true); - fillOffset += fillSize; // Move to the next fill + fillOffset += singleFillSize; // Move to the next fill } }); // Add text content - const textOffset = metadataSize; + const textOffset = leafSize; heap.set(textBuffer, textOffset); // Call the WebAssembly function Module._set_shape_text_content(); -} \ No newline at end of file +} diff --git a/frontend/text-editor/src/playground.js b/frontend/text-editor/src/playground.js index 8db4a3ac09..b474c412f6 100644 --- a/frontend/text-editor/src/playground.js +++ b/frontend/text-editor/src/playground.js @@ -68,7 +68,7 @@ class TextEditorPlayground { "font-style": "normal", "line-height": "1.2", "letter-spacing": "0", - "direction": "ltr", + direction: "ltr", "text-align": "left", "text-transform": "none", "text-decoration": "none", @@ -111,6 +111,7 @@ class TextEditorPlayground { #onWheel = (e) => { e.preventDefault(); const textShape = this.#shapes.get("text"); + if (!textShape) { console.warn("Text shape not found"); return; @@ -154,6 +155,13 @@ class TextEditorPlayground { #onClick = (e) => { console.log("click", e.type, e); + const textShape = this.#shapes.get("text"); + if (!textShape) { + console.warn("Text shape not found"); + return; + } + + this.#module.call("use_shape", ...textShape.id); const caretPosition = this.#module.call( "get_caret_position_at", e.offsetX, @@ -317,7 +325,7 @@ class TextEditorPlayground { #onShapePositionChange = (_e) => { const textShape = this.#shapes.get("text"); if (!textShape) { - console.warn("Text shape not found") + console.warn("Text shape not found"); return; } textShape.selrect.left = this.#ui.shapePositionXElement.valueAsNumber; @@ -329,7 +337,10 @@ class TextEditorPlayground { textShape.selrect.right, textShape.selrect.bottom, ); - this.#textEditor.updatePositionWithViewportAndShape(this.#viewport, textShape); + this.#textEditor.updatePositionWithViewportAndShape( + this.#viewport, + textShape, + ); this.render(); }; @@ -346,7 +357,7 @@ class TextEditorPlayground { textShape, ); this.render(); - } + }; #setupUI() { const fontFamiliesFragment = document.createDocumentFragment(); @@ -360,15 +371,15 @@ class TextEditorPlayground { this.#ui.shapePositionXElement.addEventListener( "change", - this.#onShapePositionChange + this.#onShapePositionChange, ); this.#ui.shapePositionYElement.addEventListener( "change", - this.#onShapePositionChange + this.#onShapePositionChange, ); this.#ui.shapeRotationElement.addEventListener( "change", - this.#onShapeRotationChange + this.#onShapeRotationChange, ); this.#ui.directionLTRElement.addEventListener( @@ -405,10 +416,7 @@ class TextEditorPlayground { "change", this.#onFontWeightChange, ); - this.#ui.fontSizeElement.addEventListener( - "change", - this.#onFontSizeChange - ); + this.#ui.fontSizeElement.addEventListener("change", this.#onFontSizeChange); this.#ui.fontStyleElement.addEventListener( "change", this.#onFontStyleChange, @@ -454,7 +462,7 @@ class TextEditorPlayground { // Number of text leaves in the paragraph. view.setUint32(0, paragraph.leaves.length, true); - console.log('lineHeight', paragraph.lineHeight); + console.log("lineHeight", paragraph.lineHeight); // Serialize paragraph attributes view.setUint8(4, paragraph.textAlign, true); // text-align: left @@ -495,8 +503,7 @@ class TextEditorPlayground { view.setUint32(offset + 56, leaf.fills.length, true); // total fills count leaf.fills.forEach((fill, index) => { - const fillOffset = - offset + TextSpan.BYTE_LENGTH + index * Fill.BYTE_LENGTH; + const fillOffset = offset + 60 + index * Fill.BYTE_LENGTH; if (fill.type === Fill.Type.SOLID) { view.setUint8(fillOffset + 0, fill.type, true); view.setUint32(fillOffset + 4, fill.solid.color.argb32, true); @@ -530,10 +537,11 @@ class TextEditorPlayground { id: UUID.ZERO, childrenIds: [textShape.id], }); - this.#shapes.set("text", textShape); this.#shapes.set("root", rootShape); - this.#setShape(textShape); this.#setShape(rootShape); + + this.#shapes.set("text", textShape); + this.#setShape(textShape); } #setupTextEditor() { @@ -572,7 +580,7 @@ const module = await initWasmModule(); const canvas = document.getElementById("canvas"); const textEditorPlayground = new TextEditorPlayground(module, canvas, { - shouldUpdatePositionOnScroll: true + shouldUpdatePositionOnScroll: true, }); await textEditorPlayground.setup(); textEditorPlayground.render(); diff --git a/frontend/text-editor/src/playground/text.js b/frontend/text-editor/src/playground/text.js index a5cf56d964..3ec7aa2168 100644 --- a/frontend/text-editor/src/playground/text.js +++ b/frontend/text-editor/src/playground/text.js @@ -38,27 +38,21 @@ export const TextTransform = { }; export class TextSpan { - static BYTE_LENGTH = 60; + static BYTE_LENGTH = 1340; static fromDOM(spanElement, fontManager) { const elementStyle = spanElement.style; //window.getComputedStyle(leafElement); - const fontSize = parseFloat( - elementStyle.getPropertyValue("font-size"), - ); + const fontSize = parseFloat(elementStyle.getPropertyValue("font-size")); const fontStyle = FontStyle.fromStyle(elementStyle.getPropertyValue("font-style")) ?? FontStyle.NORMAL; - const fontWeight = parseInt( - elementStyle.getPropertyValue("font-weight"), - ); + const fontWeight = parseInt(elementStyle.getPropertyValue("font-weight")); const letterSpacing = parseFloat( elementStyle.getPropertyValue("letter-spacing"), ); const fontFamily = elementStyle.getPropertyValue("font-family"); console.log("fontFamily", fontFamily); - const fontStyles = fontManager.fonts.get( - fontFamily, - ); + const fontStyles = fontManager.fonts.get(fontFamily); const textDecoration = TextDecoration.fromStyle( elementStyle.getPropertyValue("text-decoration"), ); @@ -75,7 +69,7 @@ export class TextSpan { currentFontStyle.styleAsNumber === fontStyle, ); if (!font) { - throw new Error(`Invalid font "${fontFamily}"`) + throw new Error(`Invalid font "${fontFamily}"`); } return new TextSpan({ fontId: font.id, // leafElement.style.getPropertyValue("--font-id"), @@ -134,7 +128,7 @@ export class TextSpan { } get leafByteLength() { - return this.fills.length * Fill.BYTE_LENGTH + TextSpan.BYTE_LENGTH; + return TextLeaf.BYTE_LENGTH; } }