postext 1.28.5 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/__tests__/arabicLocale.test.js +9 -3
  2. package/dist/__tests__/arabicLocale.test.js.map +1 -1
  3. package/dist/__tests__/bundle/balloonPlacementPin.test.js +5 -1
  4. package/dist/__tests__/bundle/balloonPlacementPin.test.js.map +1 -1
  5. package/dist/__tests__/bundle/cjkRulesPin.test.js +15 -9
  6. package/dist/__tests__/bundle/cjkRulesPin.test.js.map +1 -1
  7. package/dist/__tests__/bundle/designOverflowPin.test.js +3 -3
  8. package/dist/__tests__/bundle/designOverflowPin.test.js.map +1 -1
  9. package/dist/__tests__/bundle/flowColumnsPin.test.js +4 -1
  10. package/dist/__tests__/bundle/flowColumnsPin.test.js.map +1 -1
  11. package/dist/__tests__/bundle/gridBalancingPin.test.js +6 -2
  12. package/dist/__tests__/bundle/gridBalancingPin.test.js.map +1 -1
  13. package/dist/__tests__/bundle/gridBreakRulesPin.test.js +23 -17
  14. package/dist/__tests__/bundle/gridBreakRulesPin.test.js.map +1 -1
  15. package/dist/__tests__/bundle/indexGroupSplitPin.test.js +5 -2
  16. package/dist/__tests__/bundle/indexGroupSplitPin.test.js.map +1 -1
  17. package/dist/__tests__/bundle/looseOrderPin.test.js +5 -1
  18. package/dist/__tests__/bundle/looseOrderPin.test.js.map +1 -1
  19. package/dist/__tests__/bundle/refLabelBreaksPin.test.js +5 -1
  20. package/dist/__tests__/bundle/refLabelBreaksPin.test.js.map +1 -1
  21. package/dist/__tests__/bundle/scriptContextPin.test.js +7 -2
  22. package/dist/__tests__/bundle/scriptContextPin.test.js.map +1 -1
  23. package/dist/__tests__/bundle/shapingLanguagePin.test.d.ts +2 -0
  24. package/dist/__tests__/bundle/shapingLanguagePin.test.d.ts.map +1 -0
  25. package/dist/__tests__/bundle/shapingLanguagePin.test.js +93 -0
  26. package/dist/__tests__/bundle/shapingLanguagePin.test.js.map +1 -0
  27. package/dist/__tests__/bundle/taskBoxPin.test.d.ts +2 -0
  28. package/dist/__tests__/bundle/taskBoxPin.test.d.ts.map +1 -0
  29. package/dist/__tests__/bundle/taskBoxPin.test.js +94 -0
  30. package/dist/__tests__/bundle/taskBoxPin.test.js.map +1 -0
  31. package/dist/__tests__/bundle/trailingStepPin.test.js +5 -2
  32. package/dist/__tests__/bundle/trailingStepPin.test.js.map +1 -1
  33. package/dist/__tests__/bundle/wideFloatStretchPin.test.js +5 -2
  34. package/dist/__tests__/bundle/wideFloatStretchPin.test.js.map +1 -1
  35. package/dist/__tests__/bundle.test.js +5 -5
  36. package/dist/__tests__/bundle.test.js.map +1 -1
  37. package/dist/__tests__/chineseLocale.test.js +7 -3
  38. package/dist/__tests__/chineseLocale.test.js.map +1 -1
  39. package/dist/__tests__/cjk/japaneseHtml.test.js +13 -3
  40. package/dist/__tests__/cjk/japaneseHtml.test.js.map +1 -1
  41. package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js +16 -3
  42. package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js.map +1 -1
  43. package/dist/__tests__/codeBlocks.test.js +1 -1
  44. package/dist/__tests__/exports.test.js +14 -0
  45. package/dist/__tests__/exports.test.js.map +1 -1
  46. package/dist/__tests__/hardBreaks.test.js +1 -1
  47. package/dist/__tests__/headingInlineMarks.test.js +1 -1
  48. package/dist/__tests__/mathSizeMigration.test.js +4 -4
  49. package/dist/__tests__/mathSizeMigration.test.js.map +1 -1
  50. package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
  51. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +3 -3
  52. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js.map +1 -1
  53. package/dist/__tests__/pipeline/colonListRoom.test.js +3 -3
  54. package/dist/__tests__/pipeline/colonListRoom.test.js.map +1 -1
  55. package/dist/__tests__/pipeline/compoundBreaks.test.js +6 -6
  56. package/dist/__tests__/pipeline/compoundBreaks.test.js.map +1 -1
  57. package/dist/__tests__/pipeline/dashBreaks.test.js +5 -5
  58. package/dist/__tests__/pipeline/dashBreaks.test.js.map +1 -1
  59. package/dist/__tests__/pipeline/headingSplitOrphans.test.js +3 -3
  60. package/dist/__tests__/pipeline/headingSplitOrphans.test.js.map +1 -1
  61. package/dist/__tests__/pipeline/inlineFigureSpace.test.js +5 -5
  62. package/dist/__tests__/pipeline/inlineFigureSpace.test.js.map +1 -1
  63. package/dist/__tests__/pipeline/paragraphContainerTail.test.js +7 -7
  64. package/dist/__tests__/pipeline/paragraphContainerTail.test.js.map +1 -1
  65. package/dist/__tests__/pipeline/raggedOptimal.test.js +6 -6
  66. package/dist/__tests__/pipeline/raggedOptimal.test.js.map +1 -1
  67. package/dist/__tests__/rtl/htmlRtl.test.js +12 -1
  68. package/dist/__tests__/rtl/htmlRtl.test.js.map +1 -1
  69. package/dist/__tests__/shapingLanguage.test.d.ts +2 -0
  70. package/dist/__tests__/shapingLanguage.test.d.ts.map +1 -0
  71. package/dist/__tests__/shapingLanguage.test.js +335 -0
  72. package/dist/__tests__/shapingLanguage.test.js.map +1 -0
  73. package/dist/__tests__/taskBox.test.d.ts +2 -0
  74. package/dist/__tests__/taskBox.test.d.ts.map +1 -0
  75. package/dist/__tests__/taskBox.test.js +291 -0
  76. package/dist/__tests__/taskBox.test.js.map +1 -0
  77. package/dist/__tests__/verseLines.test.js +7 -7
  78. package/dist/__tests__/verseLines.test.js.map +1 -1
  79. package/dist/bundle/configVersion.d.ts +48 -7
  80. package/dist/bundle/configVersion.d.ts.map +1 -1
  81. package/dist/bundle/configVersion.js +79 -6
  82. package/dist/bundle/configVersion.js.map +1 -1
  83. package/dist/bundle/index.d.ts +2 -1
  84. package/dist/bundle/index.d.ts.map +1 -1
  85. package/dist/bundle/index.js +2 -1
  86. package/dist/bundle/index.js.map +1 -1
  87. package/dist/canvas-backend/blockRender.d.ts.map +1 -1
  88. package/dist/canvas-backend/blockRender.js +41 -2
  89. package/dist/canvas-backend/blockRender.js.map +1 -1
  90. package/dist/canvas-backend/index.d.ts.map +1 -1
  91. package/dist/canvas-backend/index.js +10 -5
  92. package/dist/canvas-backend/index.js.map +1 -1
  93. package/dist/canvas-backend/trackedText.d.ts.map +1 -1
  94. package/dist/canvas-backend/trackedText.js +9 -5
  95. package/dist/canvas-backend/trackedText.js.map +1 -1
  96. package/dist/canvas-backend/wordRuns.d.ts +6 -0
  97. package/dist/canvas-backend/wordRuns.d.ts.map +1 -1
  98. package/dist/canvas-backend/wordRuns.js +31 -0
  99. package/dist/canvas-backend/wordRuns.js.map +1 -1
  100. package/dist/comics/lettering/letter.d.ts.map +1 -1
  101. package/dist/comics/lettering/letter.js +2 -1
  102. package/dist/comics/lettering/letter.js.map +1 -1
  103. package/dist/contrast.d.ts.map +1 -1
  104. package/dist/contrast.js +5 -0
  105. package/dist/contrast.js.map +1 -1
  106. package/dist/defaults/bodyText.d.ts.map +1 -1
  107. package/dist/defaults/bodyText.js +8 -0
  108. package/dist/defaults/bodyText.js.map +1 -1
  109. package/dist/defaults/unorderedLists.d.ts.map +1 -1
  110. package/dist/defaults/unorderedLists.js +7 -0
  111. package/dist/defaults/unorderedLists.js.map +1 -1
  112. package/dist/html-backend.d.ts.map +1 -1
  113. package/dist/html-backend.js +45 -5
  114. package/dist/html-backend.js.map +1 -1
  115. package/dist/index.d.ts +3 -1
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +5 -1
  118. package/dist/index.js.map +1 -1
  119. package/dist/locale.d.ts +36 -14
  120. package/dist/locale.d.ts.map +1 -1
  121. package/dist/locale.js +39 -12
  122. package/dist/locale.js.map +1 -1
  123. package/dist/measure/cache.js +2 -2
  124. package/dist/measure/cache.js.map +1 -1
  125. package/dist/measure/canvas.d.ts +20 -0
  126. package/dist/measure/canvas.d.ts.map +1 -1
  127. package/dist/measure/canvas.js +131 -50
  128. package/dist/measure/canvas.js.map +1 -1
  129. package/dist/measure/cjkAnnotate.js +2 -2
  130. package/dist/measure/cjkAnnotate.js.map +1 -1
  131. package/dist/measure/cjkCompose.d.ts.map +1 -1
  132. package/dist/measure/cjkCompose.js +225 -209
  133. package/dist/measure/cjkCompose.js.map +1 -1
  134. package/dist/measure/plain.d.ts.map +1 -1
  135. package/dist/measure/plain.js +4 -2
  136. package/dist/measure/plain.js.map +1 -1
  137. package/dist/measure/pretextLanguage.d.ts +12 -0
  138. package/dist/measure/pretextLanguage.d.ts.map +1 -0
  139. package/dist/measure/pretextLanguage.js +117 -0
  140. package/dist/measure/pretextLanguage.js.map +1 -0
  141. package/dist/measure/rich.d.ts +4 -0
  142. package/dist/measure/rich.d.ts.map +1 -1
  143. package/dist/measure/rich.js +164 -137
  144. package/dist/measure/rich.js.map +1 -1
  145. package/dist/measure/rubyLift.d.ts.map +1 -1
  146. package/dist/measure/rubyLift.js +4 -3
  147. package/dist/measure/rubyLift.js.map +1 -1
  148. package/dist/measure/spanLanguage.d.ts +11 -0
  149. package/dist/measure/spanLanguage.d.ts.map +1 -0
  150. package/dist/measure/spanLanguage.js +19 -0
  151. package/dist/measure/spanLanguage.js.map +1 -0
  152. package/dist/measure/vertical.d.ts.map +1 -1
  153. package/dist/measure/vertical.js +4 -3
  154. package/dist/measure/vertical.js.map +1 -1
  155. package/dist/pipeline/build.d.ts.map +1 -1
  156. package/dist/pipeline/build.js +15 -2
  157. package/dist/pipeline/build.js.map +1 -1
  158. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  159. package/dist/pipeline/calloutLayout.js +10 -4
  160. package/dist/pipeline/calloutLayout.js.map +1 -1
  161. package/dist/pipeline/dropCap.js +2 -2
  162. package/dist/pipeline/dropCap.js.map +1 -1
  163. package/dist/pipeline/lists.d.ts +8 -0
  164. package/dist/pipeline/lists.d.ts.map +1 -1
  165. package/dist/pipeline/lists.js +16 -1
  166. package/dist/pipeline/lists.js.map +1 -1
  167. package/dist/pipeline/toc.d.ts.map +1 -1
  168. package/dist/pipeline/toc.js +5 -3
  169. package/dist/pipeline/toc.js.map +1 -1
  170. package/dist/taskBox.d.ts +55 -0
  171. package/dist/taskBox.d.ts.map +1 -0
  172. package/dist/taskBox.js +79 -0
  173. package/dist/taskBox.js.map +1 -0
  174. package/dist/types.d.ts +48 -1
  175. package/dist/types.d.ts.map +1 -1
  176. package/dist/vdt.d.ts +14 -0
  177. package/dist/vdt.d.ts.map +1 -1
  178. package/dist/vdt.js.map +1 -1
  179. package/package.json +1 -1
@@ -35,7 +35,8 @@
35
35
  import { createBoundingBox } from '../vdt';
36
36
  import { isJapaneseLanguage } from '../locale';
37
37
  import { lineIndentAt, lineInsetsAt, lineMeasure, markInsetLines } from './types';
38
- import { measureInkBox, measureInkExtent, measureTextWidth, normalSpaceWidthFor } from './canvas';
38
+ import { getMeasureLanguage, measureInkBox, measureInkExtent, measureTextWidth, normalSpaceWidthFor, setMeasureLanguage } from './canvas';
39
+ import { spanShapingLanguage } from './spanLanguage';
39
40
  import { atomicSpanToken, refLabelTokens, setLabelTabs, emergencySplit, expandSmallCaps, pickSpanFont, scriptMetrics, setsObject, spanScriptFields, stackedScriptPairs, textWidth, tokenSegment, URL_LIKE_RE, urlBreakIndices, } from './rich';
40
41
  import { cjkBreakAllowed, cjkClassOf, getCjkLineBreak, isCjkGrapheme, isFullwidthAlnum, isFullwidthDigit, isJapaneseLineBreak, getCjkCircledNumbers, isLabelEndProhibited, isLineEndProhibited, isLineStartProhibited, isWordInnerMark, setCjkCircledNumbers, } from './cjkClasses';
41
42
  import { hasCJK } from './cjk';
@@ -169,6 +170,10 @@ function spanLanguageHere(span) {
169
170
  lang = d.lang;
170
171
  if (lang === undefined)
171
172
  return undefined;
173
+ // In a document shaped in its own language every run that names one
174
+ // carries it (#764): it was measured in it, and is painted in it.
175
+ if (getMeasureLanguage() !== undefined)
176
+ return lang;
172
177
  return getMeasureRegion() === 'japan' || isJapaneseLanguage(lang) ? lang : undefined;
173
178
  }
174
179
  function styleOf(span, fonts, vertical = false) {
@@ -639,230 +644,241 @@ function buildUnits(spans, fonts, letterSpacingPx, vertical = false, route = fal
639
644
  // Where each span's units start, for the kanbun marks (#430).
640
645
  const spanStarts = [];
641
646
  for (let si = 0; si < spans.length; si++) {
642
- const span = spans[si];
643
- spanStarts.push(units.length);
644
- // A ruby base: one unit, sized with its reading once its neighbours
645
- // are known (`sizeRubies`).
646
- if (span.ruby && span.text.length > 0) {
647
- units.push({ ...rubyBaseUnit(span, fonts, letterSpacingPx, vertical), ...(zwsp ? { zwspBefore: true } : {}), ...glue() });
648
- zwsp = false;
649
- continue;
650
- }
651
- if (vertical && (span.combineUpright || span.orientation) && span.text.length > 0 && !setsObject(span)) {
652
- const first = units.length;
653
- orientedUnits(span, styleOf(span, fonts, vertical), letterSpacingPx, zwsp, units);
654
- if (units.length > first)
655
- Object.assign(units[first], glue());
656
- zwsp = false;
657
- continue;
658
- }
659
- // A reference whose label is words (#702): one unit per word and per
660
- // space, each carrying the reference; only the first is glued to the
661
- // text before it, so a line may break after a space of the label.
662
- const whole = refLabelTokens(span, fonts.normal, fonts.bold, fonts.italic, fonts.boldItalic, letterSpacingPx)
663
- ?? atomicSpanToken(span, fonts.normal, fonts.bold, fonts.italic, fonts.boldItalic, letterSpacingPx);
664
- const atomics = whole === undefined ? [] : Array.isArray(whole) ? whole : [whole];
665
- for (const [k, atomic] of atomics.entries()) {
666
- const style = styleOf(span, fonts, vertical);
667
- if (atomic.kind === 'space') {
647
+ // A run that names its language is measured in it (#764).
648
+ const ownLanguage = spanShapingLanguage(spans[si]);
649
+ const languageBefore = getMeasureLanguage();
650
+ if (ownLanguage !== undefined)
651
+ setMeasureLanguage(ownLanguage);
652
+ try {
653
+ const span = spans[si];
654
+ spanStarts.push(units.length);
655
+ // A ruby base: one unit, sized with its reading once its neighbours
656
+ // are known (`sizeRubies`).
657
+ if (span.ruby && span.text.length > 0) {
658
+ units.push({ ...rubyBaseUnit(span, fonts, letterSpacingPx, vertical), ...(zwsp ? { zwspBefore: true } : {}), ...glue() });
659
+ zwsp = false;
660
+ continue;
661
+ }
662
+ if (vertical && (span.combineUpright || span.orientation) && span.text.length > 0 && !setsObject(span)) {
663
+ const first = units.length;
664
+ orientedUnits(span, styleOf(span, fonts, vertical), letterSpacingPx, zwsp, units);
665
+ if (units.length > first)
666
+ Object.assign(units[first], glue());
667
+ zwsp = false;
668
+ continue;
669
+ }
670
+ // A reference whose label is words (#702): one unit per word and per
671
+ // space, each carrying the reference; only the first is glued to the
672
+ // text before it, so a line may break after a space of the label.
673
+ const whole = refLabelTokens(span, fonts.normal, fonts.bold, fonts.italic, fonts.boldItalic, letterSpacingPx)
674
+ ?? atomicSpanToken(span, fonts.normal, fonts.bold, fonts.italic, fonts.boldItalic, letterSpacingPx);
675
+ const atomics = whole === undefined ? [] : Array.isArray(whole) ? whole : [whole];
676
+ for (const [k, atomic] of atomics.entries()) {
677
+ const style = styleOf(span, fonts, vertical);
678
+ if (atomic.kind === 'space') {
679
+ units.push({
680
+ kind: 'space',
681
+ text: atomic.text,
682
+ width: atomic.width,
683
+ graphemes: graphemeCount(atomic.text),
684
+ first: 'western',
685
+ last: 'western',
686
+ firstCjk: false,
687
+ lastCjk: false,
688
+ style,
689
+ at: 0,
690
+ token: atomic,
691
+ });
692
+ continue;
693
+ }
694
+ const graphemes = graphemesOf(atomic.text);
695
+ const object = atomic.chip !== undefined || atomic.mathRender !== undefined || atomic.swatch !== undefined;
696
+ const firstG = graphemes[0] ?? '';
697
+ const lastG = graphemes[graphemes.length - 1] ?? '';
668
698
  units.push({
669
- kind: 'space',
699
+ kind: 'atomic',
670
700
  text: atomic.text,
671
701
  width: atomic.width,
672
- graphemes: graphemeCount(atomic.text),
673
- first: 'western',
674
- last: 'western',
675
- firstCjk: false,
676
- lastCjk: false,
702
+ graphemes: graphemes.length,
703
+ first: object ? 'ideograph' : cjkClassOf(firstG),
704
+ last: object ? 'ideograph' : cjkClassOf(lastG),
705
+ firstCjk: object || isCjkGrapheme(firstG),
706
+ lastCjk: object || isCjkGrapheme(lastG),
677
707
  style,
678
708
  at: 0,
679
709
  token: atomic,
710
+ ...((atomic.refResourceId !== undefined || atomic.footnoteId !== undefined) && k === 0 ? { glueBefore: true } : {}),
711
+ ...(zwsp ? { zwspBefore: true } : {}),
712
+ ...glue(),
680
713
  });
681
- continue;
714
+ zwsp = false;
682
715
  }
683
- const graphemes = graphemesOf(atomic.text);
684
- const object = atomic.chip !== undefined || atomic.mathRender !== undefined || atomic.swatch !== undefined;
685
- const firstG = graphemes[0] ?? '';
686
- const lastG = graphemes[graphemes.length - 1] ?? '';
687
- units.push({
688
- kind: 'atomic',
689
- text: atomic.text,
690
- width: atomic.width,
691
- graphemes: graphemes.length,
692
- first: object ? 'ideograph' : cjkClassOf(firstG),
693
- last: object ? 'ideograph' : cjkClassOf(lastG),
694
- firstCjk: object || isCjkGrapheme(firstG),
695
- lastCjk: object || isCjkGrapheme(lastG),
696
- style,
697
- at: 0,
698
- token: atomic,
699
- ...((atomic.refResourceId !== undefined || atomic.footnoteId !== undefined) && k === 0 ? { glueBefore: true } : {}),
700
- ...(zwsp ? { zwspBefore: true } : {}),
701
- ...glue(),
702
- });
703
- zwsp = false;
704
- }
705
- if (atomics.length > 0)
706
- continue;
707
- if (span.text.length === 0)
708
- continue;
709
- const style = styleOf(span, fonts, vertical);
710
- const graphemes = graphemesOf(span.text);
711
- const spanFirst = units.length;
712
- let run = [];
713
- let runAt = 0;
714
- let runLink;
715
- let space = '';
716
- let spaceAt = 0;
717
- // A lone mark that may pair with the next one (—, …), by unit index.
718
- let pairOpen = -1;
719
- let at = 0;
720
- // The link a character at `offset` of the span is part of: its span and
721
- // range, so two links to one target stay apart.
722
- const links = span.links;
723
- const linkAt = (offset) => {
724
- if (!links || links.length === 0)
725
- return undefined;
726
- for (let li = 0; li < links.length; li++) {
727
- const l = links[li];
728
- if (offset >= l.start && offset < l.end)
729
- return `${si}:${li}`;
730
- }
731
- return undefined;
732
- };
733
- const push = (u) => {
734
- units.push({
735
- ...u,
736
- style,
737
- ...(zwsp ? { zwspBefore: true } : {}),
738
- ...(style.script && units.length === spanFirst ? { glueBefore: true } : {}),
739
- ...glue(),
740
- });
741
- zwsp = false;
742
- };
743
- const flushRun = () => {
744
- if (run.length === 0)
745
- return;
746
- const text = run.join('');
747
- const firstG = run[0];
748
- const lastG = run[run.length - 1];
749
- // Vertical text: a short number at either end stands in one upright
750
- // cell (`cjk.uprightDigits`), and the run is Chinese on that side;
751
- // tracking follows the cell once.
752
- const cells = vertical ? numberCellEdges(run) : undefined;
753
- push({
754
- kind: 'text',
755
- text,
756
- width: textWidth(text, style.font, style.smallCaps) + track(vertical && letterSpacingPx !== 0 ? verticalTrackCount(text) : run.length),
757
- graphemes: run.length,
758
- first: cells?.start ? 'ideograph' : cjkClassOf(firstG),
759
- last: cells?.end ? 'ideograph' : cjkClassOf(lastG),
760
- firstCjk: cells?.start === true,
761
- lastCjk: cells?.end === true,
762
- at: runAt,
763
- run: true,
764
- ...(cells?.start ? { cellStart: true } : {}),
765
- ...(cells?.end ? { cellEnd: true } : {}),
766
- ...(URL_LIKE_RE.test(text) ? { url: true } : {}),
767
- ...(runLink !== undefined ? { link: runLink } : {}),
768
- });
769
- run = [];
770
- pairOpen = -1;
771
- };
772
- const flushSpace = () => {
773
- if (space === '')
774
- return;
775
- units.push({
776
- kind: 'space',
777
- text: space,
778
- width: measureTextWidth(space, style.font) + track(graphemeCount(space)),
779
- graphemes: graphemeCount(space),
780
- first: 'western',
781
- last: 'western',
782
- firstCjk: false,
783
- lastCjk: false,
784
- style,
785
- at: spaceAt,
786
- });
787
- space = '';
788
- pairOpen = -1;
789
- };
790
- for (let i = 0; i < graphemes.length; i++) {
791
- const g = graphemes[i];
792
- const gAt = at;
793
- at += g.length;
794
- if (isBreakingSpace(g[0])) {
795
- flushRun();
796
- wj = false;
797
- if (space === '')
798
- spaceAt = gAt;
799
- space += g;
716
+ if (atomics.length > 0)
800
717
  continue;
801
- }
802
- flushSpace();
803
- if (g === '\u200B') {
804
- flushRun();
805
- zwsp = true;
806
- pairOpen = -1;
718
+ if (span.text.length === 0)
807
719
  continue;
808
- }
809
- if (g === '\u2060') {
810
- flushRun();
811
- wj = true;
720
+ const style = styleOf(span, fonts, vertical);
721
+ const graphemes = graphemesOf(span.text);
722
+ const spanFirst = units.length;
723
+ let run = [];
724
+ let runAt = 0;
725
+ let runLink;
726
+ let space = '';
727
+ let spaceAt = 0;
728
+ // A lone mark that may pair with the next one (—, …), by unit index.
729
+ let pairOpen = -1;
730
+ let at = 0;
731
+ // The link a character at `offset` of the span is part of: its span and
732
+ // range, so two links to one target stay apart.
733
+ const links = span.links;
734
+ const linkAt = (offset) => {
735
+ if (!links || links.length === 0)
736
+ return undefined;
737
+ for (let li = 0; li < links.length; li++) {
738
+ const l = links[li];
739
+ if (offset >= l.start && offset < l.end)
740
+ return `${si}:${li}`;
741
+ }
742
+ return undefined;
743
+ };
744
+ const push = (u) => {
745
+ units.push({
746
+ ...u,
747
+ style,
748
+ ...(zwsp ? { zwspBefore: true } : {}),
749
+ ...(style.script && units.length === spanFirst ? { glueBefore: true } : {}),
750
+ ...glue(),
751
+ });
752
+ zwsp = false;
753
+ };
754
+ const flushRun = () => {
755
+ if (run.length === 0)
756
+ return;
757
+ const text = run.join('');
758
+ const firstG = run[0];
759
+ const lastG = run[run.length - 1];
760
+ // Vertical text: a short number at either end stands in one upright
761
+ // cell (`cjk.uprightDigits`), and the run is Chinese on that side;
762
+ // tracking follows the cell once.
763
+ const cells = vertical ? numberCellEdges(run) : undefined;
764
+ push({
765
+ kind: 'text',
766
+ text,
767
+ width: textWidth(text, style.font, style.smallCaps) + track(vertical && letterSpacingPx !== 0 ? verticalTrackCount(text) : run.length),
768
+ graphemes: run.length,
769
+ first: cells?.start ? 'ideograph' : cjkClassOf(firstG),
770
+ last: cells?.end ? 'ideograph' : cjkClassOf(lastG),
771
+ firstCjk: cells?.start === true,
772
+ lastCjk: cells?.end === true,
773
+ at: runAt,
774
+ run: true,
775
+ ...(cells?.start ? { cellStart: true } : {}),
776
+ ...(cells?.end ? { cellEnd: true } : {}),
777
+ ...(URL_LIKE_RE.test(text) ? { url: true } : {}),
778
+ ...(runLink !== undefined ? { link: runLink } : {}),
779
+ });
780
+ run = [];
812
781
  pairOpen = -1;
813
- continue;
814
- }
815
- // A soft hyphen: the composer breaks Western words only when they are
816
- // wider than the line, so it is left out, and the word stays whole.
817
- if (g === '\u00AD')
818
- continue;
819
- const link = linkAt(gAt);
820
- if (!isCjkHere(g, graphemes[i - 1], graphemes[i + 1])) {
821
- // A link that starts or ends inside a run cuts it: the two parts
822
- // still never part (neither is CJK) and take no space between them.
823
- if (run.length > 0 && link !== runLink)
782
+ };
783
+ const flushSpace = () => {
784
+ if (space === '')
785
+ return;
786
+ units.push({
787
+ kind: 'space',
788
+ text: space,
789
+ width: measureTextWidth(space, style.font) + track(graphemeCount(space)),
790
+ graphemes: graphemeCount(space),
791
+ first: 'western',
792
+ last: 'western',
793
+ firstCjk: false,
794
+ lastCjk: false,
795
+ style,
796
+ at: spaceAt,
797
+ });
798
+ space = '';
799
+ pairOpen = -1;
800
+ };
801
+ for (let i = 0; i < graphemes.length; i++) {
802
+ const g = graphemes[i];
803
+ const gAt = at;
804
+ at += g.length;
805
+ if (isBreakingSpace(g[0])) {
824
806
  flushRun();
825
- if (run.length === 0) {
826
- runAt = gAt;
827
- runLink = link;
807
+ wj = false;
808
+ if (space === '')
809
+ spaceAt = gAt;
810
+ space += g;
811
+ continue;
828
812
  }
829
- run.push(g);
830
- continue;
813
+ flushSpace();
814
+ if (g === '\u200B') {
815
+ flushRun();
816
+ zwsp = true;
817
+ pairOpen = -1;
818
+ continue;
819
+ }
820
+ if (g === '\u2060') {
821
+ flushRun();
822
+ wj = true;
823
+ pairOpen = -1;
824
+ continue;
825
+ }
826
+ // A soft hyphen: the composer breaks Western words only when they are
827
+ // wider than the line, so it is left out, and the word stays whole.
828
+ if (g === '\u00AD')
829
+ continue;
830
+ const link = linkAt(gAt);
831
+ if (!isCjkHere(g, graphemes[i - 1], graphemes[i + 1])) {
832
+ // A link that starts or ends inside a run cuts it: the two parts
833
+ // still never part (neither is CJK) and take no space between them.
834
+ if (run.length > 0 && link !== runLink)
835
+ flushRun();
836
+ if (run.length === 0) {
837
+ runAt = gAt;
838
+ runLink = link;
839
+ }
840
+ run.push(g);
841
+ continue;
842
+ }
843
+ flushRun();
844
+ // —— and …… are one unit of two ems; a third mark opens another.
845
+ if (pairOpen >= 0 && units[pairOpen].text === g && !zwsp && units[pairOpen].link === link) {
846
+ const u = units[pairOpen];
847
+ u.text += g;
848
+ u.width += cellAdvance(g, style.font, vertical, u.first) + track(1);
849
+ u.graphemes = 2;
850
+ u.first = u.last = g === '\u2026' || g === '\u22EF' ? 'ellipsis' : 'dash';
851
+ pairOpen = -1;
852
+ continue;
853
+ }
854
+ let cls = cjkClassOf(g);
855
+ // A single em dash (or horizontal bar) joins two words as a connector
856
+ // (clreq §6.1.1): never at a line start.
857
+ if (g === '\u2014' || g === '\u2015')
858
+ cls = 'connector';
859
+ push({
860
+ kind: 'text',
861
+ text: g,
862
+ width: (style.smallCaps && !vertical ? textWidth(g, style.font, true) : cellAdvance(g, style.font, vertical, cls)) + track(1),
863
+ graphemes: 1,
864
+ first: cls,
865
+ last: cls,
866
+ firstCjk: true,
867
+ lastCjk: true,
868
+ at: gAt,
869
+ ...(link !== undefined ? { link } : {}),
870
+ });
871
+ pairOpen = pairable(g) ? units.length - 1 : -1;
831
872
  }
832
873
  flushRun();
833
- // —— and …… are one unit of two ems; a third mark opens another.
834
- if (pairOpen >= 0 && units[pairOpen].text === g && !zwsp && units[pairOpen].link === link) {
835
- const u = units[pairOpen];
836
- u.text += g;
837
- u.width += cellAdvance(g, style.font, vertical, u.first) + track(1);
838
- u.graphemes = 2;
839
- u.first = u.last = g === '\u2026' || g === '\u22EF' ? 'ellipsis' : 'dash';
840
- pairOpen = -1;
841
- continue;
842
- }
843
- let cls = cjkClassOf(g);
844
- // A single em dash (or horizontal bar) joins two words as a connector
845
- // (clreq §6.1.1): never at a line start.
846
- if (g === '\u2014' || g === '\u2015')
847
- cls = 'connector';
848
- push({
849
- kind: 'text',
850
- text: g,
851
- width: (style.smallCaps && !vertical ? textWidth(g, style.font, true) : cellAdvance(g, style.font, vertical, cls)) + track(1),
852
- graphemes: 1,
853
- first: cls,
854
- last: cls,
855
- firstCjk: true,
856
- lastCjk: true,
857
- at: gAt,
858
- ...(link !== undefined ? { link } : {}),
859
- });
860
- pairOpen = pairable(g) ? units.length - 1 : -1;
874
+ flushSpace();
875
+ if (units.length === spanFirst + 1 && units[spanFirst].kind === 'text')
876
+ wholeSpan.add(spanFirst);
877
+ }
878
+ finally {
879
+ if (ownLanguage !== undefined)
880
+ setMeasureLanguage(languageBefore);
861
881
  }
862
- flushRun();
863
- flushSpace();
864
- if (units.length === spanFirst + 1 && units[spanFirst].kind === 'text')
865
- wholeSpan.add(spanFirst);
866
882
  }
867
883
  if (spans.some((s) => s.kunten))
868
884
  attachKunten(spans, spanStarts, units);