@weasel-js/text 1.14.2 → 1.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.
@@ -1,5 +1,5 @@
1
1
  import { isSolidPaint, resolveFill, resolveScreenLength, fillHasInk, strokeHasInk, paintKey, strokeKey } from '@weasel-js/paint';
2
- import { cssFamilyName, resolveFontVariant, syntheticBoldGrowth, glyphOutline, faceMetricsOf, glyphGeneration, faceGeneration, metricsOf, warnNoMetrics, resolveColorCluster, glyphIn, resolveGlyph } from '@weasel-js/font';
2
+ import { cssFamilyName, cachedFontVariant, syntheticBoldGrowth, glyphOutline, faceMetricsOf, glyphGeneration, faceGeneration, metricsOf, warnNoMetrics, resolveColorCluster, glyphIn, resolveGlyph, outlineMetrics } from '@weasel-js/font';
3
3
  import { mergeRegion, growRegion, pathToD, pathFromD } from '@weasel-js/geom';
4
4
 
5
5
  var __defProp = Object.defineProperty;
@@ -134,20 +134,24 @@ function registerShaper(shaper) {
134
134
  function registeredShaper() {
135
135
  return registered;
136
136
  }
137
- var bytesOf = /* @__PURE__ */ new WeakMap();
138
- function shapeableBytes(resolved2) {
139
- const face = resolved2.outlineFace;
137
+ var embeddingOf = /* @__PURE__ */ new WeakMap();
138
+ function shapingFace(resolved2) {
139
+ const { family, weight, style } = resolved2.resolved;
140
+ const face = resolved2.outlineFace ?? (resolved2.source === "outline" ? void 0 : outlineMetrics(family, weight, style === "italic" ? "italic" : "normal"));
140
141
  if (!face) return null;
141
- let bytes = bytesOf.get(face);
142
- if (bytes === void 0) {
143
- bytes = face.embedding?.().sfnt ?? null;
144
- bytesOf.set(face, bytes);
142
+ let embedding = embeddingOf.get(face);
143
+ if (embedding === void 0) {
144
+ embedding = face.embedding?.() ?? null;
145
+ embeddingOf.set(face, embedding);
145
146
  }
146
- return bytes;
147
+ return embedding;
148
+ }
149
+ function shapeableBytes(resolved2) {
150
+ return shapingFace(resolved2)?.sfnt ?? null;
147
151
  }
148
152
  function shapesWithFeature(family, weight, style, tag, shaper = registered) {
149
153
  if (shaper === null) return false;
150
- const bytes = shapeableBytes(resolveFontVariant(family, weight, style));
154
+ const bytes = shapeableBytes(cachedFontVariant(family, weight, style));
151
155
  return bytes !== null && shaper.hasFeature(bytes, tag);
152
156
  }
153
157
 
@@ -649,7 +653,7 @@ function within(ranges, cp2) {
649
653
  return false;
650
654
  }
651
655
  var warnedLogicalRtl = false;
652
- function warnRtlWithoutEngine(codePoints) {
656
+ function warnRtlWithoutEngine(codePoints, who = "weasel layoutRuns") {
653
657
  if (warnedLogicalRtl) return;
654
658
  for (let i = 0; i < codePoints.length; i++) {
655
659
  const cp2 = codePoints[i];
@@ -658,7 +662,7 @@ function warnRtlWithoutEngine(codePoints) {
658
662
  console.warn(
659
663
  // Naming the import in `from "<pkg>"` form would trip labkit's
660
664
  // consumer smoke test, which greps dist for that exact shape.
661
- "weasel layoutRuns: this text contains right-to-left characters but no `bidi` engine is registered, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Import `bidi` out of the `@weasel-js/bidi` package and call `registerBidi(bidi)` once at startup."
665
+ `${who}: this text contains right-to-left characters but no \`bidi\` engine is registered, so it is being laid out in logical order \u2014 the glyphs will appear reversed. Import \`bidi\` out of the \`@weasel-js/bidi\` package and call \`registerBidi(bidi)\` once at startup.`
662
666
  );
663
667
  return;
664
668
  }
@@ -1122,19 +1126,23 @@ function graphemeCount(text) {
1122
1126
  var NOTHING_DRAWN = Object.freeze({ d: null, key: "", glyphs: Object.freeze([]), rtl: false, text: "" });
1123
1127
  var COVERED = Object.freeze({ cluster: NOTHING_DRAWN, advance: 0 });
1124
1128
  var slotsOf = /* @__PURE__ */ new WeakMap();
1129
+ var atlasSlotsOf = /* @__PURE__ */ new WeakMap();
1125
1130
  var fmt = (v) => String(Math.round(v * 1e5) / 1e5);
1126
1131
  function shapeRun(run, resolved2, shaper) {
1127
1132
  if (shaper === null) return null;
1128
- const face = shapeableBytes(resolved2);
1129
- if (face === null) return null;
1133
+ const embedding = shapingFace(resolved2);
1134
+ if (embedding === null) return null;
1135
+ const face = embedding.sfnt;
1130
1136
  const glyphs = shaper.shape(face, run.text, run.fontFeatures, run.lang);
1131
- let slots2 = slotsOf.get(glyphs);
1137
+ const cache2 = resolved2.source === "outline" ? slotsOf : atlasSlotsOf;
1138
+ let slots2 = cache2.get(glyphs);
1132
1139
  if (slots2) return slots2;
1133
- slots2 = slotsFor(shaper, face, run.text, glyphs);
1134
- slotsOf.set(glyphs, slots2);
1140
+ const plain2 = resolved2.source === "outline" ? null : (cp2) => embedding.glyphIndexOf(cp2);
1141
+ slots2 = slotsFor(shaper, face, run.text, glyphs, plain2);
1142
+ cache2.set(glyphs, slots2);
1135
1143
  return slots2;
1136
1144
  }
1137
- function slotsFor(shaper, face, text, glyphs) {
1145
+ function slotsFor(shaper, face, text, glyphs, plain2) {
1138
1146
  const slots2 = new Array(text.length);
1139
1147
  const ends = graphemeEnds(text);
1140
1148
  let g = 0;
@@ -1147,7 +1155,7 @@ function slotsFor(shaper, face, text, glyphs) {
1147
1155
  while (g < ends.length && ends[g] <= start2) g++;
1148
1156
  let inner = 0;
1149
1157
  for (let k = g; k < ends.length && ends[k] < end; k++) inner++;
1150
- if (!holdsNotdef(glyphs, i, j)) {
1158
+ if (!holdsNotdef(glyphs, i, j) && !(plain2 && isPlain(glyphs[i], j - i, text, start2, end, plain2))) {
1151
1159
  const total = advanceOf(glyphs, i, j);
1152
1160
  const share = total / (inner + 1);
1153
1161
  const shift = glyphs[i].rtl ? share - total : 0;
@@ -1163,6 +1171,11 @@ function slotsFor(shaper, face, text, glyphs) {
1163
1171
  }
1164
1172
  return slots2;
1165
1173
  }
1174
+ function isPlain(first, count, text, start2, end, plain2) {
1175
+ if (count !== 1 || first.dx !== 0 || first.dy !== 0) return false;
1176
+ const cp2 = text.codePointAt(start2);
1177
+ return end - start2 === (cp2 > 65535 ? 2 : 1) && plain2(cp2) === first.gid;
1178
+ }
1166
1179
  function advanceOf(glyphs, from, to) {
1167
1180
  let sum2 = 0;
1168
1181
  for (let k = from; k < to; k++) sum2 += glyphs[k].advance;
@@ -1180,7 +1193,7 @@ function clusterSlot(shaper, face, glyphs, from, to, text, shift, advance) {
1180
1193
  for (let k = from; k < to; k++) {
1181
1194
  const g = glyphs[k];
1182
1195
  const x = pen + g.dx, y = g.dy;
1183
- placed2.push({ gid: g.gid, x, y });
1196
+ placed2.push({ gid: g.gid, x, y, advance: g.advance });
1184
1197
  d += shaper.glyphD(face, g.gid, x, y) ?? "";
1185
1198
  key += `${k === from ? "" : ";"}${g.gid}${x === 0 && y === 0 ? "" : `@${fmt(x)},${fmt(y)}`}`;
1186
1199
  pen += g.advance;
@@ -1197,7 +1210,7 @@ function measureRuns(runs, tabSize, pool, into, shaper = null) {
1197
1210
  let srcIndex = 0;
1198
1211
  for (let runIndex = 0; runIndex < runs.length; runIndex++) {
1199
1212
  const run = runs[runIndex];
1200
- const resolved2 = resolveFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
1213
+ const resolved2 = cachedFontVariant(run.fontFamily, run.fontWeight, run.fontStyle);
1201
1214
  const metrics = metricsOf(resolved2);
1202
1215
  if (!metrics) {
1203
1216
  warnNoMetrics(run.fontFamily, run.fontWeight, run.fontStyle);
@@ -1359,7 +1372,7 @@ function mirrorEntries(entries2, levels, mirror, runs) {
1359
1372
  const odd = levels[pos] % 2 === 1;
1360
1373
  if (entry.shaped !== null && entry.shaped.rtl !== odd) {
1361
1374
  const flipped = mirror(entry.cp);
1362
- if (flipped !== null && flipped !== entry.cp) entry = { ...entry, shaped: null };
1375
+ if (flipped !== null && flipped !== entry.cp) entry = unshaped(entry, runs[entry.run]);
1363
1376
  }
1364
1377
  if (!odd) return entry;
1365
1378
  if (entry.shaped !== null) return entry;
@@ -1372,6 +1385,11 @@ function mirrorEntries(entries2, levels, mirror, runs) {
1372
1385
  return { ...entry, cp: cp2, resolved: hit.resolved, metrics: hit.metrics, font: hit.font, glyph: hit.glyph };
1373
1386
  });
1374
1387
  }
1388
+ function unshaped(entry, run) {
1389
+ if (entry.resolved.source === "outline") return { ...entry, shaped: null };
1390
+ const hit = resolveGlyph(entry.resolved, run.fontFamily, run.fontWeight, run.fontStyle, entry.cp, false);
1391
+ return hit ? { ...entry, shaped: null, resolved: hit.resolved, metrics: hit.metrics, font: hit.font, glyph: hit.glyph } : { ...entry, shaped: null };
1392
+ }
1375
1393
 
1376
1394
  // src/layout/lineBreak/wrapLines.ts
1377
1395
  var hangs = (cp2) => cp2 === 32 || cp2 === 9;
@@ -1588,7 +1606,7 @@ function resolveLayoutOpts(opts, out = {
1588
1606
  tabSize: 0
1589
1607
  }) {
1590
1608
  out.maxWidth = opts.maxWidth;
1591
- out.alignWidth = opts.alignWidth ?? opts.maxWidth;
1609
+ storeAlignWidth(out, opts);
1592
1610
  out.lineHeight = opts.lineHeight;
1593
1611
  out.align = opts.align;
1594
1612
  out.justify = opts.justify === true || opts.align === "justify";
@@ -1598,6 +1616,10 @@ function resolveLayoutOpts(opts, out = {
1598
1616
  out.tabSize = opts.tabSize ?? DEFAULT_TAB_SIZE;
1599
1617
  return out;
1600
1618
  }
1619
+ function storeAlignWidth(out, opts) {
1620
+ if (opts.alignWidth === void 0) out.alignWidth = opts.maxWidth;
1621
+ else out.alignWidth = opts.alignWidth;
1622
+ }
1601
1623
  function layoutBidi(opts) {
1602
1624
  return opts.bidi ?? registeredBidi();
1603
1625
  }
@@ -1872,9 +1894,9 @@ function takeGroups(out) {
1872
1894
  }
1873
1895
 
1874
1896
  // src/layout/paintRuns.ts
1875
- function glyphOutlineD(g, run, minSize) {
1897
+ function glyphOutlineD(g, run, minSize, ungated = false) {
1876
1898
  const outlineOnly = g.resolved.source === "outline";
1877
- if (!outlineOnly) {
1899
+ if (!outlineOnly && !ungated) {
1878
1900
  if (minSize === void 0) return null;
1879
1901
  if (run.fontSize < minSize && !strokeHasInk(run.stroke)) return null;
1880
1902
  }
@@ -1893,8 +1915,16 @@ function faceOutlineD(g) {
1893
1915
  if (g.resolved.source !== "outline" && g.glyph === null) return null;
1894
1916
  return glyphOutline(family, weight, style, g.cp);
1895
1917
  }
1918
+ function shapedQuadGlyph(g) {
1919
+ const face = g.resolved.dynamicFace;
1920
+ const shaped = g.shaped;
1921
+ if (!face || shaped.d === null) return null;
1922
+ const glyph = face.requestShaped(shaped.key, shaped.d);
1923
+ if (!glyph || glyph.page < 0 || glyph.width === 0) return null;
1924
+ return { ...g, font: face.font, glyph };
1925
+ }
1896
1926
  function runFaces(runs) {
1897
- const faces = runs.map((r) => faceMetricsOf(resolveFontVariant(r.fontFamily, r.fontWeight, r.fontStyle)));
1927
+ const faces = runs.map((r) => faceMetricsOf(cachedFontVariant(r.fontFamily, r.fontWeight, r.fontStyle)));
1898
1928
  return (run) => faces[run];
1899
1929
  }
1900
1930
  var isDecorated = (r) => r.underline || r.strikethrough || r.overline;
@@ -1933,7 +1963,13 @@ function paintRunsInto(laid, runs, opts, out) {
1933
1963
  rules?.add(g);
1934
1964
  const run = runs[g.run];
1935
1965
  const inks = cols !== null ? cols.drawsInk(start2, pos) : line.cells[g.cell].drawsInk;
1936
- const outline = inks ? glyphOutlineD(g, run, opts.outlineMinSize) : null;
1966
+ let outline = inks ? glyphOutlineD(g, run, opts.outlineMinSize) : null;
1967
+ let q = g;
1968
+ if (inks && outline === null && g.shaped) {
1969
+ const baked = shapedQuadGlyph(g);
1970
+ if (baked) q = baked;
1971
+ else outline = glyphOutlineD(g, run, opts.outlineMinSize, true);
1972
+ }
1937
1973
  if (!fillHasInk(run.fill) && !(outline !== null && strokeHasInk(run.stroke))) continue;
1938
1974
  if (outline !== null) {
1939
1975
  const placed2 = { ...outline, x: g.x, baselineY: g.baselineY, scale: g.fontSize };
@@ -1941,8 +1977,8 @@ function paintRunsInto(laid, runs, opts, out) {
1941
1977
  groupFor(runs, g, "outline").glyphs.push(placed2);
1942
1978
  continue;
1943
1979
  }
1944
- if (!hasGlyphQuad(g)) continue;
1945
- const group = groupFor(runs, g, g.glyph.color ? "color" : g.resolved.source);
1980
+ if (!hasGlyphQuad(q)) continue;
1981
+ const group = groupFor(runs, q, q.glyph.color ? "color" : q.resolved.source);
1946
1982
  if (sized === null) {
1947
1983
  sized = group;
1948
1984
  group.quadData = buf;
@@ -1950,12 +1986,12 @@ function paintRunsInto(laid, runs, opts, out) {
1950
1986
  let quads = group.quadData;
1951
1987
  if (quads === NO_QUADS) group.quadData = quads = [];
1952
1988
  if (group === sized) {
1953
- writeGlyphQuad(g, quads, sizedAt);
1989
+ writeGlyphQuad(q, quads, sizedAt);
1954
1990
  sizedAt += QUAD_STRIDE;
1955
1991
  } else {
1956
- writeGlyphQuad(g, quads, quads.length);
1992
+ writeGlyphQuad(q, quads, quads.length);
1957
1993
  }
1958
- group.colorGlyphs?.push(g.glyph);
1994
+ group.colorGlyphs?.push(q.glyph);
1959
1995
  }
1960
1996
  if (!rules) continue;
1961
1997
  for (const rule of rules.end()) {
@@ -1990,7 +2026,8 @@ function entryFor(runs) {
1990
2026
  painting: null,
1991
2027
  paintedOn: null,
1992
2028
  paintedStamp: 0,
1993
- paintMin: void 0
2029
+ paintMin: void 0,
2030
+ listed: null
1994
2031
  };
1995
2032
  entries.set(runs, e);
1996
2033
  }
@@ -2001,11 +2038,15 @@ function dropEntry(runs) {
2001
2038
  if (e !== void 0) entries.delete(runs);
2002
2039
  return e;
2003
2040
  }
2004
- function giveEntry(e) {
2041
+ function emptyEntry(e) {
2005
2042
  e.generation = -1;
2006
2043
  e.painting = null;
2007
2044
  e.paintedOn = null;
2008
2045
  e.paintMin = void 0;
2046
+ e.listed = null;
2047
+ }
2048
+ function giveEntry(e) {
2049
+ emptyEntry(e);
2009
2050
  if (spare2.length < SPARE_MAX2) spare2.push(e);
2010
2051
  }
2011
2052
  function _resetRunsEntriesForTests() {
@@ -2104,12 +2145,16 @@ function cachedPaintRuns(laid, runs, opts = {}, use = "keep") {
2104
2145
  if (list.length >= PAINT_CACHE_VARIANT_LIMIT) list.shift();
2105
2146
  list.push(p);
2106
2147
  const e = mine ?? entryFor(runs);
2148
+ if (e.painting !== null && e.painting !== p) settlePainting(e.painting, runs);
2107
2149
  e.paintedOn = laid;
2108
2150
  e.paintedStamp = stamp;
2109
2151
  e.painting = p;
2110
2152
  e.paintMin = min;
2111
2153
  return handOut(p, use);
2112
2154
  }
2155
+ function settlePainting(p, runs) {
2156
+ if (p.runs === runs) keyOf(p);
2157
+ }
2113
2158
  function handOut(p, use) {
2114
2159
  if (use === "keep") p.pinned = true;
2115
2160
  return p.painted;
@@ -2183,7 +2228,7 @@ function disown(vs) {
2183
2228
  var NO_LAYOUT = { lines: [], bounds: { width: 0, height: 0 } };
2184
2229
  function fill(v, opts, bidi, laid) {
2185
2230
  v.maxWidth = opts.maxWidth;
2186
- v.alignWidth = opts.alignWidth ?? opts.maxWidth;
2231
+ storeAlignWidth(v, opts);
2187
2232
  v.lineHeight = opts.lineHeight;
2188
2233
  v.align = opts.align;
2189
2234
  v.justify = opts.justify === true || opts.align === "justify";
@@ -2197,9 +2242,9 @@ function fill(v, opts, bidi, laid) {
2197
2242
  function variant(opts, bidi, laid) {
2198
2243
  const spare4 = spareVariants.pop();
2199
2244
  if (spare4 !== void 0) return fill(spare4, opts, bidi, laid);
2200
- return {
2245
+ const v = {
2201
2246
  maxWidth: opts.maxWidth,
2202
- alignWidth: opts.alignWidth ?? opts.maxWidth,
2247
+ alignWidth: opts.maxWidth,
2203
2248
  lineHeight: opts.lineHeight,
2204
2249
  align: opts.align,
2205
2250
  justify: opts.justify === true || opts.align === "justify",
@@ -2209,6 +2254,8 @@ function variant(opts, bidi, laid) {
2209
2254
  shaper: layoutShaper(opts),
2210
2255
  laid
2211
2256
  };
2257
+ storeAlignWidth(v, opts);
2258
+ return v;
2212
2259
  }
2213
2260
  function mix(h, n) {
2214
2261
  return Math.imul(h ^ n, 16777619);
@@ -2279,7 +2326,7 @@ function cachedLayoutRuns(runs, opts, use = "keep") {
2279
2326
  return handOut2(v.laid, use);
2280
2327
  }
2281
2328
  }
2282
- const laid = structuralLayout(runs, opts, bidi, fonts);
2329
+ const laid = structuralLayout(runs, opts, bidi, fonts, entry);
2283
2330
  const vs = entry.variants;
2284
2331
  if (vs.length >= LAYOUT_CACHE_VARIANT_LIMIT) disown(vs);
2285
2332
  vs.unshift(variant(opts, bidi, laid));
@@ -2293,9 +2340,12 @@ function dropRuns(runs) {
2293
2340
  disown(entry.variants);
2294
2341
  giveEntry(entry);
2295
2342
  }
2296
- function structuralLayout(runs, opts, bidi, fonts) {
2343
+ function structuralLayout(runs, opts, bidi, fonts, entry) {
2297
2344
  if (structuralGeneration !== fonts) {
2298
- for (const s of structural) unlist(s);
2345
+ for (const s of structural) {
2346
+ unlist(s);
2347
+ s.runs = NO_RUNS;
2348
+ }
2299
2349
  structural = [];
2300
2350
  structuralGeneration = fonts;
2301
2351
  }
@@ -2316,11 +2366,29 @@ function structuralLayout(runs, opts, bidi, fonts) {
2316
2366
  filled.hash = hash;
2317
2367
  filled.runs = runs;
2318
2368
  filled.used = ++clock;
2369
+ entry.listed = filled;
2319
2370
  const cols = columnsOf(laid);
2320
2371
  if (cols !== null) cols.listed = true;
2321
2372
  if (!full) structural.push(filled);
2322
2373
  return laid;
2323
2374
  }
2375
+ function clearRuns(runs) {
2376
+ const entry = entryOf(runs);
2377
+ if (entry === void 0) return;
2378
+ const s = entry.listed;
2379
+ if (s !== null && s.runs === runs) {
2380
+ unlist(s);
2381
+ s.laid = NO_LAYOUT;
2382
+ s.runs = NO_RUNS;
2383
+ s.hash = -1;
2384
+ s.used = 0;
2385
+ }
2386
+ const p = entry.painting;
2387
+ disown(entry.variants);
2388
+ if (p !== null) settlePainting(p, runs);
2389
+ emptyEntry(entry);
2390
+ }
2391
+ var NO_RUNS = [];
2324
2392
  function structuralEntry(opts, bidi, laid) {
2325
2393
  return fill({
2326
2394
  maxWidth: 0,
@@ -2351,6 +2419,6 @@ function _resetLayoutCacheForTests() {
2351
2419
  clock = 0;
2352
2420
  }
2353
2421
 
2354
- export { BREAK_ALLOWED, BREAK_MANDATORY, DECORATION_KINDS, DEFAULT_DECORATION_METRICS, DEFAULT_TAB_SIZE, DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, QUAD_STRIDE, _resetBidiWarningForTests, _resetLayoutCacheForTests, cachedLayoutRuns, cachedPaintRuns, decorationMetrics, decorationRule, dropRuns, emboldenGlyph, emboldenGlyphD, faceOutlineD, fontString, graphemeCount, hangs, hasGlyphQuad, isHardLineBreak, layoutRuns, lineBreakOpportunities, lineDecorations, paintRuns, paragraphDirection, registerBidi, registerShaper, resolveAlign, resolveTextStyle, resolveTextStyleInto, runFaces, shapesWithFeature, syntheticBoldOffset, tabAdvance, widthWithTabs, wrapLines };
2355
- //# sourceMappingURL=chunk-5BJN6MC6.js.map
2356
- //# sourceMappingURL=chunk-5BJN6MC6.js.map
2422
+ export { BREAK_ALLOWED, BREAK_MANDATORY, DECORATION_KINDS, DEFAULT_DECORATION_METRICS, DEFAULT_TAB_SIZE, DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, QUAD_STRIDE, _resetBidiWarningForTests, _resetLayoutCacheForTests, cachedLayoutRuns, cachedPaintRuns, clearRuns, decorationMetrics, decorationRule, dropRuns, emboldenGlyph, emboldenGlyphD, faceOutlineD, fontString, graphemeCount, hangs, hasGlyphQuad, isHardLineBreak, layoutRuns, lineBreakOpportunities, lineDecorations, paintRuns, paragraphDirection, registerBidi, registerShaper, registeredBidi, resolveAlign, resolveTextStyle, resolveTextStyleInto, runFaces, shapesWithFeature, syntheticBoldOffset, tabAdvance, warnRtlWithoutEngine, widthWithTabs, wrapLines };
2423
+ //# sourceMappingURL=chunk-WUFANHTE.js.map
2424
+ //# sourceMappingURL=chunk-WUFANHTE.js.map