pretrunc 0.0.0-stage → 0.1.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/DESIGN.md +235 -0
  3. package/LICENSE +21 -0
  4. package/README.md +200 -2
  5. package/dist/cut-D7qi2yZl.js +356 -0
  6. package/dist/cut-D7qi2yZl.js.map +1 -0
  7. package/dist/cut.d.ts +75 -0
  8. package/dist/dom-CPL0-qc3.js +52 -0
  9. package/dist/dom-CPL0-qc3.js.map +1 -0
  10. package/dist/dom.d.ts +22 -0
  11. package/dist/dom.js +1 -0
  12. package/dist/face-pICHAjvk.js +68 -0
  13. package/dist/face-pICHAjvk.js.map +1 -0
  14. package/dist/face.d.ts +46 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +3 -0
  17. package/dist/lines-DOj2DQLl.js +334 -0
  18. package/dist/lines-DOj2DQLl.js.map +1 -0
  19. package/dist/lines.d.ts +32 -0
  20. package/dist/lines.js +1 -0
  21. package/dist/metrics-C2jv4JVL.js +49 -0
  22. package/dist/metrics-C2jv4JVL.js.map +1 -0
  23. package/dist/metrics-cli.js +19 -0
  24. package/dist/metrics-cli.js.map +1 -0
  25. package/dist/metrics.d.ts +36 -0
  26. package/dist/metrics.js +73 -0
  27. package/dist/metrics.js.map +1 -0
  28. package/dist/react-CnQfjWyS.js +156 -0
  29. package/dist/react-CnQfjWyS.js.map +1 -0
  30. package/dist/react-lines.d.ts +27 -0
  31. package/dist/react-lines.js +74 -0
  32. package/dist/react-lines.js.map +1 -0
  33. package/dist/react.d.ts +81 -0
  34. package/dist/react.js +1 -0
  35. package/dist/solid-BWC32VMg.js +155 -0
  36. package/dist/solid-BWC32VMg.js.map +1 -0
  37. package/dist/solid-lines.d.ts +27 -0
  38. package/dist/solid-lines.js +90 -0
  39. package/dist/solid-lines.js.map +1 -0
  40. package/dist/solid.d.ts +77 -0
  41. package/dist/solid.js +1 -0
  42. package/package.json +145 -3
  43. package/src/cut.ts +596 -0
  44. package/src/dom.ts +59 -0
  45. package/src/face.ts +112 -0
  46. package/src/index.ts +15 -0
  47. package/src/lines.ts +585 -0
  48. package/src/metrics.ts +106 -0
  49. package/src/react-lines.ts +99 -0
  50. package/src/react.ts +251 -0
  51. package/src/solid-lines.tsx +89 -0
  52. package/src/solid.tsx +225 -0
@@ -0,0 +1,356 @@
1
+ import { c as widthOf, i as faceOf, o as pairOf, s as unitOf } from "./face-pICHAjvk.js";
2
+ //#region src/cut.ts
3
+ var SHIFT = 2 ** 26;
4
+ var SLACK = 1 / 64;
5
+ var segmenter;
6
+ var flags;
7
+ var identity = [0];
8
+ var none = [];
9
+ /** A cut from its two offsets; for pretrunc's own entries, not re-exported. */
10
+ var pack = (head, tail) => head * SHIFT + tail;
11
+ var head = (c) => Math.floor(c / SHIFT);
12
+ var tail = (c) => c % SHIFT;
13
+ var truncated = (c) => head(c) < tail(c);
14
+ var toText = (p, c, ellipsis = "…") => truncated(c) ? p.text.slice(0, head(c)) + ellipsis + p.text.slice(tail(c)) : p.text;
15
+ var breaks = /[\s\-/_.…–—⺀-￿]/;
16
+ var trims = /[\s,;:]/;
17
+ var across = (kerning, broken) => broken ? Math.max(0, kerning) : kerning;
18
+ var prepare = (text, font, options = {}) => build(/[\t\n\f\r]| {2}|^ | $/.test(text) ? text.replace(/[\t\n\f\r ]+/g, " ").replace(/^ | $/g, "") : text, typeof font === "string" ? faceOf(font) : font, options);
19
+ /**
20
+ * Prepares `full.slice(from, to)` as it stands, white space kept, with protected
21
+ * matches found in all of `full`, and a tab `tab` px wide when that's given: the most
22
+ * a tab stop can take. For pretrunc's own entries.
23
+ */
24
+ var prepareRange = (full, from, to, font, options, tab = 0) => build(full.slice(from, to), faceOf(font), options, full, from, tab);
25
+ var build = (t, f, options, full = t, from = 0, tab = 0) => {
26
+ const spacing = options.letterSpacing ?? 0;
27
+ const simple = !/[^\0-\u02ff]/.test(t);
28
+ let b = identity;
29
+ if (simple) while (b.length <= t.length) b.push(b.length);
30
+ else {
31
+ b = Array.from((segmenter ??= new Intl.Segmenter()).segment(t), (g) => g.index);
32
+ b.push(t.length);
33
+ }
34
+ const n = simple ? t.length : b.length - 1;
35
+ const w = new Float64Array(n + 1);
36
+ const d = new Float64Array(n + 1);
37
+ const a = new Float64Array(n);
38
+ const c = new Uint8Array(n);
39
+ let x = true;
40
+ let word = 0;
41
+ const check = (end) => {
42
+ if (x && end - word > 1) {
43
+ const sum = w[end] - d[end] - w[word + 1] + d[word + 1] + a[word] - spacing * (b[end] - b[word]);
44
+ x = Math.abs(widthOf(f, t.slice(b[word], b[end])) - sum) < .01;
45
+ }
46
+ };
47
+ if (!flags) {
48
+ flags = /* @__PURE__ */ new Uint8Array(768);
49
+ for (let code = 0; code < 768; code++) {
50
+ const ch = String.fromCharCode(code);
51
+ flags[code] = +breaks.test(ch) | +trims.test(ch) << 1;
52
+ }
53
+ }
54
+ let previous = "";
55
+ let before = 0;
56
+ for (let i = 0; i < n; i++) {
57
+ let own;
58
+ let kerning = 0;
59
+ let space = false;
60
+ if (simple) {
61
+ const code = t.charCodeAt(i);
62
+ own = tab && code === 9 ? tab : unitOf(f, code);
63
+ c[i] = flags[code];
64
+ if (i && !(tab && (code === 9 || t.charCodeAt(i - 1) === 9))) kerning = pairOf(f, t.charCodeAt(i - 1), code) - before - own;
65
+ space = code === 32;
66
+ } else {
67
+ const g = t.slice(b[i], b[i + 1]);
68
+ own = tab && g === " " ? tab : widthOf(f, g);
69
+ c[i] = +breaks.test(g) | +trims.test(g) << 1;
70
+ if (i && !(tab && (g === " " || previous === " "))) kerning = widthOf(f, previous + g) - before - own;
71
+ space = g === " ";
72
+ previous = g;
73
+ }
74
+ const spaced = spacing * (b[i + 1] - b[i]);
75
+ a[i] = own + spaced;
76
+ const counted = i ? across(kerning, spacing || c[i - 1] & 1) : 0;
77
+ w[i + 1] = w[i] + Math.max(0, own + counted) + spaced;
78
+ d[i + 1] = d[i] + counted - kerning;
79
+ if (space) {
80
+ check(i);
81
+ word = i + 1;
82
+ }
83
+ before = own;
84
+ }
85
+ check(n);
86
+ const g = tab && tab - widthOf(f, " ");
87
+ const y = x ? w[n] : f.m(t) + spacing * t.length + g * (t.split(" ").length - 1) + d[n];
88
+ let k = none;
89
+ let q = none;
90
+ const { protect } = options;
91
+ if (protect) {
92
+ const at = (o, back) => {
93
+ const i = Math.min(Math.max(o, 0), t.length);
94
+ if (simple) return i;
95
+ let lo = 0;
96
+ let hi = n;
97
+ while (lo < hi) {
98
+ const mid = lo + hi >> 1;
99
+ if (b[mid] < i) lo = mid + 1;
100
+ else hi = mid;
101
+ }
102
+ return back && b[lo] > i ? lo - 1 : lo;
103
+ };
104
+ const spans = [];
105
+ k = [];
106
+ const list = "length" in protect ? protect : [protect];
107
+ for (let i = 0; i < list.length; i++) {
108
+ const given = list[i];
109
+ const entry = given instanceof RegExp ? { pattern: given } : given;
110
+ const { pattern } = entry;
111
+ for (const match of full.matchAll(pattern.global ? pattern : new RegExp(pattern, pattern.flags + "g"))) {
112
+ const start = match.index - from;
113
+ const end = start + match[0].length;
114
+ if (start < t.length && end > 0 && end > start) {
115
+ const s = at(start, true);
116
+ const e = at(end, false);
117
+ if (entry.whole) k.push(s, end > t.length ? n + 1 : e);
118
+ spans.push([
119
+ -(entry.weight ?? 1),
120
+ i,
121
+ s,
122
+ e,
123
+ +!!entry.whole
124
+ ]);
125
+ }
126
+ }
127
+ }
128
+ spans.sort((x, y) => x[0] - y[0]);
129
+ q = [];
130
+ for (const x of spans) q.push(x[2], x[3], x[4]);
131
+ }
132
+ return {
133
+ text: t,
134
+ f,
135
+ n,
136
+ b,
137
+ w,
138
+ d,
139
+ a,
140
+ c,
141
+ x,
142
+ y,
143
+ k,
144
+ q,
145
+ u: simple,
146
+ s: spacing,
147
+ g,
148
+ j: void 0,
149
+ v: 0,
150
+ z: n
151
+ };
152
+ };
153
+ var P;
154
+ var N = 0;
155
+ var O = 0;
156
+ var room = 0;
157
+ var E = "";
158
+ var EW = 0;
159
+ var J;
160
+ var at = "end";
161
+ var grapheme = (i) => P.text.slice(P.b[i], P.b[i + 1]);
162
+ var headWidth = (h) => h > O ? P.w[h] - P.w[O + 1] + P.a[O] : 0;
163
+ var tailWidth = (t) => t < N ? P.w[N] - P.w[t + 1] + P.a[t] : 0;
164
+ var isBreak = (i) => i > O && i < N && ((P.c[i - 1] | P.c[i]) & 1) !== 0;
165
+ var inside = (i) => {
166
+ const k = P.k;
167
+ for (let j = 0; j < k.length; j += 2) if (k[j] < i && i < k[j + 1]) return true;
168
+ return false;
169
+ };
170
+ var kern = (left, right) => widthOf(P.f, left + right) - widthOf(P.f, left) - widthOf(P.f, right);
171
+ var beside = (i, side) => {
172
+ const at = P.u ? side * 768 + P.text.charCodeAt(i) : side * N + i;
173
+ let kerning = J[at];
174
+ if (kerning !== kerning) {
175
+ kerning = side ? kern(E, grapheme(i)) : kern(grapheme(i), E);
176
+ J[at] = kerning;
177
+ }
178
+ return kerning;
179
+ };
180
+ var maxHead = (r) => {
181
+ let lo = O;
182
+ let hi = N;
183
+ let i = Math.max(P.v, O);
184
+ for (let step = 1; lo < hi; step *= 2) if (i === lo || headWidth(i) <= r) {
185
+ lo = i;
186
+ i += step;
187
+ if (i > hi) break;
188
+ } else {
189
+ hi = i - 1;
190
+ i -= step;
191
+ if (i < lo) break;
192
+ }
193
+ while (lo < hi) {
194
+ const mid = lo + hi + 1 >> 1;
195
+ if (headWidth(mid) <= r) lo = mid;
196
+ else hi = mid - 1;
197
+ }
198
+ return P.v = lo;
199
+ };
200
+ var minTail = (r) => {
201
+ let lo = 0;
202
+ let hi = N;
203
+ let i = P.z;
204
+ for (let step = 1; lo < hi; step *= 2) if (i === hi || tailWidth(i) <= r) {
205
+ hi = i;
206
+ i -= step;
207
+ if (i < lo) break;
208
+ } else {
209
+ lo = i + 1;
210
+ i += step;
211
+ if (i > hi) break;
212
+ }
213
+ while (lo < hi) {
214
+ const mid = lo + hi >> 1;
215
+ if (tailWidth(mid) <= r) hi = mid;
216
+ else lo = mid + 1;
217
+ }
218
+ return P.z = lo;
219
+ };
220
+ var measure = (h, t) => {
221
+ const { text, f, b, d, c, x } = P;
222
+ let painted = headWidth(h) + EW + tailWidth(t);
223
+ if (!x) {
224
+ const before = text.slice(b[O], b[h]);
225
+ const after = text.slice(b[t]);
226
+ painted = J ? f.m(before + E + after) + EW - widthOf(f, E) : f.m(before) + EW + f.m(after);
227
+ painted += P.s * (b[h] - b[O] + b[N] - b[t]);
228
+ if (P.g) painted += P.g * ((before + after).split(" ").length - 1);
229
+ if (h > O) painted += d[h] - d[O + 1];
230
+ if (t < N) painted += d[N] - d[t + 1];
231
+ }
232
+ if (J && h > O) {
233
+ const kerning = beside(h - 1, 0);
234
+ painted += across(kerning, P.s || c[h - 1] & 1) - (x ? 0 : kerning);
235
+ }
236
+ if (J && t < N) {
237
+ const kerning = beside(t, 1);
238
+ painted += across(kerning, 1) - (x ? 0 : kerning);
239
+ }
240
+ return painted;
241
+ };
242
+ var headSide = (h) => headWidth(h) + (J && h > O ? across(beside(h - 1, 0), P.s || P.c[h - 1] & 1) : 0);
243
+ var tailSide = (t) => tailWidth(t) + (J && t < N ? across(beside(t, 1), 1) : 0);
244
+ var fitHead = (budget) => {
245
+ let i = maxHead(budget);
246
+ while (i > O && headSide(i) > budget) i--;
247
+ while (i < N && headSide(i + 1) <= budget) i++;
248
+ return i;
249
+ };
250
+ var fitTail = (budget) => {
251
+ let i = minTail(budget);
252
+ while (i < N && tailSide(i) > budget) i++;
253
+ while (i > 0 && tailSide(i - 1) <= budget) i--;
254
+ return i;
255
+ };
256
+ var h = 0;
257
+ var t = 0;
258
+ var shrink = () => {
259
+ while ((h > O || t < N) && measure(h, t) > room) if (at === "end" || at === "middle" && h > N - t) h--;
260
+ else t++;
261
+ };
262
+ /**
263
+ * `cut`, keeping the text before grapheme `origin` out of the cut, the head starting
264
+ * there; with `more`, the text goes on after its end, so even text that fits leaves
265
+ * room for the ellipsis. For pretrunc's own entries: only an end cut uses them.
266
+ */
267
+ var cutFrom = (prepared, origin, width, options = {}, more = false) => {
268
+ const p = prepared;
269
+ const { b, w } = p;
270
+ const { ratio = .5, words = true, trim = true, ellipsis = "…" } = options;
271
+ P = p;
272
+ N = p.n;
273
+ O = origin;
274
+ room = width - SLACK;
275
+ if (!N || !O && !more && p.y <= room) return pack(b[N], b[N]);
276
+ at = options.at ?? "end";
277
+ E = ellipsis;
278
+ if (typeof ellipsis === "string") {
279
+ EW = widthOf(p.f, ellipsis) + p.s * ellipsis.length;
280
+ const kerns = p.u ? p.f.e : p.j ??= /* @__PURE__ */ new Map();
281
+ J = kerns.get(ellipsis);
282
+ if (!J) kerns.set(ellipsis, J = new Float64Array(p.u ? 1536 : 2 * N).fill(NaN));
283
+ } else {
284
+ EW = ellipsis;
285
+ J = void 0;
286
+ }
287
+ const r = room - EW;
288
+ h = N;
289
+ t = N;
290
+ let keepH = O;
291
+ let keepT = N;
292
+ if (at === "start") {
293
+ h = O;
294
+ t = fitTail(r);
295
+ } else if (at === "end") h = fitHead(r);
296
+ else {
297
+ const { tailGraphemes } = options;
298
+ const fixed = tailGraphemes === void 0 ? -1 : Math.max(0, N - tailGraphemes);
299
+ if (fixed >= 0 && fixed < N && tailSide(fixed) <= r) {
300
+ t = keepT = fixed;
301
+ h = Math.min(t, fitHead(r - tailSide(t)));
302
+ } else {
303
+ const { q } = p;
304
+ let H = O;
305
+ let T = N;
306
+ let spare = true;
307
+ for (let j = 0; j < q.length; j += 3) {
308
+ const s = q[j];
309
+ const e = q[j + 1];
310
+ const toTail = e === N || s > 0 && w[s] + w[e] > 2 * ratio * w[N];
311
+ if (toTail ? s >= T : e <= H) continue;
312
+ if ((toTail ? headSide(H) + tailSide(s) : headSide(e) + tailSide(T)) <= r) {
313
+ if (toTail) T = keepT = s;
314
+ else H = keepH = e;
315
+ } else if (!q[j + 2]) {
316
+ if (toTail) T = fitTail(r - headSide(H));
317
+ else H = fitHead(r - tailSide(T));
318
+ spare = false;
319
+ break;
320
+ }
321
+ }
322
+ const rest = spare ? r - headSide(H) - tailSide(T) : 0;
323
+ t = fitTail(tailSide(T) + rest * (1 - ratio));
324
+ let headRoom = headSide(H) + rest * ratio;
325
+ if (inside(t)) {
326
+ while (t < N && inside(t)) t++;
327
+ headRoom = r - tailSide(t);
328
+ }
329
+ h = Math.min(t, fitHead(headRoom));
330
+ }
331
+ }
332
+ if (!p.x) {
333
+ shrink();
334
+ if (at !== "start") while (h < t && measure(h + 1, t) <= room) h++;
335
+ if (at !== "end") while (t > h && measure(h, t - 1) <= room) t--;
336
+ }
337
+ if (words !== false) {
338
+ let limit = words === true ? headWidth(h) / 4 : words.within;
339
+ let i = h;
340
+ while (i > keepH && !isBreak(i) && w[h] - w[i - 1] <= limit) i--;
341
+ if (isBreak(i)) h = i;
342
+ if (words === true) limit = tailWidth(t) / 4;
343
+ i = t;
344
+ while (i < keepT && !isBreak(i) && w[i + 1] - w[t] <= limit) i++;
345
+ if (isBreak(i)) t = i;
346
+ }
347
+ while (h > O && (inside(h) || trim && p.c[h - 1] & 2)) h--;
348
+ while (t < N && (inside(t) || trim && p.c[t] & 2)) t++;
349
+ shrink();
350
+ return pack(b[h], b[t]);
351
+ };
352
+ var cut = (prepared, width, options = {}) => cutFrom(prepared, 0, width, options);
353
+ //#endregion
354
+ export { prepare as a, toText as c, pack as i, truncated as l, cutFrom as n, prepareRange as o, head as r, tail as s, cut as t };
355
+
356
+ //# sourceMappingURL=cut-D7qi2yZl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cut-D7qi2yZl.js","names":[],"sources":["../src/cut.ts"],"sourcesContent":["import { type Face, type FaceData, faceOf, pairOf, unitOf, widthOf } from \"./face.ts\";\n\n/** Text measured once, ready to cut at any width. */\nexport interface Prepared {\n /** The text as `white-space: normal` paints it; every offset refers to this string. */\n readonly text: string;\n}\n\ninterface Internal extends Prepared {\n readonly f: FaceData;\n /** Graphemes. */\n readonly n: number;\n /**\n * Each grapheme's start offset, then the text's length. Simple text shares one\n * array of 0, 1, 2…, which can run past its length.\n */\n readonly b: number[];\n /**\n * `w[i]`: the width of the first `i` graphemes, kerning between them included,\n * except kerning that narrows across a possible line break.\n */\n readonly w: Float64Array;\n /** `d[i]`: the narrowing kerning `w` leaves out of the first `i` graphemes. */\n readonly d: Float64Array;\n /** `a[i]`: grapheme `i`'s width alone, letter spacing included. */\n readonly a: Float64Array;\n /** Per grapheme: 1 when a word can end beside it, 2 when a cut trims it. */\n readonly c: Uint8Array;\n /** Whether `w - d` matches canvas for the whole text, so cuts can trust `w` without measuring. */\n readonly x: boolean;\n /** The whole text's measured width. */\n readonly y: number;\n /**\n * Whole protected matches as grapheme pairs, start then end. An end past the text's is\n * one past its last grapheme, so a cut at the end of a range of a longer text still\n * sees the match run on.\n * Empty without `protect`. A few numbers, where marking every unit took an array as\n * long as the text, which in Chromium cost more than the regex.\n */\n readonly k: number[];\n /**\n * Every protected match, in the order a middle cut keeps them: highest weight first,\n * then as given. Grapheme triples: start, end, and 1 for a whole match.\n */\n readonly q: number[];\n /** Whether every UTF-16 unit is a grapheme below U+0300, so widths are found by code. */\n readonly u: boolean;\n /** Letter spacing in px, counted per UTF-16 unit: WebKit spaces glyphs, several to a grapheme in Indic and Thai. */\n readonly s: number;\n /** How much wider a tab lays out than canvas measures it, in `pre-wrap`; 0 otherwise. */\n readonly g: number;\n /**\n * Kerning beside each ellipsis cut with, by ellipsis: `[i]` between grapheme `i` and the\n * ellipsis after it, `[n + i]` between the ellipsis and grapheme `i`. NaN until needed.\n */\n j: Map<string, Float64Array> | undefined;\n /** Where the last head search ended, and the last tail search: the next one starts there. */\n v: number;\n z: number;\n}\n\n/** A pattern whose matches a middle cut keeps before the rest of the text. */\nexport interface Protection {\n readonly pattern: RegExp;\n /** From 0 to 1: a match with a higher weight is kept first. Default 1; ties go in order. */\n readonly weight?: number;\n /**\n * Shown whole or not at all (true), or as much as fits, cut on its side toward the\n * ellipsis (false). Default false. A cut never splits a whole match.\n */\n readonly whole?: boolean;\n}\n\n/** What `protect` takes: a pattern, a `Protection`, or several, in order. */\nexport type Protect = RegExp | Protection | ReadonlyArray<RegExp | Protection>;\n\nexport interface PrepareOptions {\n /**\n * Matches a middle cut keeps first, such as a file name, `/\\/[^/]+$/`, before a\n * leading folder, `{ pattern: /^~\\/[^/]+\\//, weight: 0.5 }`. A match keeps the side\n * nearer its middle. Start and end cuts only keep whole matches whole.\n */\n readonly protect?: Protect;\n /** CSS `letter-spacing` in px, which every grapheme carries after it. */\n readonly letterSpacing?: number;\n}\n\nexport interface CutOptions {\n /** Where the ellipsis goes. Default \"end\". */\n readonly at?: \"start\" | \"middle\" | \"end\";\n /** Middle: the head's share of the room protected matches leave. Default 0.5. */\n readonly ratio?: number;\n /** Middle: the tail keeps this many graphemes, when they fit. */\n readonly tailGraphemes?: number;\n /**\n * Back off to a word boundary within a quarter of what that side keeps (true), within\n * `within` px, or never (false). Default true.\n */\n readonly words?: boolean | { readonly within: number };\n /** Drop spaces and `,;:` beside the ellipsis. Default true. */\n readonly trim?: boolean;\n /** The ellipsis text, or the width in px of an element you render in its place. Default \"…\". */\n readonly ellipsis?: string | number;\n}\n\n/**\n * Kept text is `text.slice(0, head(c)) + ellipsis + text.slice(tail(c))`. Two cuts are\n * `===` when they keep the same head and tail, so a memo can compare them.\n */\nexport type Cut = number & { readonly cut: unique symbol };\n\nconst SHIFT = 2 ** 26;\n// Browsers lay text out in 1/64 px units and can round a run's width up by one.\nconst SLACK = 1 / 64;\n\nlet segmenter: Intl.Segmenter | undefined;\n// `breaks` and `trims` per character below U+0300, as grapheme flags; built on first use.\nlet flags: Uint8Array | undefined;\n// Offsets 0, 1, 2…: every simple text's grapheme starts. It only grows, and is shared.\nconst identity = [0];\nconst none: number[] = [];\n\n/** A cut from its two offsets; for pretrunc's own entries, not re-exported. */\nexport const pack = (head: number, tail: number): Cut => (head * SHIFT + tail) as Cut;\nexport const head = (c: Cut): number => Math.floor(c / SHIFT);\nexport const tail = (c: Cut): number => c % SHIFT;\nexport const truncated = (c: Cut): boolean => head(c) < tail(c);\nexport const toText = (p: Prepared, c: Cut, ellipsis = \"…\"): string =>\n truncated(c) ? p.text.slice(0, head(c)) + ellipsis + p.text.slice(tail(c)) : p.text;\n\n// A word can end beside white space, a hyphen, a slash, an underscore, a full stop or an\n// ellipsis, an en or em dash. From U+2E80 up (CJK, emoji) every character is a word of its own.\nconst breaks = /[\\s\\-/_.…–—⺀-￿]/;\nconst trims = /[\\s,;:]/;\n// WebKit lays out the text between two break opportunities as a run of its own, so\n// kerning across one, after a hyphen, a dash, CJK or an ellipsis, never reaches the\n// page there, while Chrome and Firefox keep it. Across a possible break, only kerning\n// that widens counts: never narrower than any engine lays it out. WebKit also turns\n// kerning off under letter spacing, so with it no narrowing kerning counts anywhere.\nconst across = (kerning: number, broken: unknown) => (broken ? Math.max(0, kerning) : kerning);\n\nexport const prepare = (\n text: string,\n font: string | Face,\n options: PrepareOptions = {},\n): Prepared =>\n build(\n // Most text has no run of white space to collapse, so one test saves two replaces.\n /[\\t\\n\\f\\r]| {2}|^ | $/.test(text)\n ? text.replace(/[\\t\\n\\f\\r ]+/g, \" \").replace(/^ | $/g, \"\")\n : text,\n typeof font === \"string\" ? faceOf(font) : (font as FaceData),\n options,\n );\n\n/**\n * Prepares `full.slice(from, to)` as it stands, white space kept, with protected\n * matches found in all of `full`, and a tab `tab` px wide when that's given: the most\n * a tab stop can take. For pretrunc's own entries.\n */\nexport const prepareRange = (\n full: string,\n from: number,\n to: number,\n font: string,\n options: PrepareOptions,\n tab = 0,\n): Prepared => build(full.slice(from, to), faceOf(font), options, full, from, tab);\n\nconst build = (\n t: string,\n f: FaceData,\n options: PrepareOptions,\n full = t,\n from = 0,\n tab = 0,\n): Prepared => {\n const spacing = options.letterSpacing ?? 0;\n // Below U+0300 every UTF-16 unit is a grapheme, CRLF aside, which white space\n // handling removes or a hard break ends a line at, so most text never meets the\n // segmenter. The class is anything from U+0300 up.\n const simple = !/[^\\0-\\u02ff]/.test(t);\n let b = identity;\n if (simple) while (b.length <= t.length) b.push(b.length);\n else {\n b = Array.from((segmenter ??= new Intl.Segmenter()).segment(t), (g) => g.index);\n b.push(t.length);\n }\n const n = simple ? t.length : b.length - 1;\n const w = new Float64Array(n + 1);\n const d = new Float64Array(n + 1);\n const a = new Float64Array(n);\n const c = new Uint8Array(n);\n // Whether each word, measured whole, matches the sum of its pairs (`w - d`, kerning\n // uncapped, letter spacing left out). Then the whole text does, since nothing shapes\n // across a space, and every cut of it is arithmetic. Words repeat across texts in one\n // font, as names and folders do, so most are measured once for a whole table.\n let x = true;\n let word = 0;\n const check = (end: number) => {\n if (x && end - word > 1) {\n const sum =\n w[end]! - d[end]! - w[word + 1]! + d[word + 1]! + a[word]! - spacing * (b[end]! - b[word]!);\n x = Math.abs(widthOf(f, t.slice(b[word], b[end])) - sum) < 0.01;\n }\n };\n if (!flags) {\n flags = new Uint8Array(768);\n for (let code = 0; code < 768; code++) {\n const ch = String.fromCharCode(code);\n flags[code] = +breaks.test(ch) | (+trims.test(ch) << 1);\n }\n }\n let previous = \"\";\n let before = 0;\n for (let i = 0; i < n; i++) {\n // A grapheme adds its own advance, the kerning from its pair and its letter\n // spacing. The step is never below 0, so a binary search stays valid.\n let own: number;\n let kerning = 0;\n let space = false;\n if (simple) {\n const code = t.charCodeAt(i);\n own = tab && code === 9 ? tab : unitOf(f, code);\n c[i] = flags[code]!;\n if (i && !(tab && (code === 9 || t.charCodeAt(i - 1) === 9))) {\n kerning = pairOf(f, t.charCodeAt(i - 1), code) - before - own;\n }\n space = code === 32;\n } else {\n const g = t.slice(b[i], b[i + 1]);\n own = tab && g === \"\\t\" ? tab : widthOf(f, g);\n c[i] = +breaks.test(g) | (+trims.test(g) << 1);\n if (i && !(tab && (g === \"\\t\" || previous === \"\\t\"))) {\n kerning = widthOf(f, previous + g) - before - own;\n }\n space = g === \" \";\n previous = g;\n }\n const spaced = spacing * (b[i + 1]! - b[i]!);\n a[i] = own + spaced;\n const counted = i ? across(kerning, spacing || c[i - 1]! & 1) : 0;\n w[i + 1] = w[i]! + Math.max(0, own + counted) + spaced;\n d[i + 1] = d[i]! + counted - kerning;\n if (space) {\n check(i);\n word = i + 1;\n }\n before = own;\n }\n check(n);\n // Text the model can't be trusted with, such as ligatures or joined scripts, is\n // measured whole.\n // Canvas has no tab stops, so a measured tab is widened to the most a stop can take.\n const g = tab && tab - widthOf(f, \"\\t\");\n // The width that has to fit for the text to show whole, kerning counted as for a cut.\n const y = x ? w[n]! : f.m(t) + spacing * t.length + g * (t.split(\"\\t\").length - 1) + d[n]!;\n let k = none;\n let q = none;\n const { protect } = options;\n if (protect) {\n // The first grapheme starting at or after offset `o`, or with `back`, the one that\n // holds it.\n const at = (o: number, back: boolean) => {\n const i = Math.min(Math.max(o, 0), t.length);\n if (simple) return i;\n let lo = 0;\n let hi = n;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (b[mid]! < i) lo = mid + 1;\n else hi = mid;\n }\n return back && b[lo]! > i ? lo - 1 : lo;\n };\n const spans: number[][] = [];\n k = [];\n const list = \"length\" in protect ? protect : [protect];\n for (let i = 0; i < list.length; i++) {\n const given = list[i]!;\n const entry = given instanceof RegExp ? { pattern: given } : given;\n const { pattern } = entry;\n // `matchAll` throws on a pattern without the g flag, so one is given it.\n for (const match of full.matchAll(\n pattern.global ? pattern : new RegExp(pattern, pattern.flags + \"g\"),\n )) {\n const start = match.index - from;\n const end = start + match[0].length;\n if (start < t.length && end > 0 && end > start) {\n const s = at(start, true);\n const e = at(end, false);\n if (entry.whole) k.push(s, end > t.length ? n + 1 : e);\n spans.push([-(entry.weight ?? 1), i, s, e, +!!entry.whole]);\n }\n }\n }\n // By weight alone: the sort is stable, and matches come in pattern order, then text order.\n spans.sort((x, y) => x[0]! - y[0]!);\n q = [];\n for (const x of spans) q.push(x[2]!, x[3]!, x[4]!);\n }\n const p: Internal = {\n text: t,\n f,\n n,\n b,\n w,\n d,\n a,\n c,\n x,\n y,\n k,\n q,\n u: simple,\n s: spacing,\n g,\n j: undefined,\n v: 0,\n z: n,\n };\n return p;\n};\n\n// The cut in progress. A cut runs to its end without calling out except to a face's\n// measure, so its helpers read this state instead of closing over it: a resize frame\n// over thousands of rows then allocates nothing per cut.\nlet P: Internal;\nlet N = 0;\nlet O = 0;\nlet room = 0;\nlet E: string | number = \"\";\nlet EW = 0;\nlet J: Float64Array | undefined;\nlet at: CutOptions[\"at\"] = \"end\";\n\nconst grapheme = (i: number) => P.text.slice(P.b[i], P.b[i + 1]);\n// The head from grapheme O to `h` alone: its first grapheme without kerning before it.\nconst headWidth = (h: number) => (h > O ? P.w[h]! - P.w[O + 1]! + P.a[O]! : 0);\n// The tail alone: its first grapheme's own advance, without the kerning before it.\nconst tailWidth = (t: number) => (t < N ? P.w[N]! - P.w[t + 1]! + P.a[t]! : 0);\n// A word boundary inside the text: backing off to either end would keep nothing.\nconst isBreak = (i: number) => i > O && i < N && ((P.c[i - 1]! | P.c[i]!) & 1) !== 0;\n// Whether a cut before grapheme `i` falls inside a whole protected match.\nconst inside = (i: number) => {\n const k = P.k;\n for (let j = 0; j < k.length; j += 2) if (k[j]! < i && i < k[j + 1]!) return true;\n return false;\n};\nconst kern = (left: string, right: string) =>\n widthOf(P.f, left + right) - widthOf(P.f, left) - widthOf(P.f, right);\n// The kerning between grapheme `i` and the ellipsis after it (`side` 0) or before it (1).\nconst beside = (i: number, side: number) => {\n // Simple text shares its face's kerning by character code; other text keeps its own.\n const at = P.u ? side * 768 + P.text.charCodeAt(i) : side * N + i;\n let kerning = J![at]!;\n // NaN, not yet measured.\n if (kerning !== kerning) {\n kerning = side ? kern(E as string, grapheme(i)) : kern(grapheme(i), E as string);\n J![at] = kerning;\n }\n return kerning;\n};\n\n// The largest `h` whose head fits `r` px, and the smallest `t` whose tail does. A resize\n// moves a cut a grapheme or two, so each search gallops from where the last one on this\n// text ended, doubling its step until it brackets the answer, then bisects the bracket.\n// The widths only grow with `h`, so the answer is the one a plain bisection finds.\nconst maxHead = (r: number) => {\n let lo = O;\n let hi = N;\n let i = Math.max(P.v, O);\n // `lo` always fits, as the empty head does even in no room, so it is never measured.\n for (let step = 1; lo < hi; step *= 2) {\n if (i === lo || headWidth(i) <= r) {\n lo = i;\n i += step;\n if (i > hi) break;\n } else {\n hi = i - 1;\n i -= step;\n if (i < lo) break;\n }\n }\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (headWidth(mid) <= r) lo = mid;\n else hi = mid - 1;\n }\n return (P.v = lo);\n};\nconst minTail = (r: number) => {\n let lo = 0;\n let hi = N;\n let i = P.z;\n for (let step = 1; lo < hi; step *= 2) {\n if (i === hi || tailWidth(i) <= r) {\n hi = i;\n i -= step;\n if (i < lo) break;\n } else {\n lo = i + 1;\n i += step;\n if (i > hi) break;\n }\n }\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (tailWidth(mid) <= r) hi = mid;\n else lo = mid + 1;\n }\n return (P.z = lo);\n};\n\n// How wide a cut paints. When the model matched canvas over the whole text it is\n// trusted; otherwise the cut is measured for real, plus the kerning that a break\n// opportunity drops in WebKit. Either way the kerning beside the ellipsis is added,\n// and the one after it always crosses a break.\nconst measure = (h: number, t: number) => {\n const { text, f, b, d, c, x } = P;\n let painted = headWidth(h) + EW + tailWidth(t);\n if (!x) {\n const before = text.slice(b[O], b[h]);\n const after = text.slice(b[t]);\n painted = J\n ? f.m(before + E + after) + EW - widthOf(f, E as string)\n : f.m(before) + EW + f.m(after);\n painted += P.s * (b[h]! - b[O]! + b[N]! - b[t]!);\n if (P.g) painted += P.g * ((before + after).split(\"\\t\").length - 1);\n if (h > O) painted += d[h]! - d[O + 1]!;\n if (t < N) painted += d[N]! - d[t + 1]!;\n }\n if (J && h > O) {\n const kerning = beside(h - 1, 0);\n painted += across(kerning, P.s || c[h - 1]! & 1) - (x ? 0 : kerning);\n }\n if (J && t < N) {\n const kerning = beside(t, 1);\n painted += across(kerning, 1) - (x ? 0 : kerning);\n }\n return painted;\n};\n\n// What a side paints beside the ellipsis: its text and the kerning across to it.\nconst headSide = (h: number) =>\n headWidth(h) + (J && h > O ? across(beside(h - 1, 0), P.s || P.c[h - 1]! & 1) : 0);\nconst tailSide = (t: number) => tailWidth(t) + (J && t < N ? across(beside(t, 1), 1) : 0);\n// The most a side keeps within `budget`: the search, then a step or two for the\n// kerning beside the ellipsis, which the search leaves out.\nconst fitHead = (budget: number) => {\n let i = maxHead(budget);\n while (i > O && headSide(i) > budget) i--;\n while (i < N && headSide(i + 1) <= budget) i++;\n return i;\n};\nconst fitTail = (budget: number) => {\n let i = minTail(budget);\n while (i < N && tailSide(i) > budget) i++;\n while (i > 0 && tailSide(i - 1) <= budget) i--;\n return i;\n};\n\nlet h = 0;\nlet t = 0;\n// Shrink the cut a grapheme at a time until it fits, from the side that keeps more.\nconst shrink = () => {\n while ((h > O || t < N) && measure(h, t) > room) {\n if (at === \"end\" || (at === \"middle\" && h > N - t)) h--;\n else t++;\n }\n};\n\n/**\n * `cut`, keeping the text before grapheme `origin` out of the cut, the head starting\n * there; with `more`, the text goes on after its end, so even text that fits leaves\n * room for the ellipsis. For pretrunc's own entries: only an end cut uses them.\n */\nexport const cutFrom = (\n prepared: Prepared,\n origin: number,\n width: number,\n options: CutOptions = {},\n more = false,\n): Cut => {\n const p = prepared as Internal;\n const { b, w } = p;\n const { ratio = 0.5, words = true, trim = true, ellipsis = \"…\" } = options;\n P = p;\n N = p.n;\n O = origin;\n room = width - SLACK;\n if (!N || (!O && !more && p.y <= room)) return pack(b[N]!, b[N]!);\n at = options.at ?? \"end\";\n E = ellipsis;\n if (typeof ellipsis === \"string\") {\n // Its letter spacing counted per UTF-16 unit, never less than per grapheme.\n EW = widthOf(p.f, ellipsis) + p.s * ellipsis.length;\n const kerns = p.u ? p.f.e : (p.j ??= new Map());\n J = kerns.get(ellipsis);\n if (!J) kerns.set(ellipsis, (J = new Float64Array(p.u ? 1536 : 2 * N).fill(Number.NaN)));\n } else {\n EW = ellipsis;\n J = undefined;\n }\n\n // The head keeps graphemes [O, h) and the tail [t, N), in `r` px of text. Each side\n // keeps the most that fits its own share and never takes the other's slack, so as the\n // width moves, each side only ever grows or only ever shrinks: the ellipsis doesn't\n // drift a grapheme one way and back.\n const r = room - EW;\n h = N;\n t = N;\n // Kept whole: the word backoff stops at the head's end of these, and the tail's start.\n let keepH = O;\n let keepT = N;\n if (at === \"start\") {\n h = O;\n t = fitTail(r);\n } else if (at === \"end\") {\n h = fitHead(r);\n } else {\n const { tailGraphemes } = options;\n const fixed = tailGraphemes === undefined ? -1 : Math.max(0, N - tailGraphemes);\n if (fixed >= 0 && fixed < N && tailSide(fixed) <= r) {\n t = keepT = fixed;\n h = Math.min(t, fitHead(r - tailSide(t)));\n } else {\n // Protected matches, highest weight first, take the room they need: a whole one\n // all of it or none, any other as much as fits, cut on its side toward the\n // ellipsis, and then nothing after it gets room. A match keeps the side nearer its\n // middle, so a file name keeps the tail and a leading folder the head. The text\n // outside them shares what's left by ratio. As the width grows, a cut match only\n // gains and the rest only grows, so each side moves one way; a whole match\n // switches the cut once, at the width where it starts to fit.\n const { q } = p;\n let H = O;\n let T = N;\n // Room for the rest of the text, until a match is cut to fit: then none, since the\n // slack beside a cut match moves with every grapheme it gains.\n let spare = true;\n for (let j = 0; j < q.length; j += 3) {\n const s = q[j]!;\n const e = q[j + 1]!;\n const toTail = e === N || (s > 0 && w[s]! + w[e]! > 2 * ratio * w[N]!);\n if (toTail ? s >= T : e <= H) continue;\n if ((toTail ? headSide(H) + tailSide(s) : headSide(e) + tailSide(T)) <= r) {\n if (toTail) T = keepT = s;\n else H = keepH = e;\n } else if (!q[j + 2]) {\n if (toTail) T = fitTail(r - headSide(H));\n else H = fitHead(r - tailSide(T));\n spare = false;\n break;\n }\n }\n const rest = spare ? r - headSide(H) - tailSide(T) : 0;\n t = fitTail(tailSide(T) + rest * (1 - ratio));\n let headRoom = headSide(H) + rest * ratio;\n // A whole match the tail's share would split goes whole, and leaves the head its room.\n if (inside(t)) {\n while (t < N && inside(t)) t++;\n headRoom = r - tailSide(t);\n }\n h = Math.min(t, fitHead(headRoom));\n }\n }\n // Text the model can't be trusted with settles on real measurements: shrink until it\n // fits, then each side grows while one more fits, which also finds a longer cut that a\n // ligature paints narrower.\n if (!p.x) {\n shrink();\n if (at !== \"start\") while (h < t && measure(h + 1, t) <= room) h++;\n if (at !== \"end\") while (t > h && measure(h, t - 1) <= room) t--;\n }\n if (words !== false) {\n // A quarter of what the side keeps, not of the room: a limit that grew with the width\n // while the cut point held still would cross the distance to a break mid-grapheme and\n // snap the side back a word, then forward again at the next grapheme.\n let limit = words === true ? headWidth(h) / 4 : words.within;\n let i = h;\n while (i > keepH && !isBreak(i) && w[h]! - w[i - 1]! <= limit) i--;\n if (isBreak(i)) h = i;\n if (words === true) limit = tailWidth(t) / 4;\n i = t;\n while (i < keepT && !isBreak(i) && w[i + 1]! - w[t]! <= limit) i++;\n if (isBreak(i)) t = i;\n }\n while (h > O && (inside(h) || (trim && p.c[h - 1]! & 2))) h--;\n while (t < N && (inside(t) || (trim && p.c[t]! & 2))) t++;\n // Dropping graphemes can widen shaped text, as when a letter loses its joining form.\n shrink();\n return pack(b[h]!, b[t]!);\n};\n\nexport const cut = (prepared: Prepared, width: number, options: CutOptions = {}): Cut =>\n cutFrom(prepared, 0, width, options);\n"],"mappings":"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"}
package/dist/cut.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { type Face } from "./face.js";
2
+ /** Text measured once, ready to cut at any width. */
3
+ export interface Prepared {
4
+ /** The text as `white-space: normal` paints it; every offset refers to this string. */
5
+ readonly text: string;
6
+ }
7
+ /** A pattern whose matches a middle cut keeps before the rest of the text. */
8
+ export interface Protection {
9
+ readonly pattern: RegExp;
10
+ /** From 0 to 1: a match with a higher weight is kept first. Default 1; ties go in order. */
11
+ readonly weight?: number;
12
+ /**
13
+ * Shown whole or not at all (true), or as much as fits, cut on its side toward the
14
+ * ellipsis (false). Default false. A cut never splits a whole match.
15
+ */
16
+ readonly whole?: boolean;
17
+ }
18
+ /** What `protect` takes: a pattern, a `Protection`, or several, in order. */
19
+ export type Protect = RegExp | Protection | ReadonlyArray<RegExp | Protection>;
20
+ export interface PrepareOptions {
21
+ /**
22
+ * Matches a middle cut keeps first, such as a file name, `/\/[^/]+$/`, before a
23
+ * leading folder, `{ pattern: /^~\/[^/]+\//, weight: 0.5 }`. A match keeps the side
24
+ * nearer its middle. Start and end cuts only keep whole matches whole.
25
+ */
26
+ readonly protect?: Protect;
27
+ /** CSS `letter-spacing` in px, which every grapheme carries after it. */
28
+ readonly letterSpacing?: number;
29
+ }
30
+ export interface CutOptions {
31
+ /** Where the ellipsis goes. Default "end". */
32
+ readonly at?: "start" | "middle" | "end";
33
+ /** Middle: the head's share of the room protected matches leave. Default 0.5. */
34
+ readonly ratio?: number;
35
+ /** Middle: the tail keeps this many graphemes, when they fit. */
36
+ readonly tailGraphemes?: number;
37
+ /**
38
+ * Back off to a word boundary within a quarter of what that side keeps (true), within
39
+ * `within` px, or never (false). Default true.
40
+ */
41
+ readonly words?: boolean | {
42
+ readonly within: number;
43
+ };
44
+ /** Drop spaces and `,;:` beside the ellipsis. Default true. */
45
+ readonly trim?: boolean;
46
+ /** The ellipsis text, or the width in px of an element you render in its place. Default "…". */
47
+ readonly ellipsis?: string | number;
48
+ }
49
+ /**
50
+ * Kept text is `text.slice(0, head(c)) + ellipsis + text.slice(tail(c))`. Two cuts are
51
+ * `===` when they keep the same head and tail, so a memo can compare them.
52
+ */
53
+ export type Cut = number & {
54
+ readonly cut: unique symbol;
55
+ };
56
+ /** A cut from its two offsets; for pretrunc's own entries, not re-exported. */
57
+ export declare const pack: (head: number, tail: number) => Cut;
58
+ export declare const head: (c: Cut) => number;
59
+ export declare const tail: (c: Cut) => number;
60
+ export declare const truncated: (c: Cut) => boolean;
61
+ export declare const toText: (p: Prepared, c: Cut, ellipsis?: string) => string;
62
+ export declare const prepare: (text: string, font: string | Face, options?: PrepareOptions) => Prepared;
63
+ /**
64
+ * Prepares `full.slice(from, to)` as it stands, white space kept, with protected
65
+ * matches found in all of `full`, and a tab `tab` px wide when that's given: the most
66
+ * a tab stop can take. For pretrunc's own entries.
67
+ */
68
+ export declare const prepareRange: (full: string, from: number, to: number, font: string, options: PrepareOptions, tab?: number) => Prepared;
69
+ /**
70
+ * `cut`, keeping the text before grapheme `origin` out of the cut, the head starting
71
+ * there; with `more`, the text goes on after its end, so even text that fits leaves
72
+ * room for the ellipsis. For pretrunc's own entries: only an end cut uses them.
73
+ */
74
+ export declare const cutFrom: (prepared: Prepared, origin: number, width: number, options?: CutOptions, more?: boolean) => Cut;
75
+ export declare const cut: (prepared: Prepared, width: number, options?: CutOptions) => Cut;
@@ -0,0 +1,52 @@
1
+ import { a as forget } from "./face-pICHAjvk.js";
2
+ //#region src/dom.ts
3
+ var watched = /* @__PURE__ */ new WeakMap();
4
+ var observer;
5
+ /**
6
+ * Calls `listener` with the element's content-box width, first before the element
7
+ * paints and then on every change, through one ResizeObserver shared by every element.
8
+ * Returns the function that stops it.
9
+ */
10
+ var observe = (element, listener) => {
11
+ observer ??= new ResizeObserver((entries) => {
12
+ for (const entry of entries) watched.get(entry.target)?.(entry.contentRect.width);
13
+ });
14
+ watched.set(element, listener);
15
+ observer.observe(element);
16
+ return () => {
17
+ watched.delete(element);
18
+ observer.unobserve(element);
19
+ };
20
+ };
21
+ /**
22
+ * The canvas font for an element's computed style. Built from the longhands: the
23
+ * `font` shorthand reads back empty in some engines, and `font-stretch` reads back as
24
+ * a percentage, which canvas rejects, so it is left out. Reading the font and the
25
+ * letter spacing, pass one `getComputedStyle(element)` to both: computing it is most of
26
+ * the cost, and twice takes twice as long.
27
+ */
28
+ var fontOf = (element, style = getComputedStyle(element)) => `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
29
+ /** An element's computed `letter-spacing` in px; "normal" is 0. */
30
+ var spacingOf = (element, style = getComputedStyle(element)) => parseFloat(style.letterSpacing) || 0;
31
+ var loaded = /* @__PURE__ */ new Set();
32
+ /**
33
+ * Calls `listener` whenever web fonts finish loading, after dropping every width
34
+ * measured so far: those were the fallback font's. Prepare the text again in it.
35
+ * Returns the function that stops it.
36
+ */
37
+ var onFonts = (listener) => {
38
+ if (!loaded.size) document.fonts.addEventListener("loading", loading);
39
+ loaded.add(listener);
40
+ return () => {
41
+ loaded.delete(listener);
42
+ if (!loaded.size) document.fonts.removeEventListener("loading", loading);
43
+ };
44
+ };
45
+ var loading = () => void document.fonts.ready.then(() => {
46
+ forget();
47
+ for (const listener of loaded) listener();
48
+ });
49
+ //#endregion
50
+ export { spacingOf as i, observe as n, onFonts as r, fontOf as t };
51
+
52
+ //# sourceMappingURL=dom-CPL0-qc3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-CPL0-qc3.js","names":[],"sources":["../src/dom.ts"],"sourcesContent":["import { forget } from \"./face.ts\";\n\nconst watched = new WeakMap<Element, (width: number) => void>();\nlet observer: ResizeObserver | undefined;\n\n/**\n * Calls `listener` with the element's content-box width, first before the element\n * paints and then on every change, through one ResizeObserver shared by every element.\n * Returns the function that stops it.\n */\nexport const observe = (element: Element, listener: (width: number) => void): (() => void) => {\n observer ??= new ResizeObserver((entries) => {\n for (const entry of entries) watched.get(entry.target)?.(entry.contentRect.width);\n });\n watched.set(element, listener);\n observer.observe(element);\n return () => {\n watched.delete(element);\n observer!.unobserve(element);\n };\n};\n\n/**\n * The canvas font for an element's computed style. Built from the longhands: the\n * `font` shorthand reads back empty in some engines, and `font-stretch` reads back as\n * a percentage, which canvas rejects, so it is left out. Reading the font and the\n * letter spacing, pass one `getComputedStyle(element)` to both: computing it is most of\n * the cost, and twice takes twice as long.\n */\nexport const fontOf = (element: Element, style = getComputedStyle(element)): string =>\n `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;\n\n/** An element's computed `letter-spacing` in px; \"normal\" is 0. */\nexport const spacingOf = (element: Element, style = getComputedStyle(element)): number =>\n parseFloat(style.letterSpacing) || 0;\n\nconst loaded = new Set<() => void>();\n\n/**\n * Calls `listener` whenever web fonts finish loading, after dropping every width\n * measured so far: those were the fallback font's. Prepare the text again in it.\n * Returns the function that stops it.\n */\nexport const onFonts = (listener: () => void): (() => void) => {\n if (!loaded.size) document.fonts.addEventListener(\"loading\", loading);\n loaded.add(listener);\n return () => {\n loaded.delete(listener);\n if (!loaded.size) document.fonts.removeEventListener(\"loading\", loading);\n };\n};\n\n// WebKit fires no `loadingdone` for a face loaded from script, but every engine fires\n// `loading`, and `ready` settles once every pending load is done.\nconst loading = () =>\n void document.fonts.ready.then(() => {\n forget();\n for (const listener of loaded) listener();\n });\n"],"mappings":";;AAEA,IAAM,0BAAU,IAAI,QAA0C;AAC9D,IAAI;;;;;;AAOJ,IAAa,WAAW,SAAkB,aAAoD;CAC5F,aAAa,IAAI,gBAAgB,YAAY;EAC3C,KAAK,MAAM,SAAS,SAAS,QAAQ,IAAI,MAAM,MAAM,CAAC,GAAG,MAAM,YAAY,KAAK;CAClF,CAAC;CACD,QAAQ,IAAI,SAAS,QAAQ;CAC7B,SAAS,QAAQ,OAAO;CACxB,aAAa;EACX,QAAQ,OAAO,OAAO;EACtB,SAAU,UAAU,OAAO;CAC7B;AACF;;;;;;;;AASA,IAAa,UAAU,SAAkB,QAAQ,iBAAiB,OAAO,MACvE,GAAG,MAAM,UAAU,GAAG,MAAM,WAAW,GAAG,MAAM,SAAS,GAAG,MAAM;;AAGpE,IAAa,aAAa,SAAkB,QAAQ,iBAAiB,OAAO,MAC1E,WAAW,MAAM,aAAa,KAAK;AAErC,IAAM,yBAAS,IAAI,IAAgB;;;;;;AAOnC,IAAa,WAAW,aAAuC;CAC7D,IAAI,CAAC,OAAO,MAAM,SAAS,MAAM,iBAAiB,WAAW,OAAO;CACpE,OAAO,IAAI,QAAQ;CACnB,aAAa;EACX,OAAO,OAAO,QAAQ;EACtB,IAAI,CAAC,OAAO,MAAM,SAAS,MAAM,oBAAoB,WAAW,OAAO;CACzE;AACF;AAIA,IAAM,gBACJ,KAAK,SAAS,MAAM,MAAM,WAAW;CACnC,OAAO;CACP,KAAK,MAAM,YAAY,QAAQ,SAAS;AAC1C,CAAC"}
package/dist/dom.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Calls `listener` with the element's content-box width, first before the element
3
+ * paints and then on every change, through one ResizeObserver shared by every element.
4
+ * Returns the function that stops it.
5
+ */
6
+ export declare const observe: (element: Element, listener: (width: number) => void) => (() => void);
7
+ /**
8
+ * The canvas font for an element's computed style. Built from the longhands: the
9
+ * `font` shorthand reads back empty in some engines, and `font-stretch` reads back as
10
+ * a percentage, which canvas rejects, so it is left out. Reading the font and the
11
+ * letter spacing, pass one `getComputedStyle(element)` to both: computing it is most of
12
+ * the cost, and twice takes twice as long.
13
+ */
14
+ export declare const fontOf: (element: Element, style?: CSSStyleDeclaration) => string;
15
+ /** An element's computed `letter-spacing` in px; "normal" is 0. */
16
+ export declare const spacingOf: (element: Element, style?: CSSStyleDeclaration) => number;
17
+ /**
18
+ * Calls `listener` whenever web fonts finish loading, after dropping every width
19
+ * measured so far: those were the fallback font's. Prepare the text again in it.
20
+ * Returns the function that stops it.
21
+ */
22
+ export declare const onFonts: (listener: () => void) => (() => void);
package/dist/dom.js ADDED
@@ -0,0 +1 @@
1
+ export { t as fontOf, n as observe, r as onFonts, i as spacingOf } from "./dom-CPL0-qc3.js";
@@ -0,0 +1,68 @@
1
+ //#region src/face.ts
2
+ var faceOver = (measure) => ({
3
+ m: measure,
4
+ c: /* @__PURE__ */ new Map(),
5
+ u: (/* @__PURE__ */ new Float64Array(768)).fill(NaN),
6
+ p: /* @__PURE__ */ new Map(),
7
+ q: (/* @__PURE__ */ new Float64Array(16384)).fill(NaN),
8
+ e: /* @__PURE__ */ new Map()
9
+ });
10
+ /** A face over your own measure function: node-canvas, a test double. */
11
+ var face = faceOver;
12
+ var context;
13
+ var current = "";
14
+ var faces = /* @__PURE__ */ new Map();
15
+ /** The canvas face for a CSS font shorthand, for pretrunc's own modules. */
16
+ var faceOf = (font) => {
17
+ let f = faces.get(font);
18
+ if (!f) {
19
+ f = faceOver((text) => {
20
+ context ??= new OffscreenCanvas(0, 0).getContext("2d");
21
+ if (current !== font) context.font = current = font;
22
+ return context.measureText(text).width;
23
+ });
24
+ faces.set(font, f);
25
+ }
26
+ return f;
27
+ };
28
+ /** The canvas face for a CSS font shorthand, shared by everything in that font. */
29
+ var canvasFace = faceOf;
30
+ /**
31
+ * Drops the canvas face for `font`, or every one, and with `pretrunc/lines` loaded
32
+ * every width pretext keeps, so text prepared afterwards is measured again. Call it
33
+ * once a web font loads: widths measured before are the fallback font's.
34
+ */
35
+ var forget = (font) => {
36
+ if (font === void 0) faces.clear();
37
+ else faces.delete(font);
38
+ for (const clear of clears) clear();
39
+ };
40
+ var clears = [];
41
+ var alsoForget = (clear) => void clears.push(clear);
42
+ /** The width of `text` in `f`, measured once. */
43
+ var widthOf = (f, text) => {
44
+ let w = f.c.get(text);
45
+ if (w === void 0) f.c.set(text, w = f.m(text));
46
+ return w;
47
+ };
48
+ var unitOf = (f, code) => {
49
+ let w = f.u[code];
50
+ if (w !== w) f.u[code] = w = f.m(String.fromCharCode(code));
51
+ return w;
52
+ };
53
+ var pairOf = (f, first, second) => {
54
+ if (first < 128 && second < 128) {
55
+ const key = first * 128 + second;
56
+ let w = f.q[key];
57
+ if (w !== w) f.q[key] = w = f.m(String.fromCharCode(first, second));
58
+ return w;
59
+ }
60
+ const key = first * 768 + second;
61
+ let w = f.p.get(key);
62
+ if (w === void 0) f.p.set(key, w = f.m(String.fromCharCode(first, second)));
63
+ return w;
64
+ };
65
+ //#endregion
66
+ export { forget as a, widthOf as c, faceOf as i, canvasFace as n, pairOf as o, face as r, unitOf as s, alsoForget as t };
67
+
68
+ //# sourceMappingURL=face-pICHAjvk.js.map