@weasel-js/text 1.11.1 → 1.13.0-rc.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/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { DEFAULT_TAB_SIZE, resolveTextStyle, cachedLayoutRuns, graphemeCount, wrapLines, hangs, tabAdvance, widthWithTabs, layoutRuns } from './chunk-JOVZEHBJ.js';
2
- export { BREAK_ALLOWED, BREAK_MANDATORY, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, cachedLayoutRuns, fontString, isHardLineBreak, layoutRuns, lineBreakOpportunities, paragraphDirection, registerBidi, resolveAlign, resolveTextStyle } from './chunk-JOVZEHBJ.js';
1
+ import { DEFAULT_TAB_SIZE, resolveTextStyle, cachedLayoutRuns, graphemeCount, wrapLines, hangs, layoutRuns, tabAdvance, widthWithTabs } from './chunk-ADRQIIHS.js';
2
+ export { BREAK_ALLOWED, BREAK_MANDATORY, DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, cachedLayoutRuns, fontString, isHardLineBreak, layoutRuns, lineBreakOpportunities, paragraphDirection, registerBidi, resolveAlign, resolveTextStyle } from './chunk-ADRQIIHS.js';
3
3
  import { resolveScreenLength, fillHasInk, strokeHasInk, paintKey, strokeKey } from '@weasel-js/paint';
4
4
  import { faceMetricsOf, resolveFontVariant, syntheticBoldGrowth, glyphGeneration, loadFontOutlines, faceMetricsFor, glyphOutline, outlineStatus, cssFamilyName } from '@weasel-js/font';
5
- import { pathToD, pathFromD, mergeRegion, growRegion, transformPath, poseFrameOf, pathSignedArea, reversePath, PATH_M, PATH_L, PATH_Z } from '@weasel-js/geom';
5
+ import { pathToD, pathFromD, mergeRegion, growRegion, poseFrameOf, transformPath, PATH_M, PATH_L, PATH_Z, pathSignedArea, reversePath } from '@weasel-js/geom';
6
6
  import { MIXED, deepEqual } from '@weasel-js/registry';
7
7
  export { MIXED } from '@weasel-js/registry';
8
8
 
@@ -1307,8 +1307,8 @@ var TextOutlinesError = class extends Error {
1307
1307
  family;
1308
1308
  weight;
1309
1309
  style;
1310
- constructor(reason, family, weight, style, detail) {
1311
- super(`textToPath: "${family}" ${weight}/${style} \u2014 ${detail}`);
1310
+ constructor(reason, family, weight, style, detail, caller = "textToPath") {
1311
+ super(`${caller}: "${family}" ${weight}/${style} \u2014 ${detail}`);
1312
1312
  this.name = "TextOutlinesError";
1313
1313
  this.reason = reason;
1314
1314
  this.family = family;
@@ -1316,22 +1316,10 @@ var TextOutlinesError = class extends Error {
1316
1316
  this.style = style;
1317
1317
  }
1318
1318
  };
1319
- function facesOf(runs) {
1320
- const out = /* @__PURE__ */ new Map();
1321
- const add = (family, weight, style) => {
1322
- out.set(`${family}|${weight}|${style}`, { family, weight, style });
1323
- };
1324
- for (const r of runs) {
1325
- add(r.fontFamily, r.fontWeight, r.fontStyle);
1326
- const { family, weight, style } = resolveFontVariant(r.fontFamily, r.fontWeight, r.fontStyle).resolved;
1327
- add(family, weight, style);
1328
- }
1329
- return [...out.values()];
1330
- }
1331
- function assertOutlinable(run) {
1319
+ function assertOutlinable(run, caller) {
1332
1320
  const { fontFamily, fontWeight, fontStyle } = run;
1333
1321
  const fail = (reason, detail) => {
1334
- throw new TextOutlinesError(reason, fontFamily, fontWeight, fontStyle, detail);
1322
+ throw new TextOutlinesError(reason, fontFamily, fontWeight, fontStyle, detail, caller);
1335
1323
  };
1336
1324
  const requested = outlineStatus(fontFamily, fontWeight, fontStyle);
1337
1325
  if (requested === "idle" || requested === "loading") {
@@ -1353,6 +1341,21 @@ function assertOutlinable(run) {
1353
1341
  fail("outlines-failed", `the outlines registered for ${family} ${weight}/${style} failed to load.`);
1354
1342
  }
1355
1343
  }
1344
+ function paintsOf(runs) {
1345
+ const paints = [];
1346
+ const indexOf = /* @__PURE__ */ new Map();
1347
+ const paintOf = runs.map((run) => {
1348
+ const key = `${paintKey(run.fill?.paint)}|${strokeKey(run.stroke)}`;
1349
+ let i = indexOf.get(key);
1350
+ if (i === void 0) {
1351
+ i = paints.length;
1352
+ indexOf.set(key, i);
1353
+ paints.push(run.stroke !== void 0 ? { fill: run.fill, stroke: run.stroke } : { fill: run.fill });
1354
+ }
1355
+ return i;
1356
+ });
1357
+ return { paints, paintOf };
1358
+ }
1356
1359
  var EM_CACHE_LIMIT = 4096;
1357
1360
  var emCache = /* @__PURE__ */ new Map();
1358
1361
  function emGlyph(d) {
@@ -1364,6 +1367,37 @@ function emGlyph(d) {
1364
1367
  emCache.set(d, p);
1365
1368
  return p;
1366
1369
  }
1370
+ function placedGlyphOutline(g, dx, dy, caller, blankFallback = false) {
1371
+ const d = faceOutlineD(g);
1372
+ if (d === null) {
1373
+ if (!blankFallback && glyphQuad(g) !== null) {
1374
+ const { family, weight, style } = g.resolved.resolved;
1375
+ throw new TextOutlinesError(
1376
+ "no-outlines",
1377
+ family,
1378
+ weight,
1379
+ style,
1380
+ "a fallback face serving some of the text's characters has no outlines registered.",
1381
+ caller
1382
+ );
1383
+ }
1384
+ return null;
1385
+ }
1386
+ const s = g.fontSize;
1387
+ const shear = g.resolved.synthetic.italic ? Math.tan(SYNTHETIC_ITALIC_RADIANS) : 0;
1388
+ const m = [s, 0, -shear * s, s, g.x + dx, g.baselineY + dy];
1389
+ return transformPath(emboldenGlyph(emGlyph(d), d, g.resolved), m);
1390
+ }
1391
+ function colorGlyphError(run, caller) {
1392
+ return new TextOutlinesError(
1393
+ "color-glyph",
1394
+ run.fontFamily,
1395
+ run.fontWeight,
1396
+ run.fontStyle,
1397
+ "the text holds an emoji, which is drawn in its own colors and has no outline.",
1398
+ caller
1399
+ );
1400
+ }
1367
1401
  function concat(parts) {
1368
1402
  let nc = 0, nk = 0;
1369
1403
  for (const p of parts) {
@@ -1381,20 +1415,26 @@ function concat(parts) {
1381
1415
  }
1382
1416
  return { kind: "polygon", commands, coords, fillRule: "nonzero" };
1383
1417
  }
1384
- function ruleContour(x0, y0, x1, y1) {
1385
- return {
1386
- kind: "polygon",
1387
- commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),
1388
- coords: new Float32Array([x0, y0, x1, y0, x1, y1, x0, y1]),
1389
- fillRule: "nonzero"
1418
+
1419
+ // src/textToPath.ts
1420
+ function facesOf(runs) {
1421
+ const out = /* @__PURE__ */ new Map();
1422
+ const add = (family, weight, style) => {
1423
+ out.set(`${family}|${weight}|${style}`, { family, weight, style });
1390
1424
  };
1425
+ for (const r of runs) {
1426
+ add(r.fontFamily, r.fontWeight, r.fontStyle);
1427
+ const { family, weight, style } = resolveFontVariant(r.fontFamily, r.fontWeight, r.fontStyle).resolved;
1428
+ add(family, weight, style);
1429
+ }
1430
+ return [...out.values()];
1391
1431
  }
1392
- function walkOutlines(data, pose, opts) {
1432
+ function outlineLayoutInput(data, box, opts, caller) {
1393
1433
  const textPose = {
1394
- x: pose.x,
1395
- y: pose.y,
1396
- width: pose.width,
1397
- height: pose.height,
1434
+ x: box.x,
1435
+ y: box.y,
1436
+ width: box.width,
1437
+ height: box.height,
1398
1438
  text: data.text,
1399
1439
  runs: data.runs,
1400
1440
  style: data.style,
@@ -1402,20 +1442,21 @@ function walkOutlines(data, pose, opts) {
1402
1442
  fill: data.fill,
1403
1443
  stroke: data.stroke
1404
1444
  };
1405
- const { runs, opts: layoutOpts } = textPoseLayoutInput(textPose, opts.scale ?? 1);
1406
- for (const run of runs) assertOutlinable(run);
1407
- const paints = [];
1408
- const indexOf = /* @__PURE__ */ new Map();
1409
- const paintOf = runs.map((run) => {
1410
- const key = `${paintKey(run.fill?.paint)}|${strokeKey(run.stroke)}`;
1411
- let i = indexOf.get(key);
1412
- if (i === void 0) {
1413
- i = paints.length;
1414
- indexOf.set(key, i);
1415
- paints.push(run.stroke !== void 0 ? { fill: run.fill, stroke: run.stroke } : { fill: run.fill });
1416
- }
1417
- return i;
1418
- });
1445
+ const input = textPoseLayoutInput(textPose, opts.scale ?? 1);
1446
+ for (const run of input.runs) assertOutlinable(run, caller);
1447
+ return input;
1448
+ }
1449
+ function ruleContour(x0, y0, x1, y1) {
1450
+ return {
1451
+ kind: "polygon",
1452
+ commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),
1453
+ coords: new Float32Array([x0, y0, x1, y0, x1, y1, x0, y1]),
1454
+ fillRule: "nonzero"
1455
+ };
1456
+ }
1457
+ function walkOutlines(data, pose, opts) {
1458
+ const { runs, opts: layoutOpts } = outlineLayoutInput(data, pose, opts, "textToPath");
1459
+ const { paints, paintOf } = paintsOf(runs);
1419
1460
  const laid = layoutRuns(runs, layoutOpts);
1420
1461
  const dx = pose.x;
1421
1462
  const dy = pose.y + verticalAlignOffset(data.verticalAlign, pose.height, laid.bounds.height);
@@ -1424,34 +1465,9 @@ function walkOutlines(data, pose, opts) {
1424
1465
  const faceOf = runFaces(runs);
1425
1466
  for (const line of laid.lines) {
1426
1467
  for (const g of line.glyphs) {
1427
- if (g.glyph?.color) {
1428
- const { fontFamily, fontWeight, fontStyle } = runs[g.run];
1429
- throw new TextOutlinesError(
1430
- "color-glyph",
1431
- fontFamily,
1432
- fontWeight,
1433
- fontStyle,
1434
- "the text holds an emoji, which is drawn in its own colors and has no outline."
1435
- );
1436
- }
1437
- const d = faceOutlineD(g);
1438
- if (d === null) {
1439
- if (glyphQuad(g) !== null) {
1440
- const { family, weight, style } = g.resolved.resolved;
1441
- throw new TextOutlinesError(
1442
- "no-outlines",
1443
- family,
1444
- weight,
1445
- style,
1446
- "a fallback face serving some of the text's characters has no outlines registered."
1447
- );
1448
- }
1449
- continue;
1450
- }
1451
- const s = g.fontSize;
1452
- const shear = g.resolved.synthetic.italic ? Math.tan(SYNTHETIC_ITALIC_RADIANS) : 0;
1453
- const m = [s, 0, -shear * s, s, g.x + dx, g.baselineY + dy];
1454
- parts[paintOf[g.run]].push(transformPath(emboldenGlyph(emGlyph(d), d, g.resolved), m));
1468
+ if (g.glyph?.color) throw colorGlyphError(runs[g.run], "textToPath");
1469
+ const glyph = placedGlyphOutline(g, dx, dy, "textToPath");
1470
+ if (glyph) parts[paintOf[g.run]].push(glyph);
1455
1471
  }
1456
1472
  for (const r of lineDecorations(line.glyphs, runs, faceOf, (run) => String(paintOf[run]))) {
1457
1473
  rules[paintOf[r.run]].push(ruleContour(r.x0 + dx, r.y0 + dy, r.x1 + dx, r.y1 + dy));
@@ -1487,7 +1503,48 @@ async function loadTextOutlines(data, opts = {}) {
1487
1503
  await Promise.all(facesOf(runs).map((f) => loadFontOutlines(f.family, { weight: f.weight, style: f.style })));
1488
1504
  await Promise.all(facesOf(runs).map((f) => loadFontOutlines(f.family, { weight: f.weight, style: f.style })));
1489
1505
  }
1506
+ var MIDDLE_FALLBACK = 0.25;
1507
+ function textClusters(data, opts = {}) {
1508
+ const box = { x: 0, y: 0, width: opts.width ?? Infinity, height: 0 };
1509
+ const { runs, opts: layoutOpts } = outlineLayoutInput(data, box, opts, "textClusters");
1510
+ const line = layoutRuns(runs, layoutOpts).lines[0];
1511
+ if (!line || line.cells.length === 0) return [];
1512
+ const { paints, paintOf } = paintsOf(runs);
1513
+ const glyphOf = new Array(line.cells.length);
1514
+ for (const g of line.glyphs) glyphOf[g.cell] = g;
1515
+ const groups = [];
1516
+ let open = [];
1517
+ line.cells.forEach((cell, i) => {
1518
+ open.push(glyphOf[i]);
1519
+ if (cell.clusterEnd || i === line.cells.length - 1) {
1520
+ groups.push(open);
1521
+ open = [];
1522
+ }
1523
+ });
1524
+ const slots = groups.map((glyphs) => ({ glyphs, left: Math.min(...glyphs.map((g) => g.x)) })).sort((a, b) => a.left - b.left);
1525
+ return slots.map(({ glyphs, left }, i) => {
1526
+ const right = i + 1 < slots.length ? slots[i + 1].left : line.x1;
1527
+ const parts = paints.map(() => []);
1528
+ for (const g of glyphs) {
1529
+ if (g.glyph?.color || !line.cells[g.cell].drawsInk) continue;
1530
+ const outline = placedGlyphOutline(g, -left, -line.baselineY, "textClusters", true);
1531
+ if (outline) parts[paintOf[g.run]].push(outline);
1532
+ }
1533
+ const paths = [];
1534
+ parts.forEach((p, k) => {
1535
+ if (p.length > 0) paths.push({ ...paints[k], path: concat(p) });
1536
+ });
1537
+ const first = glyphs[0];
1538
+ const xHeight = faceMetricsOf(first.resolved)?.xHeight;
1539
+ return {
1540
+ x: left - line.x0,
1541
+ advance: right - left,
1542
+ paths,
1543
+ middle: (xHeight === void 0 ? MIDDLE_FALLBACK : xHeight / 2) * first.fontSize
1544
+ };
1545
+ });
1546
+ }
1490
1547
 
1491
- export { DEFAULT_DECORATION_METRICS, MARKDOWN_RUN_GRAMMAR, SCRIPT_METRICS, SMALL_CAPS_SCALE, SYNTHETIC_ITALIC_RADIANS, TextOutlinesError, applyStyleToRange, cachedPaintRuns, caretIndexAt, createMarkdownRenderer, decorationMetrics, defaultLabelTextRenderer, effectiveRangeStyle, emboldenGlyphD, fitTextPose, isBoldWeight, isSmallCapsLetter, isTextData, layoutMarkdown, layoutTextPose, loadTextOutlines, markdownToRuns, measureText, measureTextBounds, measuredWidth, nodeHasFlag, numericWeight, paintRuns, patchRangeStyle, pointInTextPose, rangeWeight, renderLabel, resolveRunFace, resolveRuns, runsCarryStyling, runsToMarkdown, runsToPlainText, scriptMetrics, scriptMetricsFor, setFlagOverRange, smallCapsScale, smallCapsScaleFor, smallCapsText, styleAtRange, supersededKey, syntheticBoldOffset, textLineBoxes, textPoseLayoutInput, textToPath, textToPathsByPaint, toRuns, transformRunTexts, unboldPatch, verticalAlignOffset };
1548
+ export { DEFAULT_DECORATION_METRICS, MARKDOWN_RUN_GRAMMAR, SCRIPT_METRICS, SMALL_CAPS_SCALE, SYNTHETIC_ITALIC_RADIANS, TextOutlinesError, applyStyleToRange, cachedPaintRuns, caretIndexAt, createMarkdownRenderer, decorationMetrics, defaultLabelTextRenderer, effectiveRangeStyle, emboldenGlyphD, fitTextPose, isBoldWeight, isSmallCapsLetter, isTextData, layoutMarkdown, layoutTextPose, loadTextOutlines, markdownToRuns, measureText, measureTextBounds, measuredWidth, nodeHasFlag, numericWeight, paintRuns, patchRangeStyle, pointInTextPose, rangeWeight, renderLabel, resolveRunFace, resolveRuns, runsCarryStyling, runsToMarkdown, runsToPlainText, scriptMetrics, scriptMetricsFor, setFlagOverRange, smallCapsScale, smallCapsScaleFor, smallCapsText, styleAtRange, supersededKey, syntheticBoldOffset, textClusters, textLineBoxes, textPoseLayoutInput, textToPath, textToPathsByPaint, toRuns, transformRunTexts, unboldPatch, verticalAlignOffset };
1492
1549
  //# sourceMappingURL=index.js.map
1493
1550
  //# sourceMappingURL=index.js.map