ntk 8.15.0 → 8.15.1

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.
Files changed (2) hide show
  1. package/lib/text/layout.js +84 -11
  2. package/package.json +1 -1
@@ -25,15 +25,88 @@ const UNSPACED =
25
25
  * 12.8px measures 410.0125px in the 410px column it was written for — and
26
26
  * a browser takes the line as fitting, comparing in 64ths of a pixel with
27
27
  * that much to spare (Blink's line breaker adds a LayoutUnit's epsilon to
28
- * the width it fits to).
28
+ * the width it fits to). What it compares is the line's items, each
29
+ * rounded up to a 64th first (`fittedWidth`).
29
30
  */
30
31
  const FIT_SLACK = 1 / 64;
31
32
 
32
- /** How wide a line's tokens are, the white space it ends on left out. */
33
- function tokensWidth(toks) {
34
- let width = 0;
35
- for (let i = 0; i < toks.length; i++) width += toks[i].width + (i ? kernBefore(toks[i]) : 0);
36
- return toks.length ? width - toks[toks.length - 1].wsWidth : 0;
33
+ /** A width rounded up to a 64th of a pixel, as Blink rounds an item's
34
+ * shaped width up to a LayoutUnit (`ShapeResult::SnappedWidth`). The
35
+ * nudge keeps a sum that lands on a 64th, give or take the last bit of a
36
+ * double, where it lands. */
37
+ function snapUp(width) {
38
+ return Math.ceil(width * 64 - 1e-7) / 64;
39
+ }
40
+
41
+ /**
42
+ * A line's width as a browser fits it: its spans' parts of it each rounded
43
+ * up to a 64th of a pixel and summed, its trailing white space left out.
44
+ *
45
+ * A browser measures a line in items — the text of one element between its
46
+ * edges, shaped — and rounds each one's width up to a LayoutUnit before it
47
+ * adds it (Blink's `ShapeResult::SnappedWidth`, which every text item's
48
+ * `inline_size` on a line is), so a line of three items is up to three 64ths
49
+ * wider than its advances. Compared with the width and its 64th to spare
50
+ * (`FIT_SLACK`), a line of Verdana 0.002px past its 529px, the `<abbr>` in
51
+ * it making three items, is 2 64ths past and breaks, as it does in Chrome;
52
+ * a line that is one item and a hair over still fits. A span is an item,
53
+ * and so is its part of a line; a `kernAcross` span is not one of its own
54
+ * — it is a space a justified line or `word-spacing` spaces out, which a
55
+ * browser spaces inside the item — and is measured with the span before it.
56
+ *
57
+ * Kept a token at a time: `add` puts one on the line, `with` is the line's
58
+ * width were the next one put on it.
59
+ */
60
+ class LineFit {
61
+ constructor() {
62
+ this.reset();
63
+ }
64
+
65
+ reset() {
66
+ /** the items closed on the line, each rounded up */
67
+ this.closed = 0;
68
+ /** the width of the item the line ends in, as far as it goes */
69
+ this.open = 0;
70
+ this.span = null;
71
+ }
72
+
73
+ add(token, kern) {
74
+ const frags = token.fragments;
75
+ for (let i = 0; i < frags.length; i++) {
76
+ const frag = frags[i];
77
+ const width = frag.shaped.width + (frag.kern ?? 0) + (i === 0 ? kern : 0);
78
+ if (this.span === null || this.span === frag.span || frag.span.kernAcross || this.span.kernAcross) {
79
+ this.open += width;
80
+ } else {
81
+ this.closed += snapUp(this.open);
82
+ this.open = width;
83
+ }
84
+ this.span = frag.span;
85
+ }
86
+ // a token of no text, a forced break's, adds nothing but its kerning
87
+ if (!frags.length) this.open += kern;
88
+ }
89
+
90
+ /** The line's width with `token` on it, the white space it ends on left
91
+ * out. */
92
+ with(token, kern) {
93
+ const { closed, open, span } = this;
94
+ this.add(token, kern);
95
+ const width = this.closed + snapUp(this.open - token.wsWidth);
96
+ this.closed = closed;
97
+ this.open = open;
98
+ this.span = span;
99
+ return width;
100
+ }
101
+ }
102
+
103
+ /** A line's tokens' width as a browser fits it (`LineFit`). */
104
+ function fittedWidth(toks) {
105
+ if (!toks.length) return 0;
106
+ const fit = new LineFit();
107
+ for (let i = 0; i < toks.length - 1; i++) fit.add(toks[i], i ? kernBefore(toks[i]) : 0);
108
+ const last = toks.length - 1;
109
+ return fit.with(toks[last], last ? kernBefore(toks[last]) : 0);
37
110
  }
38
111
 
39
112
  /** The kerning between a token and the one before it on its line
@@ -136,16 +209,16 @@ export class TextLayout {
136
209
  const wraps = options.wrap !== false;
137
210
  {
138
211
  let cur = [];
139
- let curWidth = 0;
212
+ const fit = new LineFit();
140
213
  const flush = () => {
141
214
  lineTokens.push(cur);
142
215
  cur = [];
143
- curWidth = 0;
216
+ fit.reset();
144
217
  };
145
218
  for (let token of tokens) {
146
219
  while (
147
220
  wraps &&
148
- curWidth + (cur.length ? kernBefore(token) : 0) + token.width - token.wsWidth > maxWidth + FIT_SLACK
221
+ fit.with(token, cur.length ? kernBefore(token) : 0) > maxWidth + FIT_SLACK
149
222
  ) {
150
223
  if (cur.length > 0) {
151
224
  flush();
@@ -160,7 +233,7 @@ export class TextLayout {
160
233
  token = rest;
161
234
  }
162
235
  }
163
- curWidth += token.width + (cur.length ? kernBefore(token) : 0);
236
+ fit.add(token, cur.length ? kernBefore(token) : 0);
164
237
  cur.push(token);
165
238
  if (token.required) flush();
166
239
  }
@@ -199,7 +272,7 @@ export class TextLayout {
199
272
  // Elision is the last kept line's business, or a line's that does not
200
273
  // wrap and runs past the width, and it happens before entries exist,
201
274
  // because dropping content means re-shaping the tail.
202
- const cut = (elide && li === lineTokens.length - 1) || (cutWide && tokensWidth(toks) > maxWidth + FIT_SLACK);
275
+ const cut = (elide && li === lineTokens.length - 1) || (cutWide && fittedWidth(toks) > maxWidth + FIT_SLACK);
203
276
  const ellipsis = cut ? this._elide(toks, baseSpan) : null;
204
277
  if (ellipsis) {
205
278
  toks = this._fitBefore(toks, maxWidth - ellipsis.width);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "8.15.0",
3
+ "version": "8.15.1",
4
4
  "description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
5
5
  "author": "Andrey Sidorov <sidorares@yandex.ru>",
6
6
  "license": "MIT",