@sushichan044/kg-core 0.6.3 → 0.6.4

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 (4) hide show
  1. package/README.md +18 -8
  2. package/dist/index.d.mts +24503 -20300
  3. package/dist/index.mjs +627 -155
  4. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -1004,7 +1004,7 @@ function composeManuscript(parsed, options) {
1004
1004
  }
1005
1005
  //#endregion
1006
1006
  //#region src/composer/composed-annotation-fragment.ts
1007
- const emLength$1 = () => v.pipe(v.number(), v.finite(), v.minValue(0));
1007
+ const emLength = () => v.pipe(v.number(), v.finite(), v.minValue(0));
1008
1008
  const continuation = v.union([
1009
1009
  v.literal("whole"),
1010
1010
  v.literal("start"),
@@ -1014,7 +1014,7 @@ const continuation = v.union([
1014
1014
  const PositionedReadingGraphemeSchema = readonlyObject({
1015
1015
  value: v.string(),
1016
1016
  offsetEm: v.pipe(v.number(), v.finite()),
1017
- advanceEm: emLength$1()
1017
+ advanceEm: emLength()
1018
1018
  });
1019
1019
  const common$1 = {
1020
1020
  annotationRange: ManuscriptRange.schema,
@@ -1039,8 +1039,8 @@ const ComposedAnnotationFragment = { schema: v.variant("kind", [
1039
1039
  kind: v.literal("ruby"),
1040
1040
  rubyKind: RubyKind.schema,
1041
1041
  reading: v.string(),
1042
- baseOffsetEm: emLength$1(),
1043
- baseAdvanceEm: emLength$1(),
1042
+ baseOffsetEm: emLength(),
1043
+ baseAdvanceEm: emLength(),
1044
1044
  readingGraphemes: readonlyArray(PositionedReadingGraphemeSchema),
1045
1045
  ...common$1
1046
1046
  })
@@ -1139,8 +1139,88 @@ const CompositionStatistics = { schema: readonlyObject({
1139
1139
  sourceLines: nonNegativeInteger(),
1140
1140
  pages: nonNegativeInteger()
1141
1141
  }) };
1142
+ const InlineSpan = { schema: readonlyObject({
1143
+ offsetEm: v.pipe(v.number(), v.finite()),
1144
+ advanceEm: v.pipe(v.number(), v.finite(), v.minValue(0))
1145
+ }) };
1146
+ //#endregion
1147
+ //#region src/composer/vertical-text-presentation.ts
1148
+ const common = { groupRange: ManuscriptRange.schema };
1149
+ const VerticalTextPresentation = { schema: v.variant("kind", [
1150
+ readonlyObject({
1151
+ kind: v.literal("mixed"),
1152
+ ...common
1153
+ }),
1154
+ readonlyObject({
1155
+ kind: v.literal("upright"),
1156
+ ...common
1157
+ }),
1158
+ readonlyObject({
1159
+ kind: v.literal("sideways"),
1160
+ ...common
1161
+ }),
1162
+ readonlyObject({
1163
+ kind: v.literal("tate-chu-yoko"),
1164
+ ...common
1165
+ })
1166
+ ]) };
1142
1167
  //#endregion
1143
- //#region src/composer/inline-measurer.ts
1168
+ //#region src/composer/composed-inline-item.ts
1169
+ const signedEm = () => v.pipe(v.number(), v.finite());
1170
+ const GlyphItemSchema = readonlyObject({
1171
+ kind: v.literal("glyph"),
1172
+ value: v.string(),
1173
+ range: ManuscriptRange.schema,
1174
+ layoutSpan: InlineSpan.schema,
1175
+ renderSpan: InlineSpan.schema,
1176
+ disposition: v.union([v.literal("placed"), v.literal("hanging")]),
1177
+ presentation: VerticalTextPresentation.schema
1178
+ });
1179
+ const GeneratedGlueItemSchema = readonlyObject({
1180
+ kind: v.literal("glue"),
1181
+ origin: v.literal("generated"),
1182
+ offsetEm: signedEm(),
1183
+ widthEm: signedEm(),
1184
+ naturalWidthEm: signedEm(),
1185
+ adjustment: v.union([
1186
+ v.literal("natural"),
1187
+ v.literal("shrunk"),
1188
+ v.literal("stretched")
1189
+ ])
1190
+ });
1191
+ const SourceGlueItemSchema = readonlyObject({
1192
+ kind: v.literal("glue"),
1193
+ origin: v.literal("source"),
1194
+ value: v.string(),
1195
+ range: ManuscriptRange.schema,
1196
+ offsetEm: signedEm(),
1197
+ widthEm: signedEm(),
1198
+ naturalWidthEm: signedEm(),
1199
+ adjustment: v.union([
1200
+ v.literal("natural"),
1201
+ v.literal("shrunk"),
1202
+ v.literal("stretched")
1203
+ ])
1204
+ });
1205
+ const KernItemSchema = readonlyObject({
1206
+ kind: v.literal("kern"),
1207
+ offsetEm: signedEm(),
1208
+ widthEm: signedEm()
1209
+ });
1210
+ const SuppressedItemSchema = readonlyObject({
1211
+ kind: v.literal("suppressed"),
1212
+ value: v.string(),
1213
+ range: ManuscriptRange.schema,
1214
+ reason: v.literal("question-or-exclamation-gap")
1215
+ });
1216
+ const ComposedInlineItem = { schema: v.union([
1217
+ GlyphItemSchema,
1218
+ GeneratedGlueItemSchema,
1219
+ SourceGlueItemSchema,
1220
+ KernItemSchema,
1221
+ SuppressedItemSchema
1222
+ ]) };
1223
+ const InlineMeasurement = { schema: readonlyObject({ advanceEm: v.pipe(v.number(), v.finite(), v.minValue(0)) }) };
1144
1224
  function graphemeWidth(grapheme) {
1145
1225
  let width = 1;
1146
1226
  for (const character of grapheme) {
@@ -1155,9 +1235,17 @@ function graphemeWidth(grapheme) {
1155
1235
  const logicalInlineMeasurer = (request) => {
1156
1236
  const { text } = request;
1157
1237
  const width = [...graphemeSegmenter.segment(text)].reduce((total, { segment }) => total + graphemeWidth(segment), 0);
1158
- if (request.role === "ruby") return width / 2;
1159
- return request.presentation === "upright" ? [...graphemeSegmenter.segment(text)].length : width;
1238
+ if (request.role === "ruby") return { advanceEm: width / 2 };
1239
+ return { advanceEm: request.presentation === "upright" ? [...graphemeSegmenter.segment(text)].length : width };
1160
1240
  };
1241
+ const LineBreakResult = { schema: v.variant("kind", [
1242
+ readonlyObject({ kind: v.literal("natural") }),
1243
+ readonlyObject({ kind: v.literal("shrunk") }),
1244
+ readonlyObject({ kind: v.literal("stretched") }),
1245
+ readonlyObject({ kind: v.literal("hanging") }),
1246
+ readonlyObject({ kind: v.literal("forced") }),
1247
+ readonlyObject({ kind: v.literal("paragraph-end") })
1248
+ ]) };
1161
1249
  //#endregion
1162
1250
  //#region src/composer/manuscript-geometry.ts
1163
1251
  const STAGE_GAP_CELLS = 2;
@@ -1298,57 +1386,413 @@ function questionOrExclamationSpacings(line) {
1298
1386
  return [...line.matchAll(MARKS)].map((marks) => analyzeMatch(line, marks)).filter((spacing) => spacing !== void 0);
1299
1387
  }
1300
1388
  //#endregion
1301
- //#region src/composer/vertical-text-presentation.ts
1302
- const common = { groupRange: ManuscriptRange.schema };
1303
- const VerticalTextPresentation = { schema: v.variant("kind", [
1304
- readonlyObject({
1305
- kind: v.literal("mixed"),
1306
- ...common
1307
- }),
1308
- readonlyObject({
1309
- kind: v.literal("upright"),
1310
- ...common
1311
- }),
1312
- readonlyObject({
1313
- kind: v.literal("sideways"),
1314
- ...common
1389
+ //#region src/composer/internal/japanese-typesetting-profile.ts
1390
+ const OPENING = /* @__PURE__ */ new Set(`${OPENING_BRACKETS}【〘〝⦅«`);
1391
+ const CLOSING = /* @__PURE__ */ new Set(`${CLOSING_BRACKETS}】〙〟⦆»`);
1392
+ const HYPHENS = /* @__PURE__ */ new Set("‐‑⁃–");
1393
+ const DIVIDING_PUNCTUATION = /* @__PURE__ */ new Set("!?‼⁇⁈⁉");
1394
+ const MIDDLE_DOTS = /* @__PURE__ */ new Set("・:;");
1395
+ const SMALL_KANA = /* @__PURE__ */ new Set("ァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖ");
1396
+ const ITERATION_MARKS = /* @__PURE__ */ new Set("ヽヾゝゞ々〻");
1397
+ const INSEPARABLE = /* @__PURE__ */ new Set("—―…‥─〳〵");
1398
+ const PREFIXED_ABBREVIATIONS = /* @__PURE__ */ new Set("¥$£##");
1399
+ const POSTFIXED_ABBREVIATIONS = /* @__PURE__ */ new Set("°′″%‰");
1400
+ const UNIT_SYMBOLS = /* @__PURE__ */ new Set("℃℉㎀㎁㎂㎃㎄㎅㎆㎇㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢");
1401
+ const GROUPED_NUMERAL = /^[0-90-9]$/u;
1402
+ const HIRAGANA = /^\p{Script=Hiragana}$/u;
1403
+ const KATAKANA = /^\p{Script=Katakana}$/u;
1404
+ const WESTERN = /^[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}]$/u;
1405
+ function classify({ value, presentation }) {
1406
+ if (presentation === "tate-chu-yoko") return "cl-30";
1407
+ if (OPENING.has(value)) return "cl-01";
1408
+ if (CLOSING.has(value)) return "cl-02";
1409
+ if (HYPHENS.has(value)) return "cl-03";
1410
+ if (DIVIDING_PUNCTUATION.has(value)) return "cl-04";
1411
+ if (MIDDLE_DOTS.has(value)) return "cl-05";
1412
+ if (value === "。" || value === ".") return "cl-06";
1413
+ if (value === "、" || value === ",") return "cl-07";
1414
+ if (INSEPARABLE.has(value)) return "cl-08";
1415
+ if (ITERATION_MARKS.has(value)) return "cl-09";
1416
+ if (value === "ー") return "cl-10";
1417
+ if (SMALL_KANA.has(value)) return "cl-11";
1418
+ if (PREFIXED_ABBREVIATIONS.has(value)) return "cl-12";
1419
+ if (POSTFIXED_ABBREVIATIONS.has(value)) return "cl-13";
1420
+ if (value === " ") return "cl-14";
1421
+ if (HIRAGANA.test(value)) return "cl-15";
1422
+ if (KATAKANA.test(value)) return "cl-16";
1423
+ if (GROUPED_NUMERAL.test(value)) return "cl-24";
1424
+ if (UNIT_SYMBOLS.has(value)) return "cl-25";
1425
+ if (value === " ") return "cl-26";
1426
+ if (WESTERN.test(value)) return "cl-27";
1427
+ return "cl-19";
1428
+ }
1429
+ function pairSpacing(left, right) {
1430
+ if (left === "cl-08" && right === "cl-08") return {
1431
+ kind: "kern",
1432
+ naturalWidthEm: 0
1433
+ };
1434
+ if (left === "cl-01" && right === "cl-01" || left === "cl-02" && right === "cl-02") return {
1435
+ kind: "kern",
1436
+ naturalWidthEm: -.5
1437
+ };
1438
+ if (left === "cl-07" || left === "cl-06") return {
1439
+ kind: "glue",
1440
+ naturalWidthEm: .5,
1441
+ shrink: {
1442
+ priority: 1,
1443
+ amountEm: .5
1444
+ }
1445
+ };
1446
+ if (left === "cl-02" || right === "cl-01") return {
1447
+ kind: "glue",
1448
+ naturalWidthEm: .5,
1449
+ shrink: {
1450
+ priority: 2,
1451
+ amountEm: .5
1452
+ }
1453
+ };
1454
+ return {
1455
+ kind: "glue",
1456
+ naturalWidthEm: 0,
1457
+ stretch: {
1458
+ priority: 3,
1459
+ amountEm: .25
1460
+ }
1461
+ };
1462
+ }
1463
+ function breakPenalty(left, right) {
1464
+ if (left === "cl-01" || left === "cl-12") return null;
1465
+ if (right === "cl-02" || right === "cl-03" || right === "cl-04" || right === "cl-05" || right === "cl-06" || right === "cl-07" || right === "cl-09" || right === "cl-10" || right === "cl-11" || right === "cl-13") return null;
1466
+ if (left === "cl-08" && right === "cl-08") return null;
1467
+ return 0;
1468
+ }
1469
+ const defaultJapaneseTypesettingProfile = {
1470
+ classify,
1471
+ boxMetrics: (characterClass, measuredAdvanceEm) => ({
1472
+ advanceEm: characterClass === "cl-01" || characterClass === "cl-02" || characterClass === "cl-06" || characterClass === "cl-07" ? Math.min(.5, measuredAdvanceEm) : measuredAdvanceEm,
1473
+ renderOffsetEm: characterClass === "cl-01" ? -.5 : 0
1315
1474
  }),
1316
- readonlyObject({
1317
- kind: v.literal("tate-chu-yoko"),
1318
- ...common
1319
- })
1320
- ]) };
1475
+ pairSpacing,
1476
+ lineStartSpacing: (first) => first === "cl-01" ? {
1477
+ kind: "glue",
1478
+ naturalWidthEm: .5,
1479
+ shrink: {
1480
+ priority: 2,
1481
+ amountEm: .5
1482
+ }
1483
+ } : null,
1484
+ lineEndSpacing: (last) => last === "cl-02" || last === "cl-06" || last === "cl-07" ? {
1485
+ kind: "glue",
1486
+ naturalWidthEm: .5,
1487
+ shrink: {
1488
+ priority: 2,
1489
+ amountEm: .5
1490
+ }
1491
+ } : null,
1492
+ breakPenalty,
1493
+ canHang: (characterClass) => characterClass === "cl-07" || characterClass === "cl-06"
1494
+ };
1321
1495
  //#endregion
1322
- //#region src/composer/positioned-grapheme.ts
1323
- const emLength = () => v.pipe(v.number(), v.finite(), v.minValue(0));
1324
- const PositionedGrapheme = { schema: readonlyObject({
1325
- kind: v.literal("grapheme"),
1326
- value: v.string(),
1327
- range: ManuscriptRange.schema,
1328
- offsetEm: emLength(),
1329
- advanceEm: emLength(),
1330
- disposition: v.union([v.literal("placed"), v.literal("hanging")]),
1331
- presentation: VerticalTextPresentation.schema
1332
- }) };
1333
- const SuppressedGrapheme = { schema: readonlyObject({
1334
- kind: v.literal("suppressed"),
1335
- value: v.string(),
1336
- range: ManuscriptRange.schema,
1337
- reason: v.literal("question-or-exclamation-gap")
1338
- }) };
1496
+ //#region src/composer/internal/paragraph-layout.ts
1497
+ const EPSILON = 1e-9;
1498
+ function skipSourceGaps(atoms, start) {
1499
+ let cursor = start;
1500
+ while (atoms[cursor]?.sourceGap === true) cursor += 1;
1501
+ return cursor;
1502
+ }
1503
+ function previousVisible(atoms, end) {
1504
+ for (let index = end - 1; index >= 0; index -= 1) if (atoms[index]?.sourceGap === false) return index;
1505
+ }
1506
+ function opportunities(atoms, classes, start, end, profile) {
1507
+ const result = [];
1508
+ const firstClass = classes[start];
1509
+ const startSpacing = firstClass === void 0 ? null : profile.lineStartSpacing(firstClass);
1510
+ if (startSpacing !== null) result.push({
1511
+ boundary: start,
1512
+ spacing: startSpacing
1513
+ });
1514
+ for (let right = start + 1; right < end; right += 1) {
1515
+ const leftAtom = atoms[right - 1];
1516
+ const rightAtom = atoms[right];
1517
+ const leftClass = classes[right - 1];
1518
+ const rightClass = classes[right];
1519
+ if (leftAtom === void 0 || rightAtom === void 0 || leftClass === void 0 || rightClass === void 0 || leftAtom.sourceGap || rightAtom.sourceGap || !leftAtom.pairSpacingAfter) continue;
1520
+ result.push({
1521
+ boundary: right,
1522
+ spacing: profile.pairSpacing(leftClass, rightClass)
1523
+ });
1524
+ }
1525
+ const lastClass = classes[end - 1];
1526
+ const endSpacing = lastClass === void 0 ? null : profile.lineEndSpacing(lastClass);
1527
+ if (endSpacing !== null) result.push({
1528
+ boundary: end,
1529
+ spacing: endSpacing
1530
+ });
1531
+ return result;
1532
+ }
1533
+ function capacity(values, select) {
1534
+ return values.reduce((total, opportunity) => total + (select(opportunity.spacing)?.amountEm ?? 0), 0);
1535
+ }
1536
+ function resolveSpacings(values, adjustment, amountEm) {
1537
+ const remainingByBoundary = /* @__PURE__ */ new Map();
1538
+ let remaining = amountEm;
1539
+ let priorityCost = 0;
1540
+ const capacities = values.flatMap((opportunity) => {
1541
+ const selected = adjustment === "shrink" ? opportunity.spacing.shrink : opportunity.spacing.stretch;
1542
+ return selected === void 0 ? [] : [{
1543
+ ...opportunity,
1544
+ capacity: selected
1545
+ }];
1546
+ }).sort((left, right) => left.capacity.priority - right.capacity.priority || left.boundary - right.boundary);
1547
+ for (const opportunity of capacities) {
1548
+ if (remaining <= EPSILON) break;
1549
+ const used = Math.min(remaining, opportunity.capacity.amountEm);
1550
+ remainingByBoundary.set(opportunity.boundary, used);
1551
+ priorityCost += used * opportunity.capacity.priority;
1552
+ remaining -= used;
1553
+ }
1554
+ return {
1555
+ spacings: values.map(({ boundary, spacing }) => {
1556
+ const used = remainingByBoundary.get(boundary) ?? 0;
1557
+ return {
1558
+ boundary,
1559
+ kind: spacing.kind,
1560
+ naturalWidthEm: spacing.naturalWidthEm,
1561
+ widthEm: adjustment === "shrink" ? spacing.naturalWidthEm - used : spacing.naturalWidthEm + used
1562
+ };
1563
+ }),
1564
+ priorityCost
1565
+ };
1566
+ }
1567
+ function candidate(atoms, classes, start, end, lineLengthEm, profile) {
1568
+ const contentStart = skipSourceGaps(atoms, start);
1569
+ const suppressedIndexes = Array.from({ length: contentStart - start }, (_, index) => start + index);
1570
+ const pairValues = opportunities(atoms, classes, contentStart, end, profile);
1571
+ const naturalSizeEm = atoms.slice(contentStart, end).reduce((total, atom) => total + atom.boxAdvanceEm, 0) + pairValues.reduce((total, value) => total + value.spacing.naturalWidthEm, 0);
1572
+ const terminal = skipSourceGaps(atoms, end) === atoms.length;
1573
+ const shrinkCapacity = capacity(pairValues, ({ shrink }) => shrink);
1574
+ const stretchCapacity = capacity(pairValues, ({ stretch }) => stretch);
1575
+ const overflow = naturalSizeEm - lineLengthEm;
1576
+ const underflow = lineLengthEm - naturalSizeEm;
1577
+ const lastVisible = previousVisible(atoms, end);
1578
+ const lastClass = lastVisible === void 0 ? void 0 : classes[lastVisible];
1579
+ const lastAdvance = lastVisible === void 0 ? 0 : atoms[lastVisible]?.boxAdvanceEm ?? 0;
1580
+ const trailingSpacing = pairValues.find(({ boundary }) => boundary === end)?.spacing.naturalWidthEm ?? 0;
1581
+ if (terminal && overflow <= EPSILON) return {
1582
+ start,
1583
+ contentStart,
1584
+ end,
1585
+ suppressedIndexes,
1586
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings,
1587
+ inlineSizeEm: naturalSizeEm,
1588
+ break: { kind: "paragraph-end" },
1589
+ hangingIndex: null,
1590
+ deformationRatio: 0,
1591
+ priorityCost: 0
1592
+ };
1593
+ if (Math.abs(overflow) <= EPSILON) return {
1594
+ start,
1595
+ contentStart,
1596
+ end,
1597
+ suppressedIndexes,
1598
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings,
1599
+ inlineSizeEm: naturalSizeEm,
1600
+ break: { kind: "natural" },
1601
+ hangingIndex: null,
1602
+ deformationRatio: 0,
1603
+ priorityCost: 0
1604
+ };
1605
+ if (overflow > 0 && overflow <= shrinkCapacity + EPSILON) {
1606
+ const resolved = resolveSpacings(pairValues, "shrink", overflow);
1607
+ return {
1608
+ start,
1609
+ contentStart,
1610
+ end,
1611
+ suppressedIndexes,
1612
+ pairSpacings: resolved.spacings,
1613
+ inlineSizeEm: lineLengthEm,
1614
+ break: { kind: "shrunk" },
1615
+ hangingIndex: null,
1616
+ deformationRatio: shrinkCapacity === 0 ? 0 : overflow / shrinkCapacity,
1617
+ priorityCost: resolved.priorityCost
1618
+ };
1619
+ }
1620
+ if (!terminal && overflow > 0 && lastVisible !== void 0 && lastClass !== void 0 && profile.canHang(lastClass) && naturalSizeEm - lastAdvance - trailingSpacing <= lineLengthEm + EPSILON) return {
1621
+ start,
1622
+ contentStart,
1623
+ end,
1624
+ suppressedIndexes,
1625
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings.filter(({ boundary }) => boundary !== end),
1626
+ inlineSizeEm: naturalSizeEm - lastAdvance - trailingSpacing,
1627
+ break: { kind: "hanging" },
1628
+ hangingIndex: lastVisible,
1629
+ deformationRatio: overflow / Math.max(lastAdvance, EPSILON),
1630
+ priorityCost: 0
1631
+ };
1632
+ if (!terminal && underflow > EPSILON && underflow <= stretchCapacity + EPSILON) {
1633
+ const resolved = resolveSpacings(pairValues, "stretch", underflow);
1634
+ return {
1635
+ start,
1636
+ contentStart,
1637
+ end,
1638
+ suppressedIndexes,
1639
+ pairSpacings: resolved.spacings,
1640
+ inlineSizeEm: lineLengthEm,
1641
+ break: { kind: "stretched" },
1642
+ hangingIndex: null,
1643
+ deformationRatio: stretchCapacity === 0 ? 0 : underflow / stretchCapacity,
1644
+ priorityCost: resolved.priorityCost
1645
+ };
1646
+ }
1647
+ return {
1648
+ start,
1649
+ contentStart,
1650
+ end,
1651
+ suppressedIndexes,
1652
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings,
1653
+ inlineSizeEm: naturalSizeEm,
1654
+ break: { kind: "forced" },
1655
+ hangingIndex: null,
1656
+ deformationRatio: Math.abs(naturalSizeEm - lineLengthEm) / Math.max(lineLengthEm, EPSILON),
1657
+ priorityCost: 0
1658
+ };
1659
+ }
1660
+ function scoreFor(line, previousFitness) {
1661
+ const mode = line.break.kind;
1662
+ const fitness = Math.min(3, Math.floor(line.deformationRatio * 4));
1663
+ const transition = Math.abs(previousFitness - fitness) > 1 ? 1 : 0;
1664
+ const modeCounts = {
1665
+ forced: mode === "forced" ? 1 : 0,
1666
+ stretched: mode === "stretched" ? 1 : 0,
1667
+ hanging: mode === "hanging" ? 1 : 0,
1668
+ shrunk: mode === "shrunk" ? 1 : 0
1669
+ };
1670
+ return {
1671
+ score: [
1672
+ modeCounts.forced,
1673
+ modeCounts.stretched,
1674
+ modeCounts.hanging,
1675
+ modeCounts.shrunk,
1676
+ line.priorityCost,
1677
+ line.deformationRatio ** 3,
1678
+ transition
1679
+ ],
1680
+ fitness
1681
+ };
1682
+ }
1683
+ function addScore(left, right) {
1684
+ return [
1685
+ left[0] + right[0],
1686
+ left[1] + right[1],
1687
+ left[2] + right[2],
1688
+ left[3] + right[3],
1689
+ left[4] + right[4],
1690
+ left[5] + right[5],
1691
+ left[6] + right[6]
1692
+ ];
1693
+ }
1694
+ function compareBreaks(left, right) {
1695
+ const length = Math.min(left.length, right.length);
1696
+ for (let index = 0; index < length; index += 1) {
1697
+ const difference = (right[index] ?? 0) - (left[index] ?? 0);
1698
+ if (difference !== 0) return difference;
1699
+ }
1700
+ return left.length - right.length;
1701
+ }
1702
+ function isBetter(candidateState, current) {
1703
+ if (current === void 0) return true;
1704
+ for (let index = 0; index < candidateState.score.length; index += 1) {
1705
+ const difference = (candidateState.score[index] ?? 0) - (current.score[index] ?? 0);
1706
+ if (Math.abs(difference) > EPSILON) return difference < 0;
1707
+ }
1708
+ return compareBreaks(candidateState.breaks, current.breaks) < 0;
1709
+ }
1710
+ function layoutParagraph(atoms, lineLengthEm, profile, boundaryAllowed) {
1711
+ if (atoms.length === 0) return [];
1712
+ const classes = atoms.map(({ characterClass }) => characterClass);
1713
+ const states = /* @__PURE__ */ new Map([[0, /* @__PURE__ */ new Map([[0, {
1714
+ score: [
1715
+ 0,
1716
+ 0,
1717
+ 0,
1718
+ 0,
1719
+ 0,
1720
+ 0,
1721
+ 0
1722
+ ],
1723
+ fitness: 0,
1724
+ previous: null,
1725
+ line: null,
1726
+ breaks: []
1727
+ }]])]]);
1728
+ for (let start = 0; start < atoms.length; start += 1) {
1729
+ const activeStates = states.get(start);
1730
+ if (activeStates === void 0) continue;
1731
+ const contentStart = skipSourceGaps(atoms, start);
1732
+ if (contentStart === atoms.length) {
1733
+ const terminalStates = states.get(atoms.length) ?? /* @__PURE__ */ new Map();
1734
+ for (const state of activeStates.values()) if (isBetter(state, terminalStates.get(state.fitness))) terminalStates.set(state.fitness, state);
1735
+ states.set(atoms.length, terminalStates);
1736
+ continue;
1737
+ }
1738
+ for (const state of activeStates.values()) for (let end = contentStart + 1; end <= atoms.length; end += 1) {
1739
+ const right = skipSourceGaps(atoms, end);
1740
+ const left = previousVisible(atoms, end);
1741
+ if (atoms[end - 1]?.sourceGap === true) continue;
1742
+ if (end < atoms.length && right < atoms.length && (left === void 0 || !boundaryAllowed(left, right))) continue;
1743
+ const line = candidate(atoms, classes, start, end, lineLengthEm, profile);
1744
+ if (line.break.kind === "forced" && line.inlineSizeEm > lineLengthEm + EPSILON && Array.from({ length: end - contentStart - 1 }, (_, index) => contentStart + index + 1).some((boundary) => boundaryAllowed(boundary - 1, boundary))) {
1745
+ if (line.inlineSizeEm > lineLengthEm * 2 && end > contentStart + 1) break;
1746
+ continue;
1747
+ }
1748
+ const lineScore = scoreFor(line, state.fitness);
1749
+ const nextState = {
1750
+ score: addScore(state.score, lineScore.score),
1751
+ fitness: lineScore.fitness,
1752
+ previous: {
1753
+ index: start,
1754
+ fitness: state.fitness
1755
+ },
1756
+ line,
1757
+ breaks: [...state.breaks, end]
1758
+ };
1759
+ const statesAtEnd = states.get(end) ?? /* @__PURE__ */ new Map();
1760
+ if (isBetter(nextState, statesAtEnd.get(nextState.fitness))) {
1761
+ statesAtEnd.set(nextState.fitness, nextState);
1762
+ states.set(end, statesAtEnd);
1763
+ }
1764
+ if (line.inlineSizeEm > lineLengthEm * 2 && line.break.kind === "forced" && end > contentStart + 1) break;
1765
+ }
1766
+ }
1767
+ const terminal = [...states.get(atoms.length)?.values() ?? []].reduce((best, state) => isBetter(state, best) ? state : best, void 0);
1768
+ if (terminal === void 0) return [];
1769
+ const lines = [];
1770
+ let cursor = terminal;
1771
+ while (cursor?.line !== null && cursor?.line !== void 0) {
1772
+ lines.push(cursor.line);
1773
+ cursor = cursor.previous === null ? void 0 : states.get(cursor.previous.index)?.get(cursor.previous.fitness);
1774
+ }
1775
+ lines.reverse();
1776
+ const last = lines.at(-1);
1777
+ if (last !== void 0 && last.end < atoms.length) lines[lines.length - 1] = {
1778
+ ...last,
1779
+ suppressedIndexes: [...last.suppressedIndexes, ...Array.from({ length: atoms.length - last.end }, (_, index) => last.end + index)]
1780
+ };
1781
+ return lines;
1782
+ }
1339
1783
  const NovelLine = {
1340
1784
  schema: readonlyObject({
1341
1785
  range: v.nullable(ManuscriptRange.schema),
1342
- advanceEm: v.pipe(v.number(), v.finite(), v.minValue(0)),
1343
- graphemes: readonlyArray(PositionedGrapheme.schema),
1344
- suppressed: readonlyArray(SuppressedGrapheme.schema),
1786
+ inlineSizeEm: v.pipe(v.number(), v.finite(), v.minValue(0)),
1787
+ items: readonlyArray(ComposedInlineItem.schema),
1788
+ break: LineBreakResult.schema,
1345
1789
  annotations: readonlyArray(ComposedAnnotationFragment.schema)
1346
1790
  }),
1347
1791
  empty: () => ({
1348
1792
  range: null,
1349
- advanceEm: 0,
1350
- graphemes: [],
1351
- suppressed: [],
1793
+ inlineSizeEm: 0,
1794
+ items: [],
1795
+ break: { kind: "paragraph-end" },
1352
1796
  annotations: []
1353
1797
  })
1354
1798
  };
@@ -1380,15 +1824,7 @@ const NovelLayout = { schema: readonlyObject({
1380
1824
  //#endregion
1381
1825
  //#region src/composer/novel-composer.ts
1382
1826
  const COMPOSER_ID = NamespacedId.of("kg/novel");
1383
- const LINE_START_PROHIBITED = /* @__PURE__ */ new Set(`、。,.・:;?!‼⁇⁈⁉ヽヾゝゞ々ーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖ${CLOSING_BRACKETS}】〙〟⦆»`);
1384
- const LINE_END_PROHIBITED = /* @__PURE__ */ new Set(`${OPENING_BRACKETS}【〘〝⦅«`);
1385
- const HANGING_PUNCTUATION = /* @__PURE__ */ new Set("、。,.!?");
1386
- const INSEPARABLE_PAIRS = /* @__PURE__ */ new Set([
1387
- "……",
1388
- "――",
1389
- "──",
1390
- "〳〵"
1391
- ]);
1827
+ const TYPESETTING_PROFILE = defaultJapaneseTypesettingProfile;
1392
1828
  const ASCII_ALPHANUMERIC = /^[A-Za-z0-9]$/u;
1393
1829
  const ASCII_TWO_DIGITS = /^[0-9]{2}$/u;
1394
1830
  const UPRIGHT_LATIN_ABBREVIATION = /^(?:[A-Z]+|[A-Z][a-z]{1,2})$/u;
@@ -1462,7 +1898,7 @@ function measure(measurer, text, role, settings, presentationKind) {
1462
1898
  fontPreset: settings.appearance.fontPreset,
1463
1899
  writingMode: "vertical-rl"
1464
1900
  };
1465
- const value = role === "base" ? measurer({
1901
+ const measurement = role === "base" ? measurer({
1466
1902
  ...context,
1467
1903
  role,
1468
1904
  presentation: presentationKind ?? "mixed"
@@ -1470,14 +1906,25 @@ function measure(measurer, text, role, settings, presentationKind) {
1470
1906
  ...context,
1471
1907
  role
1472
1908
  });
1473
- return Number.isFinite(value) && value >= 0 ? value : void 0;
1909
+ const parsed = v.safeParse(InlineMeasurement.schema, measurement);
1910
+ if (!parsed.success) return void 0;
1911
+ const { advanceEm } = parsed.output;
1912
+ return advanceEm;
1474
1913
  }
1475
1914
  function measureSourceLine(sourceLine, annotations, settings, measurer) {
1476
1915
  const mutable = presentedSourceLine(sourceLine).map(({ grapheme, presentation }) => {
1477
- const advanceEm = measure(measurer, grapheme.value, "base", settings, presentation.kind);
1478
- return advanceEm === void 0 ? void 0 : {
1916
+ const renderAdvanceEm = measure(measurer, grapheme.value, "base", settings, presentation.kind);
1917
+ if (renderAdvanceEm === void 0) return void 0;
1918
+ const characterClass = TYPESETTING_PROFILE.classify({
1919
+ value: grapheme.value,
1920
+ presentation: presentation.kind
1921
+ });
1922
+ const metrics = TYPESETTING_PROFILE.boxMetrics(characterClass, renderAdvanceEm);
1923
+ return {
1479
1924
  grapheme,
1480
- advanceEm,
1925
+ boxAdvanceEm: metrics.advanceEm,
1926
+ renderAdvanceEm,
1927
+ renderOffsetEm: metrics.renderOffsetEm,
1481
1928
  presentation
1482
1929
  };
1483
1930
  });
@@ -1491,11 +1938,11 @@ function measureSourceLine(sourceLine, annotations, settings, measurer) {
1491
1938
  if (annotation.reading.kind === "group") {
1492
1939
  const readingAdvance = measure(measurer, annotation.reading.text, "ruby", settings);
1493
1940
  if (readingAdvance === void 0) return void 0;
1494
- const baseAdvance = indexes.reduce((total, index) => total + (atoms[index]?.advanceEm ?? 0), 0);
1941
+ const baseAdvance = indexes.reduce((total, index) => total + (atoms[index]?.boxAdvanceEm ?? 0), 0);
1495
1942
  const extra = Math.max(0, readingAdvance - baseAdvance) / indexes.length;
1496
1943
  for (const index of indexes) {
1497
1944
  const atom = atoms[index];
1498
- if (atom !== void 0) atom.advanceEm += extra;
1945
+ if (atom !== void 0) atom.boxAdvanceEm += extra;
1499
1946
  }
1500
1947
  continue;
1501
1948
  }
@@ -1505,7 +1952,7 @@ function measureSourceLine(sourceLine, annotations, settings, measurer) {
1505
1952
  if (segment === void 0 || atom === void 0) continue;
1506
1953
  const readingAdvance = measure(measurer, segment, "ruby", settings);
1507
1954
  if (readingAdvance === void 0) return void 0;
1508
- atom.advanceEm = Math.max(atom.advanceEm, readingAdvance);
1955
+ atom.boxAdvanceEm = Math.max(atom.boxAdvanceEm, readingAdvance);
1509
1956
  }
1510
1957
  }
1511
1958
  return {
@@ -1520,17 +1967,23 @@ function breakInsideFittableGroupRuby(atoms, annotations, boundary, lineLengthEm
1520
1967
  return annotations.some((annotation) => {
1521
1968
  if (annotation.kind !== "ruby" || annotation.reading.kind !== "group") return false;
1522
1969
  if (graphemeIndex <= annotation.range.graphemes.start || graphemeIndex >= annotation.range.graphemes.end) return false;
1523
- return indicesInside(atoms, annotation).reduce((total, index) => total + (atoms[index]?.advanceEm ?? 0), 0) <= lineLengthEm;
1970
+ return indicesInside(atoms, annotation).reduce((total, index) => total + (atoms[index]?.boxAdvanceEm ?? 0), 0) <= lineLengthEm;
1524
1971
  });
1525
1972
  }
1526
1973
  function samePresentationGroup(left, right) {
1527
1974
  return left.presentation.groupRange.graphemes.start === right.presentation.groupRange.graphemes.start && left.presentation.groupRange.graphemes.end === right.presentation.groupRange.graphemes.end;
1528
1975
  }
1529
- function legalBoundary(atoms, annotations, boundary, lineLengthEm) {
1530
- const left = atoms[boundary - 1];
1531
- const right = atoms[boundary];
1976
+ function legalBoundary(atoms, annotations, leftIndex, rightIndex, lineLengthEm, profile) {
1977
+ const left = atoms[leftIndex];
1978
+ const right = atoms[rightIndex];
1532
1979
  if (left === void 0 || right === void 0) return true;
1533
- return !samePresentationGroup(left, right) && !LINE_END_PROHIBITED.has(left.grapheme.value) && !LINE_START_PROHIBITED.has(right.grapheme.value) && !INSEPARABLE_PAIRS.has(left.grapheme.value + right.grapheme.value) && !breakInsideFittableGroupRuby(atoms, annotations, boundary, lineLengthEm);
1980
+ return !samePresentationGroup(left, right) && profile.breakPenalty(profile.classify({
1981
+ value: left.grapheme.value,
1982
+ presentation: left.presentation.kind
1983
+ }), profile.classify({
1984
+ value: right.grapheme.value,
1985
+ presentation: right.presentation.kind
1986
+ })) !== null && !breakInsideFittableGroupRuby(atoms, annotations, rightIndex, lineLengthEm);
1534
1987
  }
1535
1988
  function continuationFor(annotation, range) {
1536
1989
  const starts = range.graphemes.start === annotation.range.graphemes.start;
@@ -1602,14 +2055,18 @@ function positionReading(text, baseOffsetEm, baseAdvanceEm, settings, measurer)
1602
2055
  return positioned;
1603
2056
  });
1604
2057
  }
1605
- function annotationFragmentRange(graphemes, suppressed, annotation) {
1606
- return ManuscriptRange.merge([...graphemes.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range), ...suppressed.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range)]);
2058
+ function annotationFragmentRange(graphemes, suppressed, sourceGlues, annotation) {
2059
+ return ManuscriptRange.merge([
2060
+ ...graphemes.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range),
2061
+ ...suppressed.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range),
2062
+ ...sourceGlues.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range)
2063
+ ]);
1607
2064
  }
1608
- function annotationFragments(graphemes, suppressed, annotations, settings, measurer, fragmentPlansByAnnotation) {
2065
+ function annotationFragments(graphemes, suppressed, sourceGlues, annotations, settings, measurer, fragmentPlansByAnnotation) {
1609
2066
  const fragments = [];
1610
2067
  for (const annotation of annotations) {
1611
2068
  const covered = graphemes.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range));
1612
- const fragmentRange = annotationFragmentRange(graphemes, suppressed, annotation);
2069
+ const fragmentRange = annotationFragmentRange(graphemes, suppressed, sourceGlues, annotation);
1613
2070
  const first = covered[0];
1614
2071
  const last = covered.at(-1);
1615
2072
  if (fragmentRange === null || first === void 0 || last === void 0) continue;
@@ -1636,8 +2093,8 @@ function annotationFragments(graphemes, suppressed, annotations, settings, measu
1636
2093
  case "ruby": {
1637
2094
  const fragmentPlan = fragmentPlansByAnnotation.get(annotation);
1638
2095
  if (fragmentPlan === void 0) break;
1639
- const baseOffsetEm = first.offsetEm;
1640
- const baseAdvanceEm = last.offsetEm + last.advanceEm - baseOffsetEm;
2096
+ const baseOffsetEm = first.layoutSpan.offsetEm;
2097
+ const baseAdvanceEm = last.layoutSpan.offsetEm + last.layoutSpan.advanceEm - baseOffsetEm;
1641
2098
  const reading = readingForFragment(annotation, fragmentRange, fragmentPlan);
1642
2099
  fragments.push({
1643
2100
  kind: "ruby",
@@ -1654,88 +2111,103 @@ function annotationFragments(graphemes, suppressed, annotations, settings, measu
1654
2111
  }
1655
2112
  return fragments;
1656
2113
  }
1657
- function positionedLine(atoms, dispositions, suppressed) {
2114
+ function positionedLine(atoms, sourceGapIndexes, plan) {
2115
+ const items = plan.suppressedIndexes.flatMap((index) => {
2116
+ const atom = atoms[index];
2117
+ return atom === void 0 ? [] : [{
2118
+ kind: "suppressed",
2119
+ value: atom.grapheme.value,
2120
+ range: atom.grapheme.range,
2121
+ reason: "question-or-exclamation-gap"
2122
+ }];
2123
+ });
2124
+ const spacings = new Map(plan.pairSpacings.map((spacing) => [spacing.boundary, spacing]));
1658
2125
  let offsetEm = 0;
1659
- const graphemes = atoms.map(({ grapheme, advanceEm, presentation }, index) => {
1660
- const positioned = {
1661
- kind: "grapheme",
1662
- value: grapheme.value,
1663
- range: grapheme.range,
2126
+ const positionSpacing = (spacing) => {
2127
+ if (spacing.kind === "kern") items.push({
2128
+ kind: "kern",
1664
2129
  offsetEm,
1665
- advanceEm,
1666
- disposition: dispositions.has(index) ? "hanging" : "placed",
1667
- presentation
1668
- };
1669
- offsetEm += advanceEm;
1670
- return positioned;
1671
- });
1672
- return {
1673
- range: ManuscriptRange.merge([...graphemes.map(({ range }) => range), ...suppressed.map(({ range }) => range)]),
1674
- advanceEm: offsetEm,
1675
- graphemes,
1676
- suppressed,
1677
- annotations: []
2130
+ widthEm: spacing.widthEm
2131
+ });
2132
+ else items.push({
2133
+ kind: "glue",
2134
+ origin: "generated",
2135
+ offsetEm,
2136
+ widthEm: spacing.widthEm,
2137
+ naturalWidthEm: spacing.naturalWidthEm,
2138
+ adjustment: spacing.widthEm < spacing.naturalWidthEm ? "shrunk" : spacing.widthEm > spacing.naturalWidthEm ? "stretched" : "natural"
2139
+ });
2140
+ offsetEm += spacing.widthEm;
1678
2141
  };
1679
- }
1680
- function wrapSourceLine(sourceLine, annotations, settings, measurer, baseAdvances) {
1681
- if (sourceLine.atoms.length === 0) return [NovelLine.empty()];
1682
- const lines = [];
1683
- let cursor = 0;
1684
- while (cursor < sourceLine.atoms.length) {
1685
- const suppressed = [];
1686
- if (lines.length > 0 && sourceLine.suppressedIndexes.has(cursor)) {
1687
- const atom = sourceLine.atoms[cursor];
1688
- if (atom !== void 0) suppressed.push({
1689
- kind: "suppressed",
2142
+ for (let index = plan.contentStart; index < plan.end; index += 1) {
2143
+ const spacing = spacings.get(index);
2144
+ if (spacing !== void 0) positionSpacing(spacing);
2145
+ const atom = atoms[index];
2146
+ if (atom === void 0) continue;
2147
+ if (sourceGapIndexes.has(index)) {
2148
+ items.push({
2149
+ kind: "glue",
2150
+ origin: "source",
1690
2151
  value: atom.grapheme.value,
1691
2152
  range: atom.grapheme.range,
1692
- reason: "question-or-exclamation-gap"
2153
+ offsetEm,
2154
+ widthEm: atom.boxAdvanceEm,
2155
+ naturalWidthEm: atom.boxAdvanceEm,
2156
+ adjustment: "natural"
1693
2157
  });
1694
- cursor += 1;
1695
- }
1696
- if (cursor >= sourceLine.atoms.length) {
1697
- const previous = lines.at(-1);
1698
- if (previous !== void 0 && suppressed.length > 0) lines[lines.length - 1] = {
1699
- ...previous,
1700
- range: ManuscriptRange.merge([...previous.range === null ? [] : [previous.range], ...suppressed.map(({ range }) => range)]),
1701
- suppressed: [...previous.suppressed, ...suppressed]
1702
- };
1703
- break;
1704
- }
1705
- const start = cursor;
1706
- let advance = 0;
1707
- while (cursor < sourceLine.atoms.length) {
1708
- const atom = sourceLine.atoms[cursor];
1709
- if (atom === void 0) break;
1710
- const previous = sourceLine.atoms[cursor - 1];
1711
- const beginsPresentationGroup = previous === void 0 || !samePresentationGroup(previous, atom);
1712
- if (cursor > start && beginsPresentationGroup) {
1713
- let groupAdvance = 0;
1714
- for (let index = cursor; index < sourceLine.atoms.length; index += 1) {
1715
- const member = sourceLine.atoms[index];
1716
- if (member === void 0 || !samePresentationGroup(atom, member)) break;
1717
- groupAdvance += member.advanceEm;
1718
- }
1719
- if (advance + groupAdvance > settings.flow.lineLengthEm) break;
1720
- }
1721
- if (cursor > start && advance + atom.advanceEm > settings.flow.lineLengthEm && (previous === void 0 || !samePresentationGroup(previous, atom))) break;
1722
- advance += atom.advanceEm;
1723
- cursor += 1;
2158
+ offsetEm += atom.boxAdvanceEm;
2159
+ continue;
1724
2160
  }
1725
- let end = Math.max(start + 1, cursor);
1726
- const hanging = /* @__PURE__ */ new Set();
1727
- const next = sourceLine.atoms[end];
1728
- if (next !== void 0 && HANGING_PUNCTUATION.has(next.grapheme.value)) {
1729
- hanging.add(end - start);
1730
- end += 1;
1731
- } else while (end > start + 1 && !legalBoundary(sourceLine.atoms, annotations, end, settings.flow.lineLengthEm)) end -= 1;
1732
- const atoms = sourceLine.atoms.slice(start, end);
1733
- lines.push(positionedLine(atoms, hanging, suppressed));
1734
- cursor = end;
2161
+ const hanging = plan.hangingIndex === index;
2162
+ const layoutAdvanceEm = hanging ? 0 : atom.boxAdvanceEm;
2163
+ items.push({
2164
+ kind: "glyph",
2165
+ value: atom.grapheme.value,
2166
+ range: atom.grapheme.range,
2167
+ layoutSpan: {
2168
+ offsetEm,
2169
+ advanceEm: layoutAdvanceEm
2170
+ },
2171
+ renderSpan: {
2172
+ offsetEm: offsetEm + atom.renderOffsetEm,
2173
+ advanceEm: atom.renderAdvanceEm
2174
+ },
2175
+ disposition: hanging ? "hanging" : "placed",
2176
+ presentation: atom.presentation
2177
+ });
2178
+ offsetEm += layoutAdvanceEm;
1735
2179
  }
2180
+ const trailingSpacing = spacings.get(plan.end);
2181
+ if (trailingSpacing !== void 0) positionSpacing(trailingSpacing);
2182
+ const ranges = items.flatMap((item) => item.kind === "glyph" || item.kind === "suppressed" || item.kind === "glue" && item.origin === "source" ? [item.range] : []);
2183
+ return {
2184
+ range: ManuscriptRange.merge(ranges),
2185
+ inlineSizeEm: Math.max(0, plan.inlineSizeEm),
2186
+ items,
2187
+ break: plan.break,
2188
+ annotations: []
2189
+ };
2190
+ }
2191
+ function wrapSourceLine(sourceLine, annotations, settings, measurer, baseAdvances) {
2192
+ if (sourceLine.atoms.length === 0) return [NovelLine.empty()];
2193
+ const profile = TYPESETTING_PROFILE;
2194
+ const lines = layoutParagraph(sourceLine.atoms.map(({ grapheme, boxAdvanceEm, presentation }, index) => {
2195
+ const current = sourceLine.atoms[index];
2196
+ const next = sourceLine.atoms[index + 1];
2197
+ return {
2198
+ value: grapheme.value,
2199
+ boxAdvanceEm,
2200
+ sourceGap: sourceLine.suppressedIndexes.has(index),
2201
+ characterClass: profile.classify({
2202
+ value: grapheme.value,
2203
+ presentation: presentation.kind
2204
+ }),
2205
+ pairSpacingAfter: current === void 0 || next === void 0 || !samePresentationGroup(current, next) && !breakInsideFittableGroupRuby(sourceLine.atoms, annotations, index + 1, settings.flow.lineLengthEm)
2206
+ };
2207
+ }), settings.flow.lineLengthEm, profile, (leftIndex, rightIndex) => legalBoundary(sourceLine.atoms, annotations, leftIndex, rightIndex, settings.flow.lineLengthEm, profile)).map((plan) => positionedLine(sourceLine.atoms, sourceLine.suppressedIndexes, plan));
1736
2208
  const fragmentPlansByAnnotation = new Map(annotations.map((annotation) => {
1737
2209
  const ranges = lines.flatMap((line) => {
1738
- const range = annotationFragmentRange(line.graphemes, line.suppressed, annotation);
2210
+ const range = annotationFragmentRange(line.items.filter((item) => item.kind === "glyph"), line.items.filter((item) => item.kind === "suppressed"), line.items.flatMap((item) => item.kind === "glue" && item.origin === "source" ? [item] : []), annotation);
1739
2211
  return range === null ? [] : [range];
1740
2212
  });
1741
2213
  return [annotation, {
@@ -1745,10 +2217,10 @@ function wrapSourceLine(sourceLine, annotations, settings, measurer, baseAdvance
1745
2217
  }));
1746
2218
  return lines.map((line) => ({
1747
2219
  range: line.range,
1748
- advanceEm: line.advanceEm,
1749
- graphemes: line.graphemes,
1750
- suppressed: line.suppressed,
1751
- annotations: annotationFragments(line.graphemes, line.suppressed, annotations, settings, measurer, fragmentPlansByAnnotation)
2220
+ inlineSizeEm: line.inlineSizeEm,
2221
+ items: line.items,
2222
+ break: line.break,
2223
+ annotations: annotationFragments(line.items.filter((item) => item.kind === "glyph"), line.items.filter((item) => item.kind === "suppressed"), line.items.flatMap((item) => item.kind === "glue" && item.origin === "source" ? [item] : []), annotations, settings, measurer, fragmentPlansByAnnotation)
1752
2224
  }));
1753
2225
  }
1754
2226
  function blankLines(count) {
@@ -1789,7 +2261,7 @@ function createCompose(measurer) {
1789
2261
  const measured = sourceLines.map((line) => measureSourceLine(line, manuscript.annotations, settings, measurer));
1790
2262
  if (measured.some((line) => line === void 0)) return ManuscriptResult.fail({ reason: "inline measurer returned a negative or non-finite value" });
1791
2263
  const baseAdvances = /* @__PURE__ */ new Map();
1792
- for (const line of measured) for (const atom of line?.atoms ?? []) baseAdvances.set(atom.grapheme.range.graphemes.start, atom.advanceEm);
2264
+ for (const line of measured) for (const atom of line?.atoms ?? []) baseAdvances.set(atom.grapheme.range.graphemes.start, atom.boxAdvanceEm);
1793
2265
  const manuscriptLines = measured.flatMap((line) => line === void 0 ? [] : wrapSourceLine(line, manuscript.annotations, settings, measurer, baseAdvances));
1794
2266
  const pages = buildPages([
1795
2267
  ...blankLines(settings.offsets.document.leading),
@@ -2561,4 +3033,4 @@ function createDefaultProofreadingRules() {
2561
3033
  ];
2562
3034
  }
2563
3035
  //#endregion
2564
- export { BoldAnnotation, ComposeError, ComposedAnnotationFragment, CompositionStatistics, DiagnosticOrigin, DiagnosticSeverity, DisplayRange, EmphasisAnnotation, FontPreset, FontPresetId, FontSizePt, GraphemeRange, InvalidRuleOptions, ItalicAnnotation, LineOffset, ManuscriptAnnotation, ManuscriptAppearanceSettings, ManuscriptDiagnostic, ManuscriptGeometry, ManuscriptOffsets, ManuscriptRange, ManuscriptResult, NamespacedId, NovelCompositionSettings, NovelFlowSettings, NovelLayout, NovelLine, NovelPage, NovelStage, PaperSize, PaperSizeId, ParseError, ParsedGrapheme, ParsedManuscript, PositionedGrapheme, ProofreadError, ProofreadingReport, ProofreadingRuleMeta, Rejection, RubyAnnotation, RubyKind, RubyReading, SourcePosition, SourceRange, SuppressedGrapheme, TextRange, ValidationIssue, VerticalTextPresentation, assertNever, composeManuscript, consistentKanjiOpeningRule, consistentLatinWidthRule, consistentNumeralWidthRule, createConsistentKanjiOpeningRule, createDashRule, createDefaultProofreadingRules, createMaxArabicNumeralDigitsRule, createNovelComposer, createParagraphOpeningRule, dashRule, ellipsisCharacterRule, evenEllipsisRule, fullwidthJapanesePunctuationRule, kakuyomuParser, logicalInlineMeasurer, maxArabicNumeralDigitsRule, minusBeforeNumberRule, mmToPt, noConsecutiveInterpunctRule, noConsecutivePunctuationRule, novelComposer, paragraphOpeningRule, parseManuscript, pixivParser, plainTextParser, proofreadManuscript, ptToMm, punctuationBeforeClosingQuoteRule, spaceAfterQuestionOrExclamationRule, variantCharacterRule };
3036
+ export { BoldAnnotation, ComposeError, ComposedAnnotationFragment, ComposedInlineItem, CompositionStatistics, DiagnosticOrigin, DiagnosticSeverity, DisplayRange, EmphasisAnnotation, FontPreset, FontPresetId, FontSizePt, GraphemeRange, InlineMeasurement, InlineSpan, InvalidRuleOptions, ItalicAnnotation, LineBreakResult, LineOffset, ManuscriptAnnotation, ManuscriptAppearanceSettings, ManuscriptDiagnostic, ManuscriptGeometry, ManuscriptOffsets, ManuscriptRange, ManuscriptResult, NamespacedId, NovelCompositionSettings, NovelFlowSettings, NovelLayout, NovelLine, NovelPage, NovelStage, PaperSize, PaperSizeId, ParseError, ParsedGrapheme, ParsedManuscript, ProofreadError, ProofreadingReport, ProofreadingRuleMeta, Rejection, RubyAnnotation, RubyKind, RubyReading, SourcePosition, SourceRange, TextRange, ValidationIssue, VerticalTextPresentation, assertNever, composeManuscript, consistentKanjiOpeningRule, consistentLatinWidthRule, consistentNumeralWidthRule, createConsistentKanjiOpeningRule, createDashRule, createDefaultProofreadingRules, createMaxArabicNumeralDigitsRule, createNovelComposer, createParagraphOpeningRule, dashRule, ellipsisCharacterRule, evenEllipsisRule, fullwidthJapanesePunctuationRule, kakuyomuParser, logicalInlineMeasurer, maxArabicNumeralDigitsRule, minusBeforeNumberRule, mmToPt, noConsecutiveInterpunctRule, noConsecutivePunctuationRule, novelComposer, paragraphOpeningRule, parseManuscript, pixivParser, plainTextParser, proofreadManuscript, ptToMm, punctuationBeforeClosingQuoteRule, spaceAfterQuestionOrExclamationRule, variantCharacterRule };