ntk 8.12.1 → 8.12.3
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 +9 -1
- package/lib/text/font.js +32 -6
- package/lib/text/glyphs.js +34 -5
- package/lib/text/layout.js +1 -3
- package/lib/text/shape.js +20 -0
- package/package.json +1 -1
|
@@ -2716,7 +2716,14 @@ class RenderingContext2d {
|
|
|
2716
2716
|
if (rect.w === 0 || rect.h === 0) return;
|
|
2717
2717
|
this._setPictureClip(rect);
|
|
2718
2718
|
}
|
|
2719
|
-
drawGlyphRuns(
|
|
2719
|
+
drawGlyphRuns(
|
|
2720
|
+
app,
|
|
2721
|
+
op,
|
|
2722
|
+
src.id,
|
|
2723
|
+
this._dst(),
|
|
2724
|
+
positioned,
|
|
2725
|
+
rect ?? { x: 0, y: 0, w: this.width, h: this.height },
|
|
2726
|
+
);
|
|
2720
2727
|
if (rect) this._invalidatePictureClip();
|
|
2721
2728
|
this._markDirty();
|
|
2722
2729
|
return;
|
|
@@ -2737,6 +2744,7 @@ class RenderingContext2d {
|
|
|
2737
2744
|
this._glyphSource.id,
|
|
2738
2745
|
this.fillMask.id,
|
|
2739
2746
|
positioned,
|
|
2747
|
+
{ x: 0, y: 0, w: this.width, h: this.height },
|
|
2740
2748
|
);
|
|
2741
2749
|
R.Composite(
|
|
2742
2750
|
R.PictOp.In,
|
package/lib/text/font.js
CHANGED
|
@@ -75,6 +75,12 @@ export default class Font {
|
|
|
75
75
|
this.path = path;
|
|
76
76
|
/** stable cache key for glyph pages and font caches */
|
|
77
77
|
this.key = `${path}#${postscriptName || fkFont.postscriptName || ''}`;
|
|
78
|
+
// What the face's tables say, read once. fontkit's getters walk to the
|
|
79
|
+
// table on every read, and a text layout asks per run and per word: in
|
|
80
|
+
// a long document they were most of a relayout (see `metrics`).
|
|
81
|
+
this._upem = 0;
|
|
82
|
+
this._vertical = null;
|
|
83
|
+
this._space = -1;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
static loadSync(path, postscriptName) {
|
|
@@ -214,28 +220,48 @@ export default class Font {
|
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
scale(size) {
|
|
217
|
-
return size / this.fk.unitsPerEm;
|
|
223
|
+
return size / (this._upem ||= this.fk.unitsPerEm);
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
/**
|
|
221
227
|
* Pixel-space metrics for a font size. `ascent`/`capHeight`/`xHeight` are
|
|
222
228
|
* positive (above baseline), `descent` is positive (below baseline).
|
|
229
|
+
*
|
|
230
|
+
* A new object every call, which a caller may keep or change; the numbers
|
|
231
|
+
* behind it are the face's, read from its tables once.
|
|
223
232
|
*/
|
|
224
233
|
metrics(size) {
|
|
234
|
+
const v = (this._vertical ??= {
|
|
235
|
+
ascent: this.fk.ascent,
|
|
236
|
+
descent: -this.fk.descent,
|
|
237
|
+
lineGap: this.fk.lineGap,
|
|
238
|
+
capHeight: this.fk.capHeight,
|
|
239
|
+
xHeight: this.fk.xHeight
|
|
240
|
+
});
|
|
225
241
|
const s = this.scale(size);
|
|
226
|
-
const ascent =
|
|
227
|
-
const descent =
|
|
228
|
-
const lineGap =
|
|
242
|
+
const ascent = v.ascent * s;
|
|
243
|
+
const descent = v.descent * s;
|
|
244
|
+
const lineGap = v.lineGap * s;
|
|
229
245
|
return {
|
|
230
246
|
ascent,
|
|
231
247
|
descent,
|
|
232
248
|
lineGap,
|
|
233
249
|
lineHeight: ascent + descent + lineGap,
|
|
234
|
-
capHeight:
|
|
235
|
-
xHeight:
|
|
250
|
+
capHeight: v.capHeight * s,
|
|
251
|
+
xHeight: v.xHeight * s
|
|
236
252
|
};
|
|
237
253
|
}
|
|
238
254
|
|
|
255
|
+
/**
|
|
256
|
+
* The advance of U+0020 at `size` — what a run of trailing spaces is
|
|
257
|
+
* worth to a line. Looked up once: it was a cmap lookup for every word a
|
|
258
|
+
* layout ended with a space, which in a paragraph is every word.
|
|
259
|
+
*/
|
|
260
|
+
spaceAdvance(size) {
|
|
261
|
+
if (this._space < 0) this._space = this.fk.glyphForCodePoint(0x20).advanceWidth;
|
|
262
|
+
return this._space * this.scale(size);
|
|
263
|
+
}
|
|
264
|
+
|
|
239
265
|
hasGlyph(codepoint) {
|
|
240
266
|
return this.fk.hasGlyphForCodePoint(codepoint);
|
|
241
267
|
}
|
package/lib/text/glyphs.js
CHANGED
|
@@ -571,8 +571,11 @@ export function routeGlyphSize(app, font, size, policy = policyOf(app), textRend
|
|
|
571
571
|
* @param {number} srcId source picture (fill color/gradient)
|
|
572
572
|
* @param {number} dstId destination picture
|
|
573
573
|
* @param {Array<{run, x, y}>} positioned visual-order runs with baseline origins
|
|
574
|
+
* @param {{x: number, y: number, w: number, h: number}} [bounds] the part of
|
|
575
|
+
* the destination that can show anything — the clip, or the surface. A
|
|
576
|
+
* glyph wholly outside it is not sent (`visibleGlyph`).
|
|
574
577
|
*/
|
|
575
|
-
export function drawGlyphRuns(app, op, srcId, dstId, positioned) {
|
|
578
|
+
export function drawGlyphRuns(app, op, srcId, dstId, positioned, bounds = null) {
|
|
576
579
|
const policy = policyOf(app);
|
|
577
580
|
let bitmap = positioned;
|
|
578
581
|
let vector = null;
|
|
@@ -590,11 +593,35 @@ export function drawGlyphRuns(app, op, srcId, dstId, positioned) {
|
|
|
590
593
|
bitmap.push(positioned[i]);
|
|
591
594
|
}
|
|
592
595
|
}
|
|
593
|
-
if (bitmap.length) drawBitmapGlyphRuns(app, op, srcId, dstId, bitmap, policy);
|
|
594
|
-
if (vector) drawVectorGlyphRuns(app, op, srcId, dstId, vector);
|
|
596
|
+
if (bitmap.length) drawBitmapGlyphRuns(app, op, srcId, dstId, bitmap, policy, bounds);
|
|
597
|
+
if (vector) drawVectorGlyphRuns(app, op, srcId, dstId, vector, bounds);
|
|
595
598
|
}
|
|
596
599
|
|
|
597
|
-
|
|
600
|
+
/**
|
|
601
|
+
* Whether a glyph whose origin is at (x, y) can put any ink inside `bounds`,
|
|
602
|
+
* give or take two ems — no glyph reaches further from its origin than that,
|
|
603
|
+
* swashes and all.
|
|
604
|
+
*
|
|
605
|
+
* Not only a saving: the glyphs of a line wider than 32767 device pixels —
|
|
606
|
+
* a minified file in a code editor, a long label in a narrow cell — have
|
|
607
|
+
* origins no request can carry. CompositeGlyphs places its first glyph and
|
|
608
|
+
* every element after it in 16-bit coordinates, and the vector path's trap
|
|
609
|
+
* mask would span the whole line, so drawing such a line threw a RangeError
|
|
610
|
+
* out of the paint however little of it was on screen. What is left after
|
|
611
|
+
* the cull sits inside a surface, and every coordinate in it fits.
|
|
612
|
+
*/
|
|
613
|
+
function visibleGlyph(bounds, x, y, size) {
|
|
614
|
+
if (bounds === null) return true;
|
|
615
|
+
const reach = size * 2;
|
|
616
|
+
return (
|
|
617
|
+
x + reach > bounds.x &&
|
|
618
|
+
x - reach < bounds.x + bounds.w &&
|
|
619
|
+
y + reach > bounds.y &&
|
|
620
|
+
y - reach < bounds.y + bounds.h
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function drawBitmapGlyphRuns(app, op, srcId, dstId, positioned, policy, bounds) {
|
|
598
625
|
const Render = app.display.Render;
|
|
599
626
|
const items = [];
|
|
600
627
|
let bits = 8;
|
|
@@ -608,6 +635,7 @@ function drawBitmapGlyphRuns(app, op, srcId, dstId, positioned, policy) {
|
|
|
608
635
|
if (page.bits > bits) bits = page.bits;
|
|
609
636
|
}
|
|
610
637
|
for (const pos of positionGlyphs(positioned)) {
|
|
638
|
+
if (!visibleGlyph(bounds, pos.x, pos.y, pos.run.size)) continue;
|
|
611
639
|
const page = pages.get(pos.run);
|
|
612
640
|
const e = page.entry(pos.glyph.id);
|
|
613
641
|
items.push({ gs: e.gs, lid: e.lid, adv: e.adv, x: pos.x, y: pos.y });
|
|
@@ -646,7 +674,7 @@ const MAX_TRAPS_PER_REQUEST = 6000;
|
|
|
646
674
|
* Positions are intentionally NOT rounded to whole pixels — fractional
|
|
647
675
|
* advances and origins keep zoom animations smooth.
|
|
648
676
|
*/
|
|
649
|
-
function drawVectorGlyphRuns(app, op, srcId, dstId, positioned) {
|
|
677
|
+
function drawVectorGlyphRuns(app, op, srcId, dstId, positioned, bounds = null) {
|
|
650
678
|
// unrounded glyph origins + ink bounding box
|
|
651
679
|
const placed = [];
|
|
652
680
|
let minX = Infinity;
|
|
@@ -657,6 +685,7 @@ function drawVectorGlyphRuns(app, op, srcId, dstId, positioned) {
|
|
|
657
685
|
const gx = cursor + g.dx;
|
|
658
686
|
const gy = y - g.dy;
|
|
659
687
|
cursor += g.ax;
|
|
688
|
+
if (!visibleGlyph(bounds, gx, gy, run.size)) continue;
|
|
660
689
|
const e = run.font.glyphExtents(g.id, run.size);
|
|
661
690
|
if (!Number.isFinite(e.minX) || e.maxX <= e.minX) continue; // blank glyph
|
|
662
691
|
placed.push({ run, glyph: g, x: gx, y: gy });
|
package/lib/text/layout.js
CHANGED
|
@@ -324,10 +324,8 @@ export class TextLayout {
|
|
|
324
324
|
if (last) {
|
|
325
325
|
const m = TRAILING_WS.exec(last.text);
|
|
326
326
|
if (m) {
|
|
327
|
-
const spaceGlyph = last.span.font.fk.glyphForCodePoint(0x20);
|
|
328
327
|
const spacing = Number.isFinite(last.span.letterSpacing) ? last.span.letterSpacing : 0;
|
|
329
|
-
wsWidth =
|
|
330
|
-
m[0].length * (spaceGlyph.advanceWidth * last.span.font.scale(last.span.size) + spacing);
|
|
328
|
+
wsWidth = m[0].length * (last.span.font.spaceAdvance(last.span.size) + spacing);
|
|
331
329
|
}
|
|
332
330
|
}
|
|
333
331
|
return { fragments, width, wsWidth, required, start, end };
|
package/lib/text/shape.js
CHANGED
|
@@ -25,9 +25,29 @@ function neverSwitches(cp) {
|
|
|
25
25
|
*/
|
|
26
26
|
export function embeddingLevels(text, direction = 'auto') {
|
|
27
27
|
const explicit = direction === 'ltr' || direction === 'rtl' ? direction : null;
|
|
28
|
+
// Text that nothing can make right-to-left is level 0 throughout, which
|
|
29
|
+
// is what the algorithm would work out a character at a time: every
|
|
30
|
+
// layout asks, and most text is Latin, CJK or digits with no bidi at all.
|
|
31
|
+
if (explicit !== 'rtl' && !MAY_REVERSE.test(text)) {
|
|
32
|
+
return {
|
|
33
|
+
levels: new Uint8Array(text.length),
|
|
34
|
+
paragraphs: text.length ? [{ start: 0, end: text.length - 1, level: 0 }] : []
|
|
35
|
+
};
|
|
36
|
+
}
|
|
28
37
|
return bidi.getEmbeddingLevels(text, explicit);
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
/**
|
|
41
|
+
* What can raise a level in a left-to-right paragraph: a strong
|
|
42
|
+
* right-to-left character (Hebrew, Arabic, Syriac, Thaana, N'Ko and their
|
|
43
|
+
* neighbours, their presentation forms, and the right-to-left blocks of the
|
|
44
|
+
* supplementary planes, found by their high surrogates), an Arabic number,
|
|
45
|
+
* RLM and ALM, and every explicit embedding, override and isolate. Anything
|
|
46
|
+
* else resolves to 0.
|
|
47
|
+
*/
|
|
48
|
+
const MAY_REVERSE =
|
|
49
|
+
/[\u0590-\u08FF\u200F\u202A-\u202E\u2066-\u2069\uFB1D-\uFDFF\uFE70-\uFEFF]|[\uD802\uD803\uD83A\uD83B]/;
|
|
50
|
+
|
|
31
51
|
/**
|
|
32
52
|
* Compact embedding-levels encoding for the shaping memo key: a single
|
|
33
53
|
* number when uniform over [start, end) — the common case — else
|