@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/{chunk-JOVZEHBJ.js → chunk-ADRQIIHS.js} +9 -7
- package/dist/chunk-ADRQIIHS.js.map +1 -0
- package/dist/index.d.ts +66 -25
- package/dist/index.js +130 -73
- package/dist/index.js.map +1 -1
- package/dist/{test-seams-BypGSlZj.d.ts → test-seams-B0BDm36M.d.ts} +11 -8
- package/dist/test-seams.d.ts +1 -1
- package/dist/test-seams.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-JOVZEHBJ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { DEFAULT_TAB_SIZE, resolveTextStyle, cachedLayoutRuns, graphemeCount, wrapLines, hangs, tabAdvance, widthWithTabs
|
|
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-
|
|
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,
|
|
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(
|
|
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
|
|
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
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
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
|
|
1432
|
+
function outlineLayoutInput(data, box, opts, caller) {
|
|
1393
1433
|
const textPose = {
|
|
1394
|
-
x:
|
|
1395
|
-
y:
|
|
1396
|
-
width:
|
|
1397
|
-
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
|
|
1406
|
-
for (const run of runs) assertOutlinable(run);
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
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
|
-
|
|
1429
|
-
|
|
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
|