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

View File

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

View File

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

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

View File

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

View File

@@ -362,11 +362,13 @@ pub struct TextLeaf {
text: String,
font_family: FontFamily,
font_size: f32,
letter_spacing: f32,
font_style: u8,
font_weight: i32,
font_variant_id: Uuid,
text_decoration: u8,
text_transform: u8,
text_direction: u8,
fills: Vec<shapes::Fill>,
}
@@ -376,9 +378,11 @@ impl TextLeaf {
text: String,
font_family: FontFamily,
font_size: f32,
letter_spacing: f32,
font_style: u8,
text_decoration: u8,
text_transform: u8,
text_direction: u8,
font_weight: i32,
font_variant_id: Uuid,
fills: Vec<shapes::Fill>,
@@ -387,9 +391,11 @@ impl TextLeaf {
text,
font_family,
font_size,
letter_spacing,
font_style,
text_decoration,
text_transform,
text_direction,
font_weight,
font_variant_id,
fills,
@@ -413,7 +419,7 @@ impl TextLeaf {
style.set_foreground_paint(&paint);
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_override(true);
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);
style.set_foreground_paint(stroke_paint);
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 {
0 => skia::textlayout::TextDecoration::NO_DECORATION,
1 => skia::textlayout::TextDecoration::UNDERLINE,
@@ -503,6 +509,7 @@ pub struct RawTextLeaf {
text_decoration: u8,
text_transform: u8,
font_size: f32,
letter_spacing: f32,
font_weight: i32,
font_id: [u32; 4],
font_family: [u8; 4],
@@ -535,8 +542,9 @@ pub struct RawTextLeafData {
font_style: u8,
text_decoration: u8,
text_transform: u8,
byte_padding: u8,
text_direction: u8,
font_size: f32,
letter_spacing: f32,
font_weight: i32,
font_id: [u32; 4],
font_family: [u8; 4],
@@ -565,8 +573,9 @@ impl From<&[u8]> for RawTextLeafData {
font_style: text_leaf.font_style,
text_decoration: text_leaf.text_decoration,
text_transform: text_leaf.text_transform,
byte_padding: 0,
text_direction: 0, // TODO: Añadirlo
font_size: text_leaf.font_size,
letter_spacing: text_leaf.letter_spacing,
font_weight: text_leaf.font_weight,
font_id: text_leaf.font_id,
font_family: text_leaf.font_family,
@@ -669,9 +678,11 @@ impl From<&Vec<u8>> for RawTextData {
text,
font_family,
text_leaf.font_size,
text_leaf.letter_spacing,
text_leaf.font_style,
text_leaf.text_decoration,
text_leaf.text_transform,
text_leaf.text_direction,
text_leaf.font_weight,
font_variant_id,
text_leaf.fills.clone(),