@sushichan044/kg-core 0.6.3 → 0.6.5

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 +21 -8
  2. package/dist/index.d.mts +24503 -20300
  3. package/dist/index.mjs +634 -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,420 @@ 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: 0,
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
+ /**
1499
+ * Spending capacity at this priority changes nothing the reader can see.
1500
+ */
1501
+ const FREE_SHRINK_PRIORITY = 0;
1502
+ function skipSourceGaps(atoms, start) {
1503
+ let cursor = start;
1504
+ while (atoms[cursor]?.sourceGap === true) cursor += 1;
1505
+ return cursor;
1506
+ }
1507
+ function previousVisible(atoms, end) {
1508
+ for (let index = end - 1; index >= 0; index -= 1) if (atoms[index]?.sourceGap === false) return index;
1509
+ }
1510
+ function opportunities(atoms, classes, start, end, profile) {
1511
+ const result = [];
1512
+ const firstClass = classes[start];
1513
+ const startSpacing = firstClass === void 0 ? null : profile.lineStartSpacing(firstClass);
1514
+ if (startSpacing !== null) result.push({
1515
+ boundary: start,
1516
+ spacing: startSpacing
1517
+ });
1518
+ for (let right = start + 1; right < end; right += 1) {
1519
+ const leftAtom = atoms[right - 1];
1520
+ const rightAtom = atoms[right];
1521
+ const leftClass = classes[right - 1];
1522
+ const rightClass = classes[right];
1523
+ if (leftAtom === void 0 || rightAtom === void 0 || leftClass === void 0 || rightClass === void 0 || leftAtom.sourceGap || rightAtom.sourceGap || !leftAtom.pairSpacingAfter) continue;
1524
+ result.push({
1525
+ boundary: right,
1526
+ spacing: profile.pairSpacing(leftClass, rightClass)
1527
+ });
1528
+ }
1529
+ const lastClass = classes[end - 1];
1530
+ const endSpacing = lastClass === void 0 ? null : profile.lineEndSpacing(lastClass);
1531
+ if (endSpacing !== null) result.push({
1532
+ boundary: end,
1533
+ spacing: endSpacing
1534
+ });
1535
+ return result;
1536
+ }
1537
+ function capacity(values, select) {
1538
+ return values.reduce((total, opportunity) => total + (select(opportunity.spacing)?.amountEm ?? 0), 0);
1539
+ }
1540
+ function resolveSpacings(values, adjustment, amountEm) {
1541
+ const remainingByBoundary = /* @__PURE__ */ new Map();
1542
+ let remaining = amountEm;
1543
+ let priorityCost = 0;
1544
+ const capacities = values.flatMap((opportunity) => {
1545
+ const selected = adjustment === "shrink" ? opportunity.spacing.shrink : opportunity.spacing.stretch;
1546
+ return selected === void 0 ? [] : [{
1547
+ ...opportunity,
1548
+ capacity: selected
1549
+ }];
1550
+ }).sort((left, right) => left.capacity.priority - right.capacity.priority || left.boundary - right.boundary);
1551
+ for (const opportunity of capacities) {
1552
+ if (remaining <= EPSILON) break;
1553
+ const used = Math.min(remaining, opportunity.capacity.amountEm);
1554
+ remainingByBoundary.set(opportunity.boundary, used);
1555
+ priorityCost += used * opportunity.capacity.priority;
1556
+ remaining -= used;
1557
+ }
1558
+ return {
1559
+ spacings: values.map(({ boundary, spacing }) => {
1560
+ const used = remainingByBoundary.get(boundary) ?? 0;
1561
+ return {
1562
+ boundary,
1563
+ kind: spacing.kind,
1564
+ naturalWidthEm: spacing.naturalWidthEm,
1565
+ widthEm: adjustment === "shrink" ? spacing.naturalWidthEm - used : spacing.naturalWidthEm + used
1566
+ };
1567
+ }),
1568
+ priorityCost
1569
+ };
1570
+ }
1571
+ function candidate(atoms, classes, start, end, lineLengthEm, profile) {
1572
+ const contentStart = skipSourceGaps(atoms, start);
1573
+ const suppressedIndexes = Array.from({ length: contentStart - start }, (_, index) => start + index);
1574
+ const pairValues = opportunities(atoms, classes, contentStart, end, profile);
1575
+ const naturalSizeEm = atoms.slice(contentStart, end).reduce((total, atom) => total + atom.boxAdvanceEm, 0) + pairValues.reduce((total, value) => total + value.spacing.naturalWidthEm, 0);
1576
+ const terminal = skipSourceGaps(atoms, end) === atoms.length;
1577
+ const shrinkCapacity = capacity(pairValues, ({ shrink }) => shrink);
1578
+ const freeShrinkCapacity = capacity(pairValues, ({ shrink }) => shrink?.priority === FREE_SHRINK_PRIORITY ? shrink : void 0);
1579
+ const stretchCapacity = capacity(pairValues, ({ stretch }) => stretch);
1580
+ const overflow = naturalSizeEm - lineLengthEm;
1581
+ const underflow = lineLengthEm - naturalSizeEm;
1582
+ const lastVisible = previousVisible(atoms, end);
1583
+ const lastClass = lastVisible === void 0 ? void 0 : classes[lastVisible];
1584
+ const lastAdvance = lastVisible === void 0 ? 0 : atoms[lastVisible]?.boxAdvanceEm ?? 0;
1585
+ const trailingSpacing = pairValues.find(({ boundary }) => boundary === end)?.spacing.naturalWidthEm ?? 0;
1586
+ if (terminal && overflow <= EPSILON) return {
1587
+ start,
1588
+ contentStart,
1589
+ end,
1590
+ suppressedIndexes,
1591
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings,
1592
+ inlineSizeEm: naturalSizeEm,
1593
+ break: { kind: "paragraph-end" },
1594
+ hangingIndex: null,
1595
+ deformationRatio: 0,
1596
+ priorityCost: 0
1597
+ };
1598
+ if (Math.abs(overflow) <= EPSILON) return {
1599
+ start,
1600
+ contentStart,
1601
+ end,
1602
+ suppressedIndexes,
1603
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings,
1604
+ inlineSizeEm: naturalSizeEm,
1605
+ break: { kind: "natural" },
1606
+ hangingIndex: null,
1607
+ deformationRatio: 0,
1608
+ priorityCost: 0
1609
+ };
1610
+ if (overflow > 0 && overflow <= shrinkCapacity + EPSILON) {
1611
+ const resolved = resolveSpacings(pairValues, "shrink", overflow);
1612
+ const chargeableCapacity = shrinkCapacity - freeShrinkCapacity;
1613
+ const chargeableShrink = Math.max(0, overflow - freeShrinkCapacity);
1614
+ return {
1615
+ start,
1616
+ contentStart,
1617
+ end,
1618
+ suppressedIndexes,
1619
+ pairSpacings: resolved.spacings,
1620
+ inlineSizeEm: lineLengthEm,
1621
+ break: { kind: "shrunk" },
1622
+ hangingIndex: null,
1623
+ deformationRatio: chargeableCapacity <= EPSILON ? 0 : chargeableShrink / chargeableCapacity,
1624
+ priorityCost: resolved.priorityCost
1625
+ };
1626
+ }
1627
+ if (!terminal && overflow > 0 && lastVisible !== void 0 && lastClass !== void 0 && profile.canHang(lastClass) && naturalSizeEm - lastAdvance - trailingSpacing <= lineLengthEm + EPSILON) return {
1628
+ start,
1629
+ contentStart,
1630
+ end,
1631
+ suppressedIndexes,
1632
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings.filter(({ boundary }) => boundary !== end),
1633
+ inlineSizeEm: naturalSizeEm - lastAdvance - trailingSpacing,
1634
+ break: { kind: "hanging" },
1635
+ hangingIndex: lastVisible,
1636
+ deformationRatio: overflow / Math.max(lastAdvance, EPSILON),
1637
+ priorityCost: 0
1638
+ };
1639
+ if (!terminal && underflow > EPSILON && underflow <= stretchCapacity + EPSILON) {
1640
+ const resolved = resolveSpacings(pairValues, "stretch", underflow);
1641
+ return {
1642
+ start,
1643
+ contentStart,
1644
+ end,
1645
+ suppressedIndexes,
1646
+ pairSpacings: resolved.spacings,
1647
+ inlineSizeEm: lineLengthEm,
1648
+ break: { kind: "stretched" },
1649
+ hangingIndex: null,
1650
+ deformationRatio: stretchCapacity === 0 ? 0 : underflow / stretchCapacity,
1651
+ priorityCost: resolved.priorityCost
1652
+ };
1653
+ }
1654
+ return {
1655
+ start,
1656
+ contentStart,
1657
+ end,
1658
+ suppressedIndexes,
1659
+ pairSpacings: resolveSpacings(pairValues, "natural", 0).spacings,
1660
+ inlineSizeEm: naturalSizeEm,
1661
+ break: { kind: "forced" },
1662
+ hangingIndex: null,
1663
+ deformationRatio: Math.abs(naturalSizeEm - lineLengthEm) / Math.max(lineLengthEm, EPSILON),
1664
+ priorityCost: 0
1665
+ };
1666
+ }
1667
+ function scoreFor(line, previousFitness) {
1668
+ const mode = line.break.kind;
1669
+ const fitness = Math.min(3, Math.floor(line.deformationRatio * 4));
1670
+ const transition = Math.abs(previousFitness - fitness) > 1 ? 1 : 0;
1671
+ const modeCounts = {
1672
+ forced: mode === "forced" ? 1 : 0,
1673
+ stretched: mode === "stretched" ? 1 : 0,
1674
+ hanging: mode === "hanging" ? 1 : 0,
1675
+ shrunk: mode === "shrunk" && line.priorityCost > EPSILON ? 1 : 0
1676
+ };
1677
+ return {
1678
+ score: [
1679
+ modeCounts.forced,
1680
+ modeCounts.stretched,
1681
+ modeCounts.hanging,
1682
+ modeCounts.shrunk,
1683
+ line.priorityCost,
1684
+ line.deformationRatio ** 3,
1685
+ transition
1686
+ ],
1687
+ fitness
1688
+ };
1689
+ }
1690
+ function addScore(left, right) {
1691
+ return [
1692
+ left[0] + right[0],
1693
+ left[1] + right[1],
1694
+ left[2] + right[2],
1695
+ left[3] + right[3],
1696
+ left[4] + right[4],
1697
+ left[5] + right[5],
1698
+ left[6] + right[6]
1699
+ ];
1700
+ }
1701
+ function compareBreaks(left, right) {
1702
+ const length = Math.min(left.length, right.length);
1703
+ for (let index = 0; index < length; index += 1) {
1704
+ const difference = (right[index] ?? 0) - (left[index] ?? 0);
1705
+ if (difference !== 0) return difference;
1706
+ }
1707
+ return left.length - right.length;
1708
+ }
1709
+ function isBetter(candidateState, current) {
1710
+ if (current === void 0) return true;
1711
+ for (let index = 0; index < candidateState.score.length; index += 1) {
1712
+ const difference = (candidateState.score[index] ?? 0) - (current.score[index] ?? 0);
1713
+ if (Math.abs(difference) > EPSILON) return difference < 0;
1714
+ }
1715
+ return compareBreaks(candidateState.breaks, current.breaks) < 0;
1716
+ }
1717
+ function layoutParagraph(atoms, lineLengthEm, profile, boundaryAllowed) {
1718
+ if (atoms.length === 0) return [];
1719
+ const classes = atoms.map(({ characterClass }) => characterClass);
1720
+ const states = /* @__PURE__ */ new Map([[0, /* @__PURE__ */ new Map([[0, {
1721
+ score: [
1722
+ 0,
1723
+ 0,
1724
+ 0,
1725
+ 0,
1726
+ 0,
1727
+ 0,
1728
+ 0
1729
+ ],
1730
+ fitness: 0,
1731
+ previous: null,
1732
+ line: null,
1733
+ breaks: []
1734
+ }]])]]);
1735
+ for (let start = 0; start < atoms.length; start += 1) {
1736
+ const activeStates = states.get(start);
1737
+ if (activeStates === void 0) continue;
1738
+ const contentStart = skipSourceGaps(atoms, start);
1739
+ if (contentStart === atoms.length) {
1740
+ const terminalStates = states.get(atoms.length) ?? /* @__PURE__ */ new Map();
1741
+ for (const state of activeStates.values()) if (isBetter(state, terminalStates.get(state.fitness))) terminalStates.set(state.fitness, state);
1742
+ states.set(atoms.length, terminalStates);
1743
+ continue;
1744
+ }
1745
+ for (const state of activeStates.values()) for (let end = contentStart + 1; end <= atoms.length; end += 1) {
1746
+ const right = skipSourceGaps(atoms, end);
1747
+ const left = previousVisible(atoms, end);
1748
+ if (atoms[end - 1]?.sourceGap === true) continue;
1749
+ if (end < atoms.length && right < atoms.length && (left === void 0 || !boundaryAllowed(left, right))) continue;
1750
+ const line = candidate(atoms, classes, start, end, lineLengthEm, profile);
1751
+ 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))) {
1752
+ if (line.inlineSizeEm > lineLengthEm * 2 && end > contentStart + 1) break;
1753
+ continue;
1754
+ }
1755
+ const lineScore = scoreFor(line, state.fitness);
1756
+ const nextState = {
1757
+ score: addScore(state.score, lineScore.score),
1758
+ fitness: lineScore.fitness,
1759
+ previous: {
1760
+ index: start,
1761
+ fitness: state.fitness
1762
+ },
1763
+ line,
1764
+ breaks: [...state.breaks, end]
1765
+ };
1766
+ const statesAtEnd = states.get(end) ?? /* @__PURE__ */ new Map();
1767
+ if (isBetter(nextState, statesAtEnd.get(nextState.fitness))) {
1768
+ statesAtEnd.set(nextState.fitness, nextState);
1769
+ states.set(end, statesAtEnd);
1770
+ }
1771
+ if (line.inlineSizeEm > lineLengthEm * 2 && line.break.kind === "forced" && end > contentStart + 1) break;
1772
+ }
1773
+ }
1774
+ const terminal = [...states.get(atoms.length)?.values() ?? []].reduce((best, state) => isBetter(state, best) ? state : best, void 0);
1775
+ if (terminal === void 0) return [];
1776
+ const lines = [];
1777
+ let cursor = terminal;
1778
+ while (cursor?.line !== null && cursor?.line !== void 0) {
1779
+ lines.push(cursor.line);
1780
+ cursor = cursor.previous === null ? void 0 : states.get(cursor.previous.index)?.get(cursor.previous.fitness);
1781
+ }
1782
+ lines.reverse();
1783
+ const last = lines.at(-1);
1784
+ if (last !== void 0 && last.end < atoms.length) lines[lines.length - 1] = {
1785
+ ...last,
1786
+ suppressedIndexes: [...last.suppressedIndexes, ...Array.from({ length: atoms.length - last.end }, (_, index) => last.end + index)]
1787
+ };
1788
+ return lines;
1789
+ }
1339
1790
  const NovelLine = {
1340
1791
  schema: readonlyObject({
1341
1792
  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),
1793
+ inlineSizeEm: v.pipe(v.number(), v.finite(), v.minValue(0)),
1794
+ items: readonlyArray(ComposedInlineItem.schema),
1795
+ break: LineBreakResult.schema,
1345
1796
  annotations: readonlyArray(ComposedAnnotationFragment.schema)
1346
1797
  }),
1347
1798
  empty: () => ({
1348
1799
  range: null,
1349
- advanceEm: 0,
1350
- graphemes: [],
1351
- suppressed: [],
1800
+ inlineSizeEm: 0,
1801
+ items: [],
1802
+ break: { kind: "paragraph-end" },
1352
1803
  annotations: []
1353
1804
  })
1354
1805
  };
@@ -1380,15 +1831,7 @@ const NovelLayout = { schema: readonlyObject({
1380
1831
  //#endregion
1381
1832
  //#region src/composer/novel-composer.ts
1382
1833
  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
- ]);
1834
+ const TYPESETTING_PROFILE = defaultJapaneseTypesettingProfile;
1392
1835
  const ASCII_ALPHANUMERIC = /^[A-Za-z0-9]$/u;
1393
1836
  const ASCII_TWO_DIGITS = /^[0-9]{2}$/u;
1394
1837
  const UPRIGHT_LATIN_ABBREVIATION = /^(?:[A-Z]+|[A-Z][a-z]{1,2})$/u;
@@ -1462,7 +1905,7 @@ function measure(measurer, text, role, settings, presentationKind) {
1462
1905
  fontPreset: settings.appearance.fontPreset,
1463
1906
  writingMode: "vertical-rl"
1464
1907
  };
1465
- const value = role === "base" ? measurer({
1908
+ const measurement = role === "base" ? measurer({
1466
1909
  ...context,
1467
1910
  role,
1468
1911
  presentation: presentationKind ?? "mixed"
@@ -1470,14 +1913,25 @@ function measure(measurer, text, role, settings, presentationKind) {
1470
1913
  ...context,
1471
1914
  role
1472
1915
  });
1473
- return Number.isFinite(value) && value >= 0 ? value : void 0;
1916
+ const parsed = v.safeParse(InlineMeasurement.schema, measurement);
1917
+ if (!parsed.success) return void 0;
1918
+ const { advanceEm } = parsed.output;
1919
+ return advanceEm;
1474
1920
  }
1475
1921
  function measureSourceLine(sourceLine, annotations, settings, measurer) {
1476
1922
  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 : {
1923
+ const renderAdvanceEm = measure(measurer, grapheme.value, "base", settings, presentation.kind);
1924
+ if (renderAdvanceEm === void 0) return void 0;
1925
+ const characterClass = TYPESETTING_PROFILE.classify({
1926
+ value: grapheme.value,
1927
+ presentation: presentation.kind
1928
+ });
1929
+ const metrics = TYPESETTING_PROFILE.boxMetrics(characterClass, renderAdvanceEm);
1930
+ return {
1479
1931
  grapheme,
1480
- advanceEm,
1932
+ boxAdvanceEm: metrics.advanceEm,
1933
+ renderAdvanceEm,
1934
+ renderOffsetEm: metrics.renderOffsetEm,
1481
1935
  presentation
1482
1936
  };
1483
1937
  });
@@ -1491,11 +1945,11 @@ function measureSourceLine(sourceLine, annotations, settings, measurer) {
1491
1945
  if (annotation.reading.kind === "group") {
1492
1946
  const readingAdvance = measure(measurer, annotation.reading.text, "ruby", settings);
1493
1947
  if (readingAdvance === void 0) return void 0;
1494
- const baseAdvance = indexes.reduce((total, index) => total + (atoms[index]?.advanceEm ?? 0), 0);
1948
+ const baseAdvance = indexes.reduce((total, index) => total + (atoms[index]?.boxAdvanceEm ?? 0), 0);
1495
1949
  const extra = Math.max(0, readingAdvance - baseAdvance) / indexes.length;
1496
1950
  for (const index of indexes) {
1497
1951
  const atom = atoms[index];
1498
- if (atom !== void 0) atom.advanceEm += extra;
1952
+ if (atom !== void 0) atom.boxAdvanceEm += extra;
1499
1953
  }
1500
1954
  continue;
1501
1955
  }
@@ -1505,7 +1959,7 @@ function measureSourceLine(sourceLine, annotations, settings, measurer) {
1505
1959
  if (segment === void 0 || atom === void 0) continue;
1506
1960
  const readingAdvance = measure(measurer, segment, "ruby", settings);
1507
1961
  if (readingAdvance === void 0) return void 0;
1508
- atom.advanceEm = Math.max(atom.advanceEm, readingAdvance);
1962
+ atom.boxAdvanceEm = Math.max(atom.boxAdvanceEm, readingAdvance);
1509
1963
  }
1510
1964
  }
1511
1965
  return {
@@ -1520,17 +1974,23 @@ function breakInsideFittableGroupRuby(atoms, annotations, boundary, lineLengthEm
1520
1974
  return annotations.some((annotation) => {
1521
1975
  if (annotation.kind !== "ruby" || annotation.reading.kind !== "group") return false;
1522
1976
  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;
1977
+ return indicesInside(atoms, annotation).reduce((total, index) => total + (atoms[index]?.boxAdvanceEm ?? 0), 0) <= lineLengthEm;
1524
1978
  });
1525
1979
  }
1526
1980
  function samePresentationGroup(left, right) {
1527
1981
  return left.presentation.groupRange.graphemes.start === right.presentation.groupRange.graphemes.start && left.presentation.groupRange.graphemes.end === right.presentation.groupRange.graphemes.end;
1528
1982
  }
1529
- function legalBoundary(atoms, annotations, boundary, lineLengthEm) {
1530
- const left = atoms[boundary - 1];
1531
- const right = atoms[boundary];
1983
+ function legalBoundary(atoms, annotations, leftIndex, rightIndex, lineLengthEm, profile) {
1984
+ const left = atoms[leftIndex];
1985
+ const right = atoms[rightIndex];
1532
1986
  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);
1987
+ return !samePresentationGroup(left, right) && profile.breakPenalty(profile.classify({
1988
+ value: left.grapheme.value,
1989
+ presentation: left.presentation.kind
1990
+ }), profile.classify({
1991
+ value: right.grapheme.value,
1992
+ presentation: right.presentation.kind
1993
+ })) !== null && !breakInsideFittableGroupRuby(atoms, annotations, rightIndex, lineLengthEm);
1534
1994
  }
1535
1995
  function continuationFor(annotation, range) {
1536
1996
  const starts = range.graphemes.start === annotation.range.graphemes.start;
@@ -1602,14 +2062,18 @@ function positionReading(text, baseOffsetEm, baseAdvanceEm, settings, measurer)
1602
2062
  return positioned;
1603
2063
  });
1604
2064
  }
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)]);
2065
+ function annotationFragmentRange(graphemes, suppressed, sourceGlues, annotation) {
2066
+ return ManuscriptRange.merge([
2067
+ ...graphemes.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range),
2068
+ ...suppressed.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range),
2069
+ ...sourceGlues.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range)).map(({ range }) => range)
2070
+ ]);
1607
2071
  }
1608
- function annotationFragments(graphemes, suppressed, annotations, settings, measurer, fragmentPlansByAnnotation) {
2072
+ function annotationFragments(graphemes, suppressed, sourceGlues, annotations, settings, measurer, fragmentPlansByAnnotation) {
1609
2073
  const fragments = [];
1610
2074
  for (const annotation of annotations) {
1611
2075
  const covered = graphemes.filter(({ range }) => ManuscriptRange.overlaps(range, annotation.range));
1612
- const fragmentRange = annotationFragmentRange(graphemes, suppressed, annotation);
2076
+ const fragmentRange = annotationFragmentRange(graphemes, suppressed, sourceGlues, annotation);
1613
2077
  const first = covered[0];
1614
2078
  const last = covered.at(-1);
1615
2079
  if (fragmentRange === null || first === void 0 || last === void 0) continue;
@@ -1636,8 +2100,8 @@ function annotationFragments(graphemes, suppressed, annotations, settings, measu
1636
2100
  case "ruby": {
1637
2101
  const fragmentPlan = fragmentPlansByAnnotation.get(annotation);
1638
2102
  if (fragmentPlan === void 0) break;
1639
- const baseOffsetEm = first.offsetEm;
1640
- const baseAdvanceEm = last.offsetEm + last.advanceEm - baseOffsetEm;
2103
+ const baseOffsetEm = first.layoutSpan.offsetEm;
2104
+ const baseAdvanceEm = last.layoutSpan.offsetEm + last.layoutSpan.advanceEm - baseOffsetEm;
1641
2105
  const reading = readingForFragment(annotation, fragmentRange, fragmentPlan);
1642
2106
  fragments.push({
1643
2107
  kind: "ruby",
@@ -1654,88 +2118,103 @@ function annotationFragments(graphemes, suppressed, annotations, settings, measu
1654
2118
  }
1655
2119
  return fragments;
1656
2120
  }
1657
- function positionedLine(atoms, dispositions, suppressed) {
2121
+ function positionedLine(atoms, sourceGapIndexes, plan) {
2122
+ const items = plan.suppressedIndexes.flatMap((index) => {
2123
+ const atom = atoms[index];
2124
+ return atom === void 0 ? [] : [{
2125
+ kind: "suppressed",
2126
+ value: atom.grapheme.value,
2127
+ range: atom.grapheme.range,
2128
+ reason: "question-or-exclamation-gap"
2129
+ }];
2130
+ });
2131
+ const spacings = new Map(plan.pairSpacings.map((spacing) => [spacing.boundary, spacing]));
1658
2132
  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,
2133
+ const positionSpacing = (spacing) => {
2134
+ if (spacing.kind === "kern") items.push({
2135
+ kind: "kern",
1664
2136
  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: []
2137
+ widthEm: spacing.widthEm
2138
+ });
2139
+ else items.push({
2140
+ kind: "glue",
2141
+ origin: "generated",
2142
+ offsetEm,
2143
+ widthEm: spacing.widthEm,
2144
+ naturalWidthEm: spacing.naturalWidthEm,
2145
+ adjustment: spacing.widthEm < spacing.naturalWidthEm ? "shrunk" : spacing.widthEm > spacing.naturalWidthEm ? "stretched" : "natural"
2146
+ });
2147
+ offsetEm += spacing.widthEm;
1678
2148
  };
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",
2149
+ for (let index = plan.contentStart; index < plan.end; index += 1) {
2150
+ const spacing = spacings.get(index);
2151
+ if (spacing !== void 0) positionSpacing(spacing);
2152
+ const atom = atoms[index];
2153
+ if (atom === void 0) continue;
2154
+ if (sourceGapIndexes.has(index)) {
2155
+ items.push({
2156
+ kind: "glue",
2157
+ origin: "source",
1690
2158
  value: atom.grapheme.value,
1691
2159
  range: atom.grapheme.range,
1692
- reason: "question-or-exclamation-gap"
2160
+ offsetEm,
2161
+ widthEm: atom.boxAdvanceEm,
2162
+ naturalWidthEm: atom.boxAdvanceEm,
2163
+ adjustment: "natural"
1693
2164
  });
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;
2165
+ offsetEm += atom.boxAdvanceEm;
2166
+ continue;
1724
2167
  }
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;
2168
+ const hanging = plan.hangingIndex === index;
2169
+ const layoutAdvanceEm = hanging ? 0 : atom.boxAdvanceEm;
2170
+ items.push({
2171
+ kind: "glyph",
2172
+ value: atom.grapheme.value,
2173
+ range: atom.grapheme.range,
2174
+ layoutSpan: {
2175
+ offsetEm,
2176
+ advanceEm: layoutAdvanceEm
2177
+ },
2178
+ renderSpan: {
2179
+ offsetEm: offsetEm + atom.renderOffsetEm,
2180
+ advanceEm: atom.renderAdvanceEm
2181
+ },
2182
+ disposition: hanging ? "hanging" : "placed",
2183
+ presentation: atom.presentation
2184
+ });
2185
+ offsetEm += layoutAdvanceEm;
1735
2186
  }
2187
+ const trailingSpacing = spacings.get(plan.end);
2188
+ if (trailingSpacing !== void 0) positionSpacing(trailingSpacing);
2189
+ const ranges = items.flatMap((item) => item.kind === "glyph" || item.kind === "suppressed" || item.kind === "glue" && item.origin === "source" ? [item.range] : []);
2190
+ return {
2191
+ range: ManuscriptRange.merge(ranges),
2192
+ inlineSizeEm: Math.max(0, plan.inlineSizeEm),
2193
+ items,
2194
+ break: plan.break,
2195
+ annotations: []
2196
+ };
2197
+ }
2198
+ function wrapSourceLine(sourceLine, annotations, settings, measurer, baseAdvances) {
2199
+ if (sourceLine.atoms.length === 0) return [NovelLine.empty()];
2200
+ const profile = TYPESETTING_PROFILE;
2201
+ const lines = layoutParagraph(sourceLine.atoms.map(({ grapheme, boxAdvanceEm, presentation }, index) => {
2202
+ const current = sourceLine.atoms[index];
2203
+ const next = sourceLine.atoms[index + 1];
2204
+ return {
2205
+ value: grapheme.value,
2206
+ boxAdvanceEm,
2207
+ sourceGap: sourceLine.suppressedIndexes.has(index),
2208
+ characterClass: profile.classify({
2209
+ value: grapheme.value,
2210
+ presentation: presentation.kind
2211
+ }),
2212
+ pairSpacingAfter: current === void 0 || next === void 0 || !samePresentationGroup(current, next) && !breakInsideFittableGroupRuby(sourceLine.atoms, annotations, index + 1, settings.flow.lineLengthEm)
2213
+ };
2214
+ }), 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
2215
  const fragmentPlansByAnnotation = new Map(annotations.map((annotation) => {
1737
2216
  const ranges = lines.flatMap((line) => {
1738
- const range = annotationFragmentRange(line.graphemes, line.suppressed, annotation);
2217
+ 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
2218
  return range === null ? [] : [range];
1740
2219
  });
1741
2220
  return [annotation, {
@@ -1745,10 +2224,10 @@ function wrapSourceLine(sourceLine, annotations, settings, measurer, baseAdvance
1745
2224
  }));
1746
2225
  return lines.map((line) => ({
1747
2226
  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)
2227
+ inlineSizeEm: line.inlineSizeEm,
2228
+ items: line.items,
2229
+ break: line.break,
2230
+ 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
2231
  }));
1753
2232
  }
1754
2233
  function blankLines(count) {
@@ -1789,7 +2268,7 @@ function createCompose(measurer) {
1789
2268
  const measured = sourceLines.map((line) => measureSourceLine(line, manuscript.annotations, settings, measurer));
1790
2269
  if (measured.some((line) => line === void 0)) return ManuscriptResult.fail({ reason: "inline measurer returned a negative or non-finite value" });
1791
2270
  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);
2271
+ for (const line of measured) for (const atom of line?.atoms ?? []) baseAdvances.set(atom.grapheme.range.graphemes.start, atom.boxAdvanceEm);
1793
2272
  const manuscriptLines = measured.flatMap((line) => line === void 0 ? [] : wrapSourceLine(line, manuscript.annotations, settings, measurer, baseAdvances));
1794
2273
  const pages = buildPages([
1795
2274
  ...blankLines(settings.offsets.document.leading),
@@ -2561,4 +3040,4 @@ function createDefaultProofreadingRules() {
2561
3040
  ];
2562
3041
  }
2563
3042
  //#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 };
3043
+ 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 };