ntk 8.16.1 → 8.17.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/text/font.js CHANGED
@@ -134,9 +134,45 @@ function statedOrGlyph(fk, field, codePoint) {
134
134
  }
135
135
  }
136
136
 
137
+ /**
138
+ * fontkit's TrueType glyph reads its box from the glyph's header without
139
+ * asking whether it has one: `_getCBox` decodes a header where the glyph's
140
+ * data starts, and an empty glyph — a space, or one a subsetter left with
141
+ * no outline — has no data, where `_decode` beside it says so and draws
142
+ * nothing. Mid-table it read the next glyph's box. At the table's end it
143
+ * read past it, and in a WOFF, whose tables are each their own buffer, that
144
+ * threw: a face from fonts.com ends its glyph table on four empty glyphs,
145
+ * the space among them, and every layout in it threw, which left a whole
146
+ * document blank. An empty glyph's box is its empty outline's, as the rest
147
+ * of fontkit has it. Installed once, on the class every TrueType glyph is.
148
+ */
149
+ const MENDED = new WeakSet();
150
+
151
+ function mendEmptyGlyphBoxes(fk) {
152
+ if (!fk.directory?.tables?.glyf) return;
153
+ let glyph;
154
+ try {
155
+ glyph = fk.getGlyph(0);
156
+ } catch {
157
+ return;
158
+ }
159
+ const proto = Object.getPrototypeOf(glyph);
160
+ if (!proto || MENDED.has(proto)) return;
161
+ MENDED.add(proto);
162
+ const own = proto._getCBox;
163
+ const outline = Object.getPrototypeOf(proto)?._getCBox;
164
+ if (typeof own !== 'function' || typeof outline !== 'function') return;
165
+ proto._getCBox = function (internal) {
166
+ const offsets = this._font.loca?.offsets;
167
+ if (offsets && offsets[this.id] === offsets[this.id + 1]) return outline.call(this, internal);
168
+ return own.call(this, internal);
169
+ };
170
+ }
171
+
137
172
  export default class Font {
138
173
  constructor(fkFont, path, postscriptName) {
139
174
  answerWithCharacters(fkFont);
175
+ mendEmptyGlyphBoxes(fkFont);
140
176
  this.fk = fkFont;
141
177
  this.path = path;
142
178
  /** stable cache key for glyph pages and font caches */
@@ -110,6 +110,30 @@ function sameItem(before, span) {
110
110
  return before === span || span.kernAcross || before.kernAcross;
111
111
  }
112
112
 
113
+ /**
114
+ * The width a line's entries take as the line is fitted, white space and
115
+ * all: each item's part — a span's, or a run of them `sameItem` joins —
116
+ * rounded as the layout rounds (`LineFit`), with its kerning. A line's
117
+ * `advance`: where the text goes on after the line on the same one, as it
118
+ * does after a piece of a line composed a piece at a time, the white space
119
+ * it ends on is inside the line, and in the item it ends, which is rounded
120
+ * once with it, as Blink measures an element's text with its spaces.
121
+ */
122
+ function fittedEntries(entries, round) {
123
+ let fitted = 0;
124
+ let item = 0;
125
+ let span = null;
126
+ for (const e of entries) {
127
+ if (span !== null && !sameItem(span, e.span)) {
128
+ fitted += round(item);
129
+ item = 0;
130
+ }
131
+ item += (e.kern ?? 0) + e.run.width + (e.kernAfter ?? 0);
132
+ span = e.span;
133
+ }
134
+ return entries.length ? fitted + round(item) : 0;
135
+ }
136
+
113
137
  /** How a layout rounds its items before it adds them (`LineFit`). */
114
138
  function itemRounding(options) {
115
139
  return options.fit === 'items' ? snapUp : unrounded;
@@ -187,12 +211,15 @@ function isWsGlyph(g) {
187
211
  * is that sum. Default: the advances as they add up (`LineFit`)
188
212
  *
189
213
  * The result is inspectable before/without drawing: `width`, `height`,
190
- * `truncated`, and `lines[] = { x, y, height, baseline, width, ascent,
191
- * descent, runs, start, end }` with
214
+ * `truncated`, and `lines[] = { x, y, height, baseline, width, advance,
215
+ * ascent, descent, runs, start, end }` with
192
216
  * `runs[] = { x, width, run, span, start, end }` in visual order
193
217
  * (`start`/`end` are logical UTF-16 ranges into the full text). A line's
194
218
  * box is `y` to `y + height`; its glyphs sit centred in it, from
195
- * `baseline - ascent` to `baseline + descent`.
219
+ * `baseline - ascent` to `baseline + descent`. Its `width` leaves out the
220
+ * white space it ends on, as a line's end does; its `advance` keeps it, in
221
+ * the item it ends, which is how far the line moves the pen where text goes
222
+ * on after it on the same line.
196
223
  *
197
224
  * `caretPosition(index)` / `indexAt(x, y)` map logical code-point indices
198
225
  * to visual caret geometry and back (bidi/ligature/trailing-whitespace
@@ -334,6 +361,9 @@ export class TextLayout {
334
361
  }
335
362
  }
336
363
  }
364
+ // how far the line moves the pen where text goes on after it on the
365
+ // same line: its trailing white space kept, in the item it ends
366
+ let advance = fittedEntries(entries, round);
337
367
  let trailing = stripTrailingWhitespace(entries);
338
368
  const contentEnd = trailing
339
369
  ? trailing.start
@@ -363,6 +393,7 @@ export class TextLayout {
363
393
  // whatever whitespace was stripped is gone for good, not merely
364
394
  // pushed past the line edge: there is no wrap for it to precede
365
395
  trailing = null;
396
+ advance = null;
366
397
  lineEnd = contentEnd;
367
398
  }
368
399
  entries = reorderRuns(entries);
@@ -424,6 +455,7 @@ export class TextLayout {
424
455
  height: box,
425
456
  baseline: y + leading + ascent,
426
457
  width: fitted,
458
+ advance: trailing && advance !== null ? Math.max(fitted, advance) : fitted,
427
459
  ascent,
428
460
  descent,
429
461
  runs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "8.16.1",
3
+ "version": "8.17.0",
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",