🎉 Add missing styles on text leaves

This commit is contained in:
Aitor Moreno
2025-08-21 12:22:39 +02:00
committed by Elena Torro
parent 15eee0d8d8
commit 596193d34d
6 changed files with 358 additions and 332 deletions

View File

@@ -16,7 +16,7 @@
[app.render-wasm.wasm :as wasm])) [app.render-wasm.wasm :as wasm]))
(def ^:const PARAGRAPH-ATTR-U8-SIZE 44) (def ^:const PARAGRAPH-ATTR-U8-SIZE 44)
(def ^:const LEAF-ATTR-U8-SIZE 56) (def ^:const LEAF-ATTR-U8-SIZE 60)
(defn- encode-text (defn- encode-text
"Into an UTF8 buffer. Returns an ArrayBuffer instance" "Into an UTF8 buffer. Returns an ArrayBuffer instance"
@@ -79,6 +79,7 @@
(reduce (fn [offset leaf] (reduce (fn [offset leaf]
(let [font-style (sr/translate-font-style (get leaf :font-style)) (let [font-style (sr/translate-font-style (get leaf :font-style))
font-size (get leaf :font-size) font-size (get leaf :font-size)
letter-spacing (get leaf :letter-spacing)
font-weight (get leaf :font-weight) font-weight (get leaf :font-weight)
font-id (f/normalize-font-id (get leaf :font-id)) font-id (f/normalize-font-id (get leaf :font-id))
font-family (hash (get leaf :font-family)) font-family (hash (get leaf :font-family))
@@ -104,15 +105,21 @@
text-transform text-transform
(or (sr/translate-text-transform (:text-transform leaf)) (or (sr/translate-text-transform (:text-transform leaf))
(sr/translate-text-transform (:text-transform paragraph)) (sr/translate-text-transform (:text-transform paragraph))
(sr/translate-text-transform "none"))] (sr/translate-text-transform "none"))
text-direction
(or (sr/translate-text-direction (:text-direction leaf))
(sr/translate-text-direction (:text-direction paragraph))
(sr/translate-text-direction "ltr"))]
(-> offset (-> offset
(mem/write-u8 dview font-style) (mem/write-u8 dview font-style)
(mem/write-u8 dview text-decoration) (mem/write-u8 dview text-decoration)
(mem/write-u8 dview text-transform) (mem/write-u8 dview text-transform)
(+ 1) ;;padding (mem/write-u8 dview text-direction)
(mem/write-f32 dview font-size) (mem/write-f32 dview font-size)
(mem/write-f32 dview letter-spacing)
(mem/write-u32 dview font-weight) (mem/write-u32 dview font-weight)
(mem/write-uuid dview font-id) (mem/write-uuid dview font-id)

View File

@@ -310,7 +310,8 @@
[text-direction] [text-direction]
(case text-direction (case text-direction
"ltr" 0 "ltr" 0
"rtl" 1)) "rtl" 1
0))
(defn translate-font-style (defn translate-font-style
[font-style] [font-style]

View File

@@ -3,12 +3,20 @@
color: #eee; color: #eee;
} }
html, body {
margin: 0;
padding: 0;
}
canvas {
width: 100cqw;
}
.text-editor-container { .text-editor-container {
background-color: white; background-color: white;
} }
#output { .playground {
font-family: monospace; display: grid;
padding: 1rem; max-width: 1280px;
border: 1px solid #333;
} }

View File

@@ -8,220 +8,216 @@
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Playwrite+ES:wght@100..400&family=Playwrite+NZ:wght@100..400&family=Playwrite+US+Trad:wght@100..400&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Playwrite+ES:wght@100..400&family=Playwrite+NZ:wght@100..400&family=Playwrite+US+Trad:wght@100..400&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Penpot - Text Editor Playground</title> <title>Penpot - Text Editor Playground</title>
<style>
#output {
white-space: pre-wrap;
}
</style>
</head> </head>
<body> <body>
<form> <div class="playground">
<fieldset> <form>
<legend>Styles</legend> <fieldset>
<!-- Font --> <legend>Styles</legend>
<div class="form-group"> <!-- Font -->
<label for="font-family">Font family</label> <div class="form-group">
<select id="font-family"> <label for="font-family">Font family</label>
<option value="Open+Sans">Open Sans</option> <select id="font-family">
<option value="sourcesanspro">Source Sans Pro</option> <option value="Open+Sans">Open Sans</option>
<option value="whatever">Whatever</option> <option value="sourcesanspro">Source Sans Pro</option>
</select> <option value="whatever">Whatever</option>
</div> </select>
<div class="form-group"> </div>
<label for="font-size">Font size</label> <div class="form-group">
<input id="font-size" type="number" value="14" /> <label for="font-size">Font size</label>
</div> <input id="font-size" type="number" value="14" />
<div class="form-group"> </div>
<label for="font-weight">Font weight</label> <div class="form-group">
<select id="font-weight"> <label for="font-weight">Font weight</label>
<option value="100">100</option> <select id="font-weight">
<option value="200">200</option> <option value="100">100</option>
<option value="300">300</option> <option value="200">200</option>
<option value="400">400 (normal)</option> <option value="300">300</option>
<option value="500">500</option> <option value="400">400 (normal)</option>
<option value="600">600</option> <option value="500">500</option>
<option value="700">700 (bold)</option> <option value="600">600</option>
<option value="800">800</option> <option value="700">700 (bold)</option>
<option value="900">900</option> <option value="800">800</option>
</select> <option value="900">900</option>
</div> </select>
<div class="form-group"> </div>
<label for="font-style">Font style</label> <div class="form-group">
<select id="font-style"> <label for="font-style">Font style</label>
<option value="normal">normal</option> <select id="font-style">
<option value="italic">italic</option> <option value="normal">normal</option>
<option value="oblique">oblique</option> <option value="italic">italic</option>
</select> <option value="oblique">oblique</option>
</div> </select>
<!-- Text attributes --> </div>
<div class="form-group"> <!-- Text attributes -->
<label for="line-height">Line height</label> <div class="form-group">
<input id="line-height" type="number" value="1.0" /> <label for="line-height">Line height</label>
</div> <input id="line-height" type="number" value="1.0" />
<div class="form-group"> </div>
<label for="letter-spacing">Letter spacing</label> <div class="form-group">
<input id="letter-spacing" type="number" value="0.0" /> <label for="letter-spacing">Letter spacing</label>
</div> <input id="letter-spacing" type="number" value="0.0" />
<div class="form-group"> </div>
<label for="direction-ltr">LTR</label> <div class="form-group">
<input id="direction-ltr" type="radio" name="direction" value="ltr" checked /> <label for="direction-ltr">LTR</label>
</div> <input id="direction-ltr" type="radio" name="direction" value="ltr" checked />
<div class="form-group"> </div>
<label for="direction-rtl">RTL</label> <div class="form-group">
<input id="direction-rtl" type="radio" name="direction" value="rtl" /> <label for="direction-rtl">RTL</label>
</div> <input id="direction-rtl" type="radio" name="direction" value="rtl" />
<!-- Text Align --> </div>
<div class="form-group"> <!-- Text Align -->
<label for="text-align-left">Align left</label> <div class="form-group">
<input id="text-align-left" type="radio" name="text-align" value="left" checked /> <label for="text-align-left">Align left</label>
</div> <input id="text-align-left" type="radio" name="text-align" value="left" checked />
<div class="form-group"> </div>
<label for="text-align-center">Align center</label> <div class="form-group">
<input id="text-align-center" type="radio" name="text-align" value="center" /> <label for="text-align-center">Align center</label>
</div> <input id="text-align-center" type="radio" name="text-align" value="center" />
<div class="form-group"> </div>
<label for="text-align-right">Align right</label> <div class="form-group">
<input id="text-align-right" type="radio" name="text-align" value="right" /> <label for="text-align-right">Align right</label>
</div> <input id="text-align-right" type="radio" name="text-align" value="right" />
<div class="form-group"> </div>
<label for="text-align-justify">Align justify</label> <div class="form-group">
<input id="text-align-justify" type="radio" name="text-align" value="justify" /> <label for="text-align-justify">Align justify</label>
</div> <input id="text-align-justify" type="radio" name="text-align" value="justify" />
<!-- Text Transform --> </div>
<div class="form-group"> <!-- Text Transform -->
<label for="text-transform-none">None</label> <div class="form-group">
<input id="text-transform-none" type="radio" name="text-transform" value="none" checked /> <label for="text-transform-none">None</label>
</div> <input id="text-transform-none" type="radio" name="text-transform" value="none" checked />
<div class="form-group"> </div>
<label for="text-transform-uppercase">Uppercase</label> <div class="form-group">
<input id="text-transform-uppercase" type="radio" name="text-transform" value="uppercase" checked /> <label for="text-transform-uppercase">Uppercase</label>
</div> <input id="text-transform-uppercase" type="radio" name="text-transform" value="uppercase" checked />
<div class="form-group"> </div>
<label for="text-transform-capitalize">Capitalize</label> <div class="form-group">
<input id="text-transform-capitalize" type="radio" name="text-transform" value="capitalize" /> <label for="text-transform-capitalize">Capitalize</label>
</div> <input id="text-transform-capitalize" type="radio" name="text-transform" value="capitalize" />
<div class="form-group"> </div>
<label for="text-transform-lowercase">Lowercase</label> <div class="form-group">
<input id="text-transform-lowercase" type="radio" name="text-transform" value="lowercase" /> <label for="text-transform-lowercase">Lowercase</label>
</div> <input id="text-transform-lowercase" type="radio" name="text-transform" value="lowercase" />
</fieldset> </div>
<fieldset> </fieldset>
<legend>Debug</legend> <fieldset>
<div class="form-group"> <legend>Debug</legend>
<label for="direction">Direction</label> <div class="form-group">
<input id="direction" readonly type="text" /> <label for="direction">Direction</label>
</div> <input id="direction" readonly type="text" />
<div class="form-group"> </div>
<label for="focus-node">Focus Node</label> <div class="form-group">
<input id="focus-node" readonly type="text" /> <label for="focus-node">Focus Node</label>
</div> <input id="focus-node" readonly type="text" />
<div class="form-group"> </div>
<label for="focus-offset">Focus offset</label> <div class="form-group">
<input id="focus-offset" readonly type="number"> <label for="focus-offset">Focus offset</label>
</div> <input id="focus-offset" readonly type="number">
<div class="form-group"> </div>
<label for="focus-inline">Focus Inline</label> <div class="form-group">
<input id="focus-inline" readonly type="text" /> <label for="focus-inline">Focus Inline</label>
</div> <input id="focus-inline" readonly type="text" />
<div class="form-group"> </div>
<label for="focus-paragraph">Focus Paragraph</label> <div class="form-group">
<input id="focus-paragraph" readonly type="text" /> <label for="focus-paragraph">Focus Paragraph</label>
</div> <input id="focus-paragraph" readonly type="text" />
<div class="form-group"> </div>
<label for="anchor-node">Anchor Node</label> <div class="form-group">
<input id="anchor-node" readonly type="text" /> <label for="anchor-node">Anchor Node</label>
</div> <input id="anchor-node" readonly type="text" />
<div class="form-group"> </div>
<label for="anchor-offset">Anchor offset</label> <div class="form-group">
<input id="anchor-offset" readonly type="number"> <label for="anchor-offset">Anchor offset</label>
</div> <input id="anchor-offset" readonly type="number">
<div class="form-group"> </div>
<label for="anchor-inline">Anchor Inline</label> <div class="form-group">
<input id="anchor-inline" readonly type="text" /> <label for="anchor-inline">Anchor Inline</label>
</div> <input id="anchor-inline" readonly type="text" />
<div class="form-group"> </div>
<label for="anchor-paragraph">Anchor Paragraph</label> <div class="form-group">
<input id="anchor-paragraph" readonly type="text" /> <label for="anchor-paragraph">Anchor Paragraph</label>
</div> <input id="anchor-paragraph" readonly type="text" />
<div class="form-group"> </div>
<label for="start-container">Start container</label> <div class="form-group">
<input id="start-container" readonly type="text" /> <label for="start-container">Start container</label>
</div> <input id="start-container" readonly type="text" />
<div class="form-group"> </div>
<label for="start-offset">Start offset</label> <div class="form-group">
<input id="start-offset" readonly type="text" /> <label for="start-offset">Start offset</label>
</div> <input id="start-offset" readonly type="text" />
<div class="form-group"> </div>
<label for="end-container">End container</label> <div class="form-group">
<input id="end-container" readonly type="text" /> <label for="end-container">End container</label>
</div> <input id="end-container" readonly type="text" />
<div class="form-group"> </div>
<label for="end-offset">End offset</label> <div class="form-group">
<input id="end-offset" readonly type="text" /> <label for="end-offset">End offset</label>
</div> <input id="end-offset" readonly type="text" />
<div class="form-group"> </div>
<label for="multi">Multi?</label> <div class="form-group">
<input id="multi" readonly type="checkbox" /> <label for="multi">Multi?</label>
</div> <input id="multi" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="multi-inline">Multi inline?</label> <div class="form-group">
<input id="multi-inline" readonly type="checkbox" /> <label for="multi-inline">Multi inline?</label>
</div> <input id="multi-inline" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="multi-paragraph">Multi paragraph?</label> <div class="form-group">
<input id="multi-paragraph" readonly type="checkbox" /> <label for="multi-paragraph">Multi paragraph?</label>
</div> <input id="multi-paragraph" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="is-text-focus">Is text focus?</label> <div class="form-group">
<input id="is-text-focus" readonly type="checkbox" /> <label for="is-text-focus">Is text focus?</label>
</div> <input id="is-text-focus" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="is-text-anchor">Is text anchor?</label> <div class="form-group">
<input id="is-text-anchor" readonly type="checkbox" /> <label for="is-text-anchor">Is text anchor?</label>
</div> <input id="is-text-anchor" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="is-paragraph-start">Is paragraph start?</label> <div class="form-group">
<input id="is-paragraph-start" readonly type="checkbox" /> <label for="is-paragraph-start">Is paragraph start?</label>
</div> <input id="is-paragraph-start" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="is-paragraph-end">Is paragraph end?</label> <div class="form-group">
<input id="is-paragraph-end" readonly type="checkbox" /> <label for="is-paragraph-end">Is paragraph end?</label>
</div> <input id="is-paragraph-end" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="is-inline-start">Is inline start?</label> <div class="form-group">
<input id="is-inline-start" readonly type="checkbox" /> <label for="is-inline-start">Is inline start?</label>
</div> <input id="is-inline-start" readonly type="checkbox" />
<div class="form-group"> </div>
<label for="is-inline-end">Is inline end?</label> <div class="form-group">
<input id="is-inline-end" readonly type="checkbox"> <label for="is-inline-end">Is inline end?</label>
</div> <input id="is-inline-end" readonly type="checkbox">
</fieldset> </div>
</form> </fieldset>
<!-- </form>
<!--
Editor Editor
--> -->
<div class="text-editor-container align-top"> <div class="text-editor-container align-top">
<div <div
id="text-editor-selection-imposter" id="text-editor-selection-imposter"
class="text-editor-selection-imposter"></div> class="text-editor-selection-imposter"></div>
<div <div
class="text-editor-content" class="text-editor-content"
contenteditable="true" contenteditable="true"
role="textbox" role="textbox"
aria-multiline="true" aria-multiline="true"
aria-autocomplete="none" aria-autocomplete="none"
spellcheck="false" spellcheck="false"
autocapitalize="false"></div> autocapitalize="false"></div>
</div>
<!--
Text output
-->
<canvas id="canvas"></canvas>
</div> </div>
<!--
Text output
-->
<div id="output"></div>
<canvas id="canvas"></canvas>
<script type="module"> <script type="module">
import "./style.css"; import "./style.css";
import "./editor/TextEditor.css"; import "./editor/TextEditor.css";
@@ -403,7 +399,7 @@
}); });
} }
const fontSize = 120; const fontSize = 14;
const children = []; const children = [];
const uuid = crypto.randomUUID(); const uuid = crypto.randomUUID();
children.push(uuid); children.push(uuid);
@@ -416,11 +412,8 @@
}); });
textEditor.addEventListener("needslayout", (e) => { textEditor.addEventListener("needslayout", (e) => {
console.log("needs layout");
useShape(uuid); useShape(uuid);
console.log("useShape", uuid);
updateTextShape(fontSize, textEditor.root); updateTextShape(fontSize, textEditor.root);
console.log("updateTextShape", fontSize);
render(); render();
}) })
@@ -473,24 +466,11 @@
const x1 = 0; const x1 = 0;
const y1 = 0; const y1 = 0;
const width = 1000; const width = canvas.width;
const height = 500; const height = canvas.height;
Module._set_shape_selrect(x1, y1, x1 + width, y1 + height); Module._set_shape_selrect(x1, y1, x1 + width, y1 + height);
/* addTextShape(fontSize, "");
if (Math.random() < 0.3) {
const numStrokes = getRandomInt(1, 3);
for (let j = 0; j < numStrokes; j++) {
const strokeWidth = getRandomInt(1, 10);
Module._add_shape_center_stroke(strokeWidth, 0, 0, 0);
const color = getRandomColor();
const argb2 = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
addShapeSolidStrokeFill(argb2);
}
}
*/
addTextShape(fontSize, "Hello, World!");
useShape("00000000-0000-0000-0000-000000000000"); useShape("00000000-0000-0000-0000-000000000000");
setShapeChildren(children); setShapeChildren(children);

View File

@@ -290,11 +290,61 @@ function getFontStyle(fontStyle) {
} }
} }
export function updateTextShape(fontSize, root) { const PARAGRAPH_ATTR_SIZE = 48;
const paragraphAttrSize = 48; const LEAF_ATTR_SIZE = 60;
const leafAttrSize = 56; const FILL_SIZE = 160;
const fillSize = 160;
function setParagraphData(dview, { numLeaves, textAlign, textDirection, textDecoration, textTransform, lineHeight, letterSpacing }) {
// Set number of leaves
dview.setUint32(0, numLeaves, true);
// Serialize paragraph attributes
dview.setUint8(4, textAlign, true); // text-align: left
dview.setUint8(5, textDirection, true); // text-direction: LTR
dview.setUint8(6, textDecoration, true); // text-decoration: none
dview.setUint8(7, textTransform, true); // text-transform: none
dview.setFloat32(8, lineHeight, true); // line-height
dview.setFloat32(12, letterSpacing, true); // letter-spacing
dview.setUint32(16, 0, true); // typography-ref-file (UUID part 1)
dview.setUint32(20, 0, true); // typography-ref-file (UUID part 2)
dview.setUint32(24, 0, true); // typography-ref-file (UUID part 3)
dview.setUint32(28, 0, true); // typography-ref-file (UUID part 4)
dview.setUint32(32, 0, true); // typography-ref-id (UUID part 1)
dview.setUint32(36, 0, true); // typography-ref-id (UUID part 2)
dview.setUint32(40, 0, true); // typography-ref-id (UUID part 3)
dview.setUint32(44, 0, true); // typography-ref-id (UUID part 4)
}
function setLeafData(dview, leafOffset, {
fontStyle,
fontSize,
fontWeight,
letterSpacing,
textSize,
totalFills
}) {
// Serialize leaf attributes
dview.setUint8(leafOffset + 0, fontStyle, true); // font-style: normal
dview.setUint8(leafOffset + 1, 0, true); // text-decoration: none
dview.setUint8(leafOffset + 2, 0, true); // text-transform: none
dview.setUint8(leafOffset + 3, 0, true); // text-direction: ltr
dview.setFloat32(leafOffset + 4, fontSize, true); // font-size
dview.setFloat32(leafOffset + 8, letterSpacing, true); // letter-spacing
dview.setInt32(leafOffset + 12, fontWeight, 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.setUint32(leafOffset + 28, 0, true); // font-id (UUID part 4)
dview.setUint32(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.setUint32(leafOffset + 48, 0, true); // font-variant-id (UUID part 4)
dview.setUint32(leafOffset + 52, textSize, true); // text-length
dview.setUint32(leafOffset + 56, totalFills, true); // total fills count
}
export function updateTextShape(fontSize, root) {
// Calculate fills // Calculate fills
const fills = [ const fills = [
{ {
@@ -305,14 +355,14 @@ export function updateTextShape(fontSize, root) {
]; ];
const totalFills = fills.length; const totalFills = fills.length;
const totalFillsSize = totalFills * fillSize; const totalFillsSize = totalFills * FILL_SIZE;
const paragraphs = root.children; const paragraphs = root.children;
console.log("paragraphs", paragraphs.length); console.log("paragraphs", paragraphs.length);
Module._clear_shape_text(); Module._clear_shape_text();
for (const paragraph of paragraphs) { for (const paragraph of paragraphs) {
let totalSize = paragraphAttrSize; let totalSize = PARAGRAPH_ATTR_SIZE;
const leaves = paragraph.children; const leaves = paragraph.children;
const numLeaves = leaves.length; const numLeaves = leaves.length;
@@ -323,7 +373,7 @@ export function updateTextShape(fontSize, root) {
const textBuffer = new TextEncoder().encode(text); const textBuffer = new TextEncoder().encode(text);
const textSize = textBuffer.byteLength; const textSize = textBuffer.byteLength;
console.log("text", text, textSize); console.log("text", text, textSize);
totalSize += leafAttrSize + totalFillsSize; totalSize += LEAF_ATTR_SIZE + totalFillsSize;
} }
totalSize += paragraph.textContent.length; totalSize += paragraph.textContent.length;
@@ -371,39 +421,30 @@ export function updateTextShape(fontSize, root) {
typography_ref_id: [u32; 4], typography_ref_id: [u32; 4],
*/ */
// Set number of leaves setParagraphData(dview, {
dview.setUint32(0, numLeaves, true); numLeaves,
textAlign,
// Serialize paragraph attributes textDecoration,
dview.setUint8(4, textAlign, true); // text-align: left textTransform,
dview.setUint8(5, textDirection, true); // text-direction: LTR textDirection,
dview.setUint8(6, textDecoration, true); // text-decoration: none lineHeight,
dview.setUint8(7, textTransform, true); // text-transform: none letterSpacing
dview.setFloat32(8, lineHeight, true); // line-height })
dview.setFloat32(12, letterSpacing, true); // letter-spacing let leafOffset = PARAGRAPH_ATTR_SIZE;
dview.setUint32(16, 0, true); // typography-ref-file (UUID part 1)
dview.setUint32(20, 0, true); // typography-ref-file (UUID part 2)
dview.setUint32(24, 0, true); // typography-ref-file (UUID part 3)
dview.setUint32(28, 0, true); // typography-ref-file (UUID part 4)
dview.setUint32(32, 0, true); // typography-ref-id (UUID part 1)
dview.setUint32(36, 0, true); // typography-ref-id (UUID part 2)
dview.setUint32(40, 0, true); // typography-ref-id (UUID part 3)
dview.setUint32(44, 0, true); // typography-ref-id (UUID part 4)
let leafOffset = paragraphAttrSize;
for (const leaf of leaves) { for (const leaf of leaves) {
console.log( console.log(
"leafOffset", "leafOffset",
leafOffset, leafOffset,
paragraphAttrSize, PARAGRAPH_ATTR_SIZE,
leafAttrSize, LEAF_ATTR_SIZE,
fillSize, FILL_SIZE,
totalFills, totalFills,
totalFillsSize, totalFillsSize,
); );
const fontStyle = getFontStyle(leaf.style.getPropertyValue("font-style")); const fontStyle = getFontStyle(leaf.style.getPropertyValue("font-style"));
const fontSize = parseFloat(leaf.style.getPropertyValue("font-size")); const fontSize = parseFloat(leaf.style.getPropertyValue("font-size"));
console.log("font-size", fontSize); const letterSpacing = parseFloat(leaf.style.getPropertyValue("letter-spacing"))
console.log("font-size", fontSize, "letter-spacing", letterSpacing);
const fontWeight = parseInt( const fontWeight = parseInt(
leaf.style.getPropertyValue("font-weight"), leaf.style.getPropertyValue("font-weight"),
10, 10,
@@ -414,35 +455,29 @@ export function updateTextShape(fontSize, root) {
const textBuffer = new TextEncoder().encode(text); const textBuffer = new TextEncoder().encode(text);
const textSize = textBuffer.byteLength; const textSize = textBuffer.byteLength;
// Serialize leaf attributes setLeafData(dview, leafOffset, {
dview.setUint8(leafOffset + 0, fontStyle, true); // font-style: normal fontStyle,
dview.setUint8(leafOffset + 1, 0, true); // text-decoration: none textDecoration: 0,
dview.setUint8(leafOffset + 2, 0, true); // text-transform: none textTransform: 0,
dview.setFloat32(leafOffset + 4, fontSize, true); // font-size textDirection: 0,
dview.setInt32(leafOffset + 8, fontWeight, true); // font-weight: normal fontSize,
dview.setUint32(leafOffset + 12, 0, true); // font-id (UUID part 1) fontWeight,
dview.setUint32(leafOffset + 16, 0, true); // font-id (UUID part 2) letterSpacing,
dview.setUint32(leafOffset + 20, 0, true); // font-id (UUID part 3) textSize,
dview.setUint32(leafOffset + 24, 0, true); // font-id (UUID part 4) totalFills
dview.setUint32(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.setUint32(leafOffset + 44, 0, true); // font-variant-id (UUID part 4)
dview.setUint32(leafOffset + 48, textSize, true); // text-length
dview.setUint32(leafOffset + 52, totalFills, true); // total fills count
// Serialize fills // Serialize fills
let fillOffset = leafOffset + leafAttrSize; let fillOffset = leafOffset + LEAF_ATTR_SIZE;
fills.forEach((fill) => { fills.forEach((fill) => {
if (fill.type === "solid") { if (fill.type === "solid") {
const argb = hexToU32ARGB(fill.color, fill.opacity); const argb = hexToU32ARGB(fill.color, fill.opacity);
dview.setUint8(fillOffset + 0, 0x00, true); // Fill type: solid dview.setUint8(fillOffset + 0, 0x00, true); // Fill type: solid
dview.setUint32(fillOffset + 4, argb, true); dview.setUint32(fillOffset + 4, argb, true);
fillOffset += fillSize; // Move to the next fill fillOffset += FILL_SIZE; // Move to the next fill
} }
}); });
leafOffset += leafAttrSize + totalFillsSize; leafOffset += LEAF_ATTR_SIZE + totalFillsSize;
} }
const text = paragraph.textContent; const text = paragraph.textContent;
@@ -459,9 +494,6 @@ export function updateTextShape(fontSize, root) {
export function addTextShape(fontSize, text) { export function addTextShape(fontSize, text) {
const numLeaves = 1; // Single text leaf for simplicity const numLeaves = 1; // Single text leaf for simplicity
const paragraphAttrSize = 48;
const leafAttrSize = 56;
const fillSize = 160;
const textBuffer = new TextEncoder().encode(text); const textBuffer = new TextEncoder().encode(text);
const textSize = textBuffer.byteLength; const textSize = textBuffer.byteLength;
@@ -474,10 +506,10 @@ export function addTextShape(fontSize, text) {
}, },
]; ];
const totalFills = fills.length; const totalFills = fills.length;
const totalFillsSize = totalFills * fillSize; const totalFillsSize = totalFills * FILL_SIZE;
// Calculate metadata and total buffer size // Calculate metadata and total buffer size
const metadataSize = paragraphAttrSize + leafAttrSize + totalFillsSize; const metadataSize = PARAGRAPH_ATTR_SIZE + LEAF_ATTR_SIZE + totalFillsSize;
const totalSize = metadataSize + textSize; const totalSize = metadataSize + textSize;
// Allocate buffer // Allocate buffer
@@ -485,50 +517,37 @@ export function addTextShape(fontSize, text) {
const heap = new Uint8Array(Module.HEAPU8.buffer, bufferPtr, totalSize); const heap = new Uint8Array(Module.HEAPU8.buffer, bufferPtr, totalSize);
const dview = new DataView(heap.buffer, bufferPtr, totalSize); const dview = new DataView(heap.buffer, bufferPtr, totalSize);
// Set number of leaves setParagraphData(dview, {
dview.setUint32(0, numLeaves, true); numLeaves,
textAlign: 0,
// Serialize paragraph attributes textDecoration: 0,
dview.setUint8(4, 1); // text-align: left textTransform: 0,
dview.setUint8(5, 0); // text-direction: LTR textDirection: 0,
dview.setUint8(6, 0); // text-decoration: none lineHeight: 1.2,
dview.setUint8(7, 0); // text-transform: none letterSpacing: 0,
dview.setFloat32(8, 1.2, true); // line-height });
dview.setFloat32(12, 0, true); // letter-spacing
dview.setUint32(16, 0, true); // typography-ref-file (UUID part 1)
dview.setUint32(20, 0, true); // typography-ref-file (UUID part 2)
dview.setUint32(24, 0, true); // typography-ref-file (UUID part 3)
dview.setInt32(28, 0, true); // typography-ref-file (UUID part 4)
dview.setUint32(32, 0, true); // typography-ref-id (UUID part 1)
dview.setUint32(36, 0, true); // typography-ref-id (UUID part 2)
dview.setUint32(40, 0, true); // typography-ref-id (UUID part 3)
dview.setInt32(44, 0, true); // typography-ref-id (UUID part 4)
// Serialize leaf attributes // Serialize leaf attributes
const leafOffset = paragraphAttrSize; const leafOffset = PARAGRAPH_ATTR_SIZE;
dview.setUint8(leafOffset, 0); // font-style: normal setLeafData(dview, leafOffset, {
dview.setFloat32(leafOffset + 4, fontSize, true); // font-size fontSize,
dview.setUint32(leafOffset + 8, 400, true); // font-weight: normal fontWeight: 400,
dview.setUint32(leafOffset + 12, 0, true); // font-id (UUID part 1) textDecoration: 0,
dview.setUint32(leafOffset + 16, 0, true); // font-id (UUID part 2) textDirection: 0,
dview.setUint32(leafOffset + 20, 0, true); // font-id (UUID part 3) textTransform: 0,
dview.setInt32(leafOffset + 24, 0, true); // font-id (UUID part 4) letterSpacing: 0,
dview.setInt32(leafOffset + 28, 0, true); // font-family hash textSize,
dview.setUint32(leafOffset + 32, 0, true); // font-variant-id (UUID part 1) totalFills,
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
// Serialize fills // Serialize fills
let fillOffset = leafOffset + leafAttrSize; let fillOffset = leafOffset + LEAF_ATTR_SIZE;
fills.forEach((fill) => { fills.forEach((fill) => {
if (fill.type === "solid") { if (fill.type === "solid") {
const argb = hexToU32ARGB(fill.color, fill.opacity); const argb = hexToU32ARGB(fill.color, fill.opacity);
dview.setUint8(fillOffset, 0x00, true); // Fill type: solid dview.setUint8(fillOffset, 0x00, true); // Fill type: solid
dview.setUint32(fillOffset + 4, argb, true); dview.setUint32(fillOffset + 4, argb, true);
fillOffset += fillSize; // Move to the next fill fillOffset += FILL_SIZE; // Move to the next fill
} }
}); });

View File

@@ -362,11 +362,13 @@ pub struct TextLeaf {
text: String, text: String,
font_family: FontFamily, font_family: FontFamily,
font_size: f32, font_size: f32,
letter_spacing: f32,
font_style: u8, font_style: u8,
font_weight: i32, font_weight: i32,
font_variant_id: Uuid, font_variant_id: Uuid,
text_decoration: u8, text_decoration: u8,
text_transform: u8, text_transform: u8,
text_direction: u8,
fills: Vec<shapes::Fill>, fills: Vec<shapes::Fill>,
} }
@@ -376,9 +378,11 @@ impl TextLeaf {
text: String, text: String,
font_family: FontFamily, font_family: FontFamily,
font_size: f32, font_size: f32,
letter_spacing: f32,
font_style: u8, font_style: u8,
text_decoration: u8, text_decoration: u8,
text_transform: u8, text_transform: u8,
text_direction: u8,
font_weight: i32, font_weight: i32,
font_variant_id: Uuid, font_variant_id: Uuid,
fills: Vec<shapes::Fill>, fills: Vec<shapes::Fill>,
@@ -387,9 +391,11 @@ impl TextLeaf {
text, text,
font_family, font_family,
font_size, font_size,
letter_spacing,
font_style, font_style,
text_decoration, text_decoration,
text_transform, text_transform,
text_direction,
font_weight, font_weight,
font_variant_id, font_variant_id,
fills, fills,
@@ -413,7 +419,7 @@ impl TextLeaf {
style.set_foreground_paint(&paint); style.set_foreground_paint(&paint);
style.set_font_size(self.font_size); style.set_font_size(self.font_size);
style.set_letter_spacing(paragraph.letter_spacing); style.set_letter_spacing(self.letter_spacing);
style.set_height(paragraph.line_height); style.set_height(paragraph.line_height);
style.set_height_override(true); style.set_height_override(true);
style.set_half_leading(false); style.set_half_leading(false);
@@ -452,7 +458,7 @@ impl TextLeaf {
let mut style = self.to_style(paragraph, &Rect::default(), fallback_fonts, blur, blur_mask); let mut style = self.to_style(paragraph, &Rect::default(), fallback_fonts, blur, blur_mask);
style.set_foreground_paint(stroke_paint); style.set_foreground_paint(stroke_paint);
style.set_font_size(self.font_size); style.set_font_size(self.font_size);
style.set_letter_spacing(paragraph.letter_spacing); style.set_letter_spacing(self.letter_spacing);
style.set_decoration_type(match self.text_decoration { style.set_decoration_type(match self.text_decoration {
0 => skia::textlayout::TextDecoration::NO_DECORATION, 0 => skia::textlayout::TextDecoration::NO_DECORATION,
1 => skia::textlayout::TextDecoration::UNDERLINE, 1 => skia::textlayout::TextDecoration::UNDERLINE,
@@ -503,6 +509,7 @@ pub struct RawTextLeaf {
text_decoration: u8, text_decoration: u8,
text_transform: u8, text_transform: u8,
font_size: f32, font_size: f32,
letter_spacing: f32,
font_weight: i32, font_weight: i32,
font_id: [u32; 4], font_id: [u32; 4],
font_family: [u8; 4], font_family: [u8; 4],
@@ -535,8 +542,9 @@ pub struct RawTextLeafData {
font_style: u8, font_style: u8,
text_decoration: u8, text_decoration: u8,
text_transform: u8, text_transform: u8,
byte_padding: u8, text_direction: u8,
font_size: f32, font_size: f32,
letter_spacing: f32,
font_weight: i32, font_weight: i32,
font_id: [u32; 4], font_id: [u32; 4],
font_family: [u8; 4], font_family: [u8; 4],
@@ -565,8 +573,9 @@ impl From<&[u8]> for RawTextLeafData {
font_style: text_leaf.font_style, font_style: text_leaf.font_style,
text_decoration: text_leaf.text_decoration, text_decoration: text_leaf.text_decoration,
text_transform: text_leaf.text_transform, text_transform: text_leaf.text_transform,
byte_padding: 0, text_direction: 0, // TODO: Añadirlo
font_size: text_leaf.font_size, font_size: text_leaf.font_size,
letter_spacing: text_leaf.letter_spacing,
font_weight: text_leaf.font_weight, font_weight: text_leaf.font_weight,
font_id: text_leaf.font_id, font_id: text_leaf.font_id,
font_family: text_leaf.font_family, font_family: text_leaf.font_family,
@@ -669,9 +678,11 @@ impl From<&Vec<u8>> for RawTextData {
text, text,
font_family, font_family,
text_leaf.font_size, text_leaf.font_size,
text_leaf.letter_spacing,
text_leaf.font_style, text_leaf.font_style,
text_leaf.text_decoration, text_leaf.text_decoration,
text_leaf.text_transform, text_leaf.text_transform,
text_leaf.text_direction,
text_leaf.font_weight, text_leaf.font_weight,
font_variant_id, font_variant_id,
text_leaf.fills.clone(), text_leaf.fills.clone(),