Skip to content

Commit b191473

Browse files
committed
new tests for Typography
1 parent 8173282 commit b191473

10 files changed

Lines changed: 9486 additions & 30241 deletions

bench/baselines/node24-linux-x64-intel-core-i9-9820x-3-30ghz-chromium.json

Lines changed: 261 additions & 222 deletions
Large diffs are not rendered by default.

bench/baselines/node24-linux-x64-intel-core-i9-9820x-3-30ghz.json

Lines changed: 2747 additions & 2705 deletions
Large diffs are not rendered by default.

bench/browser/canvas.bench.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,58 @@ export default defineSuite("canvas", (b, { Pts, fx }) => {
160160
},
161161
);
162162

163+
// Same workload with the heuristic width estimator instead of measureText,
164+
// so both sides of the `fontWidthEstimate` toggle have a baseline.
165+
draw(
166+
"textBox (estimated width)",
167+
SIZES.S,
168+
() => ({
169+
form: makeForm().fontWidthEstimate(true),
170+
boxes: positive("canvas:textBox:est", SIZES.S).map((p) =>
171+
Group.fromArray([p, [p[0] + 120, p[1] + 40]]),
172+
),
173+
words: fx.words("canvas:textBox:est:words", SIZES.S, 20, 60),
174+
}),
175+
({ form, boxes, words }) => {
176+
for (let i = 0; i < SIZES.S; i++) form.textBox(boxes[i], words[i]);
177+
sink(SIZES.S);
178+
},
179+
);
180+
181+
// Word wrap re-measures the remaining text for every line, so paragraphBox
182+
// is the most expensive text call in the API. Small batch: each call wraps
183+
// a few hundred characters into many lines.
184+
draw(
185+
"paragraphBox",
186+
SIZES.XS,
187+
() => ({
188+
boxes: positive("canvas:paragraphBox", SIZES.XS).map((p) =>
189+
Group.fromArray([p, [p[0] + 220, p[1] + 160]]),
190+
),
191+
texts: fx.words("canvas:paragraphBox:texts", SIZES.XS, 240, 400),
192+
}),
193+
({ form, boxes, texts }) => {
194+
for (let i = 0; i < SIZES.XS; i++) form.paragraphBox(boxes[i], texts[i]);
195+
sink(SIZES.XS);
196+
},
197+
);
198+
199+
draw(
200+
"paragraphBox (estimated width)",
201+
SIZES.XS,
202+
() => ({
203+
form: makeForm().fontWidthEstimate(true),
204+
boxes: positive("canvas:paragraphBox:est", SIZES.XS).map((p) =>
205+
Group.fromArray([p, [p[0] + 220, p[1] + 160]]),
206+
),
207+
texts: fx.words("canvas:paragraphBox:est:texts", SIZES.XS, 240, 400),
208+
}),
209+
({ form, boxes, texts }) => {
210+
for (let i = 0; i < SIZES.XS; i++) form.paragraphBox(boxes[i], texts[i]);
211+
sink(SIZES.XS);
212+
},
213+
);
214+
163215
// Style changes force a context state write on every call, which is the
164216
// usual reason a colourful sketch is slower than a monochrome one.
165217
draw(

bench/suites/typography.bench.mjs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,61 @@ export default defineSuite("typography", (b, { Pts, fx }) => {
5454
},
5555
});
5656

57+
// A measure whose cost grows with string length, like a host `measureText`.
58+
// The flat-cost `measure` above times Typography's own overhead; this one
59+
// shows how the truncate/wrap algorithms scale when every probe re-measures
60+
// the whole remaining string.
61+
const measurePerChar = (text) => {
62+
let w = 0;
63+
for (let i = 0; i < text.length; i++) w += 4 + (text.charCodeAt(i) % 8);
64+
return w;
65+
};
66+
67+
const paragraph = (label, chars) => {
68+
const words = fx.words(label, 64, 3, 12);
69+
let text = words[0];
70+
for (let i = 1; text.length < chars; i++) {
71+
text += " " + words[i % words.length];
72+
}
73+
return text;
74+
};
75+
76+
b.case("Typography.truncate (long text, per-char measure)", {
77+
batch: 32,
78+
setupOnce: () => paragraph("typography:truncate:long", 2048),
79+
run: (text) => {
80+
let acc = 0;
81+
for (let i = 0; i < 32; i++) {
82+
acc += Typography.truncate(measurePerChar, text, 200 + i * 8, "...")[1];
83+
}
84+
sink(acc);
85+
},
86+
});
87+
88+
// The line-consumption loop inside `CanvasForm.paragraphBox`: truncate the
89+
// remaining text to one line's width, cut at the last space, repeat.
90+
// Re-measuring the whole remainder for every line makes this the quadratic
91+
// hot path in text-heavy sketches, so it gets its own baseline.
92+
b.case("paragraph wrap loop (per-char measure)", {
93+
batch: 4,
94+
setupOnce: () => paragraph("typography:wrap", 2048),
95+
run: (text) => {
96+
let lines = 0;
97+
for (let k = 0; k < 4; k++) {
98+
let sub = text;
99+
for (let guard = 0; sub && guard < 10000; guard++) {
100+
const t = Typography.truncate(measurePerChar, sub, 320, "");
101+
let dt = t[0].lastIndexOf(" ") + 1;
102+
if (dt <= 0 || t[1] === sub.length) dt = undefined;
103+
lines++;
104+
if (t[1] <= 0 || t[1] === sub.length) break;
105+
sub = sub.substring(dt ?? t[1]);
106+
}
107+
}
108+
sink(lines);
109+
},
110+
});
111+
57112
b.case("Typography.fontSizeToBox (build)", {
58113
batch: N,
59114
setupOnce: () =>

0 commit comments

Comments
 (0)