ntk 7.1.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/renderingcontext_2d.js +32 -1
- package/lib/text/glyphs.js +70 -8
- package/lib/text/layout.js +10 -1
- package/package.json +1 -1
|
@@ -525,6 +525,7 @@ class RenderingContext2d {
|
|
|
525
525
|
textStyle: this._textStyle,
|
|
526
526
|
fontString: this._lastFontString,
|
|
527
527
|
fontVariations: this._fontVariations,
|
|
528
|
+
textRendering: this._textRendering,
|
|
528
529
|
textAlign: this.textAlign,
|
|
529
530
|
textBaseline: this.textBaseline,
|
|
530
531
|
m: this._m.slice(),
|
|
@@ -548,6 +549,7 @@ class RenderingContext2d {
|
|
|
548
549
|
this._textStyle = s.textStyle;
|
|
549
550
|
this._lastFontString = s.fontString;
|
|
550
551
|
this._fontVariations = s.fontVariations;
|
|
552
|
+
this._textRendering = s.textRendering;
|
|
551
553
|
this.textAlign = s.textAlign;
|
|
552
554
|
this.textBaseline = s.textBaseline;
|
|
553
555
|
this._m = s.m;
|
|
@@ -2339,7 +2341,12 @@ class RenderingContext2d {
|
|
|
2339
2341
|
const positioned = [];
|
|
2340
2342
|
let cursor = ox;
|
|
2341
2343
|
for (const run of reorderRuns(shaped.runs)) {
|
|
2342
|
-
positioned.push({
|
|
2344
|
+
positioned.push({
|
|
2345
|
+
run,
|
|
2346
|
+
x: cursor,
|
|
2347
|
+
y: oy,
|
|
2348
|
+
textRendering: this._textRendering,
|
|
2349
|
+
});
|
|
2343
2350
|
cursor += run.width;
|
|
2344
2351
|
}
|
|
2345
2352
|
this.drawGlyphs(
|
|
@@ -2451,6 +2458,30 @@ class RenderingContext2d {
|
|
|
2451
2458
|
return this._fontVariations ?? null;
|
|
2452
2459
|
}
|
|
2453
2460
|
|
|
2461
|
+
/**
|
|
2462
|
+
* CSS's `text-rendering`: which glyph path this text takes, overriding the
|
|
2463
|
+
* size thresholds in `app.textPolicy`.
|
|
2464
|
+
*
|
|
2465
|
+
* - `'geometricPrecision'` — outlines every draw, glyph origins **not**
|
|
2466
|
+
* rounded to whole pixels. What display text wants, and what any text
|
|
2467
|
+
* whose shape is being animated wants: a variable font's axis moves
|
|
2468
|
+
* advances by fractions of a pixel, and cached glyphs can only land on
|
|
2469
|
+
* whole ones, so those fractions accumulate until a glyph crosses a
|
|
2470
|
+
* rounding boundary and jumps a pixel on its own.
|
|
2471
|
+
* - `'optimizeSpeed'` — cached server-side glyphs at any size.
|
|
2472
|
+
* - `'auto'` (default) — the thresholds decide.
|
|
2473
|
+
*
|
|
2474
|
+
* `'optimizeLegibility'` is accepted and means `'auto'`; ntk has no
|
|
2475
|
+
* hinting to turn on.
|
|
2476
|
+
*/
|
|
2477
|
+
set textRendering(val) {
|
|
2478
|
+
this._textRendering = val || undefined;
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
get textRendering() {
|
|
2482
|
+
return this._textRendering ?? "auto";
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2454
2485
|
// ------------------------------------------------------------------
|
|
2455
2486
|
// gradients / images
|
|
2456
2487
|
|
package/lib/text/glyphs.js
CHANGED
|
@@ -20,11 +20,17 @@ import { trapezoidize } from '../trapezoid.js';
|
|
|
20
20
|
* - `cacheBytes` — LRU budget for uploaded glyph bitmaps per connection;
|
|
21
21
|
* least-recently-drawn (face, size) pages are freed server-side
|
|
22
22
|
* (FreeGlyphSet) so transient sizes don't accumulate.
|
|
23
|
+
* - `textRendering` — an app-wide default for the per-run property of the
|
|
24
|
+
* same name (see `routeForRendering`). Left undefined the thresholds
|
|
25
|
+
* decide, which is what almost every app wants; `'geometricPrecision'`
|
|
26
|
+
* here is the blunt instrument for a window that is all display text.
|
|
27
|
+
* A run that names its own always wins.
|
|
23
28
|
*/
|
|
24
29
|
export const DEFAULT_TEXT_POLICY = {
|
|
25
30
|
bitmapMax: 128,
|
|
26
31
|
vectorFrom: 256,
|
|
27
|
-
cacheBytes: 8 << 20
|
|
32
|
+
cacheBytes: 8 << 20,
|
|
33
|
+
textRendering: undefined
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
function policyOf(app) {
|
|
@@ -228,21 +234,75 @@ export function positionGlyphs(positioned) {
|
|
|
228
234
|
*
|
|
229
235
|
* @returns {'bitmap'|'vector'}
|
|
230
236
|
*/
|
|
231
|
-
|
|
237
|
+
/**
|
|
238
|
+
* CSS's `text-rendering`, as far as it means anything here: a run's own
|
|
239
|
+
* answer to which glyph path it takes, overriding the size thresholds.
|
|
240
|
+
*
|
|
241
|
+
* - `geometricPrecision` — the vector path, always. Outlines are flattened
|
|
242
|
+
* at the exact size and glyph origins are not rounded, so advances land
|
|
243
|
+
* where shaping put them. This is what display text wants, and what any
|
|
244
|
+
* text whose shape is being animated wants: a variable font's axis moves
|
|
245
|
+
* advances by fractions of a pixel, and on the bitmap path those fractions
|
|
246
|
+
* accumulate silently until a glyph crosses a rounding boundary and jumps
|
|
247
|
+
* a whole pixel on its own. It is also the path that caches nothing, so
|
|
248
|
+
* an axis under a slider stops minting a glyph page per step.
|
|
249
|
+
* - `optimizeSpeed` — the bitmap path, always. Cached server-side glyphs at
|
|
250
|
+
* any size, for text that is large but static.
|
|
251
|
+
* - `auto` (the default, and anything unrecognized) — the size thresholds
|
|
252
|
+
* and the churn ring decide, as before.
|
|
253
|
+
*
|
|
254
|
+
* `optimizeLegibility` is accepted and means `auto`: ntk has no hinting to
|
|
255
|
+
* turn on, so promising anything by it would be a lie.
|
|
256
|
+
*/
|
|
257
|
+
export function routeForRendering(textRendering) {
|
|
258
|
+
if (textRendering === 'geometricPrecision') return 'vector';
|
|
259
|
+
if (textRendering === 'optimizeSpeed') return 'bitmap';
|
|
260
|
+
return null; // auto: ask the thresholds
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function routeGlyphSize(app, font, size, policy = policyOf(app), textRendering) {
|
|
264
|
+
// Inlined rather than a call to `routeForRendering`: this is the first
|
|
265
|
+
// thing every run of every draw does, and the overwhelmingly common answer
|
|
266
|
+
// is "nobody asked". One `??` and one comparison get us past it.
|
|
267
|
+
const asked = textRendering ?? policy.textRendering;
|
|
268
|
+
if (asked !== undefined && asked !== 'auto') {
|
|
269
|
+
const route = routeForRendering(asked);
|
|
270
|
+
if (route) return route;
|
|
271
|
+
}
|
|
232
272
|
if (size <= policy.bitmapMax) return 'bitmap';
|
|
233
273
|
if (size > policy.vectorFrom) return 'vector';
|
|
234
274
|
|
|
275
|
+
// The ring answers "is this face being drawn at something it has drawn
|
|
276
|
+
// recently, or is it churning?" — so it has to be keyed by the thing that
|
|
277
|
+
// stays put while the churn happens.
|
|
278
|
+
//
|
|
279
|
+
// For a variable font that is the *base* face, not the instance: every
|
|
280
|
+
// point on an axis is a Font of its own, with its own key, so an animated
|
|
281
|
+
// axis handed each step a fresh empty ring and the churn was invisible —
|
|
282
|
+
// it read as eight unrelated faces each drawn once. Keyed by the base and
|
|
283
|
+
// recording the instance alongside the size, an axis sweep and a size
|
|
284
|
+
// sweep look like what they both are, and a page of static text at one
|
|
285
|
+
// weight still reuses its entry on every frame.
|
|
235
286
|
if (!app._sizeRings) app._sizeRings = new Map();
|
|
236
|
-
|
|
287
|
+
// A face with no instances behind it keeps the old ring exactly: its key
|
|
288
|
+
// is already constant across the ring, so the size alone identifies a
|
|
289
|
+
// glyph page and the entries stay numbers. Only a variable instance pays
|
|
290
|
+
// for the composite key, and only in this band — text at or below
|
|
291
|
+
// `bitmapMax` returned above without touching any of it.
|
|
292
|
+
const base = font.variationOf;
|
|
293
|
+
let ring = app._sizeRings.get(base ? base.key : font.key);
|
|
237
294
|
if (!ring) {
|
|
238
295
|
ring = [];
|
|
239
|
-
app._sizeRings.set(font.key, ring);
|
|
296
|
+
app._sizeRings.set(base ? base.key : font.key, ring);
|
|
240
297
|
}
|
|
241
|
-
|
|
298
|
+
// what a glyph page is keyed by, which is exactly what has to repeat for
|
|
299
|
+
// caching to pay for itself
|
|
300
|
+
const entry = base ? `${font.key}@${size}` : size;
|
|
301
|
+
const reused = ring.includes(entry);
|
|
242
302
|
// dedupe consecutive entries so one frame drawing many runs at one size
|
|
243
303
|
// occupies a single slot — the ring then spans ~8 distinct frames
|
|
244
|
-
if (ring[ring.length - 1] !==
|
|
245
|
-
ring.push(
|
|
304
|
+
if (ring[ring.length - 1] !== entry) {
|
|
305
|
+
ring.push(entry);
|
|
246
306
|
if (ring.length > 8) ring.shift();
|
|
247
307
|
}
|
|
248
308
|
|
|
@@ -269,7 +329,9 @@ export function drawGlyphRuns(app, op, srcId, dstId, positioned) {
|
|
|
269
329
|
let vector = null;
|
|
270
330
|
for (let i = 0; i < positioned.length; i++) {
|
|
271
331
|
const { run } = positioned[i];
|
|
272
|
-
if (
|
|
332
|
+
if (
|
|
333
|
+
routeGlyphSize(app, run.font, run.size, policy, positioned[i].textRendering) === 'vector'
|
|
334
|
+
) {
|
|
273
335
|
if (!vector) {
|
|
274
336
|
vector = [];
|
|
275
337
|
bitmap = positioned.slice(0, i);
|
package/lib/text/layout.js
CHANGED
|
@@ -67,6 +67,7 @@ export class TextLayout {
|
|
|
67
67
|
weight: s.weight ?? style.weight,
|
|
68
68
|
style: s.style ?? style.style,
|
|
69
69
|
variations: s.variations ?? style.variations,
|
|
70
|
+
textRendering: s.textRendering ?? style.textRendering,
|
|
70
71
|
features: s.features ?? style.features,
|
|
71
72
|
language: s.language ?? style.language,
|
|
72
73
|
color: s.color ?? style.color ?? null
|
|
@@ -497,7 +498,15 @@ export class TextLayout {
|
|
|
497
498
|
const color = r.span.color;
|
|
498
499
|
if (batch.length && color !== batchColor) flush();
|
|
499
500
|
batchColor = color;
|
|
500
|
-
batch.push({
|
|
501
|
+
batch.push({
|
|
502
|
+
run: r.run,
|
|
503
|
+
x: x + line.x + r.x,
|
|
504
|
+
y: y + line.baseline,
|
|
505
|
+
// per run, because it is a span property: one paragraph may hold
|
|
506
|
+
// a display word that wants exact positions and body text that
|
|
507
|
+
// wants its glyph cache. `drawGlyphRuns` already partitions.
|
|
508
|
+
textRendering: r.span.textRendering
|
|
509
|
+
});
|
|
501
510
|
}
|
|
502
511
|
}
|
|
503
512
|
flush();
|