postext 1.28.6 → 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
@@ -6,7 +6,8 @@ import { getHyphenationRulesKey, hyphenateByDictionary, hyphenateText, softHyphe
6
6
  import { richTokensToItems, computeBreakpoints, reconstructRichLines, } from '../knuthPlass';
7
7
  import { SOFT_HYPHEN } from './types';
8
8
  import { lineStartAt, lineWidthAt, lineWidthFunction, markInsetLines, uniformMeasureFrom } from './types';
9
- import { cleanSoftHyphens, measureTextWidth, normalSpaceWidthFor } from './canvas';
9
+ import { cleanSoftHyphens, getMeasureLanguage, measureTextWidth, normalSpaceWidthFor, setMeasureLanguage, withMeasureLanguage } from './canvas';
10
+ import { spanShapingLanguage } from './spanLanguage';
10
11
  import { isRuntLastLine } from './runts';
11
12
  import { hasOverfullLine } from './plain';
12
13
  import { justifiedSpaceRatios } from './spaceRatios';
@@ -127,6 +128,8 @@ function scriptOf(t) {
127
128
  // The pieces of a word with no letter are shaped in its script (#725).
128
129
  ...(t.scriptContext ? { scriptContext: t.scriptContext } : {}),
129
130
  ...(t.scriptRtl ? { scriptRtl: true } : {}),
131
+ // And the pieces of a word of a run in another language in it (#764).
132
+ ...(t.lang !== undefined ? { lang: t.lang } : {}),
130
133
  };
131
134
  }
132
135
  /** The script fields of the piece `[from, to)` of `token`, a head or a
@@ -154,6 +157,12 @@ function rtlWordWidth(text, font) {
154
157
  /** How a token's text is measured: in its script when it is a word with
155
158
  * no letter given one (#725), else as any text (`textWidth`). */
156
159
  function tokenTextWidth(token, font) {
160
+ // A word of a run in another language is measured in it (#764).
161
+ if (token.lang !== undefined) {
162
+ const { lang, ...rest } = token;
163
+ const inner = tokenTextWidth(rest, font);
164
+ return (text) => withMeasureLanguage(lang, () => inner(text));
165
+ }
157
166
  const script = token.scriptContext;
158
167
  if (script)
159
168
  return (text) => (text === '' ? 0 : scriptTextWidth(text, font, script, token.scriptRtl === true));
@@ -1140,147 +1149,165 @@ keepAll = false) {
1140
1149
  };
1141
1150
  };
1142
1151
  for (const span of spans) {
1143
- const spanBase = spanStart;
1144
- spanStart += span.text.length;
1145
- // A tab (#622): its width is set where its line gets to it.
1146
- // Tab characters in a row are one character of the text that goes on
1147
- // that many stops: the later ones have no text.
1148
- if (span.tab) {
1149
- const tab = span.tab.px ? { stop: span.tab.px } : {};
1150
- for (let k = 0; k < Math.max(1, span.tab.count ?? 1); k++) {
1151
- tokens.push({ text: k === 0 ? span.text : '', bold: span.bold, italic: span.italic, kind: 'space', width: 0, tab });
1152
+ // A run that names its language (`:ltr[…]{lang=en}`) is measured in it,
1153
+ // in a document shaped in its own (#764); its tokens say so, for what
1154
+ // is measured of them later (a cut, a piece).
1155
+ const ownLanguage = spanShapingLanguage(span);
1156
+ const spanTokensFrom = tokens.length;
1157
+ const languageBefore = getMeasureLanguage();
1158
+ if (ownLanguage !== undefined)
1159
+ setMeasureLanguage(ownLanguage);
1160
+ try {
1161
+ const spanBase = spanStart;
1162
+ spanStart += span.text.length;
1163
+ // A tab (#622): its width is set where its line gets to it.
1164
+ // Tab characters in a row are one character of the text that goes on
1165
+ // that many stops: the later ones have no text.
1166
+ if (span.tab) {
1167
+ const tab = span.tab.px ? { stop: span.tab.px } : {};
1168
+ for (let k = 0; k < Math.max(1, span.tab.count ?? 1); k++) {
1169
+ tokens.push({ text: k === 0 ? span.text : '', bold: span.bold, italic: span.italic, kind: 'space', width: 0, tab });
1170
+ }
1171
+ continue;
1152
1172
  }
1153
- continue;
1154
- }
1155
- // A reference whose label is words: one token per word and space (#702).
1156
- const label = refLabelTokens(span, normalFont, boldFont, italicFont, boldItalicFont, letterSpacingPx);
1157
- if (label) {
1158
- tokens.push(...label);
1159
- continue;
1160
- }
1161
- const atomic = atomicSpanToken(span, normalFont, boldFont, italicFont, boldItalicFont, letterSpacingPx);
1162
- if (atomic) {
1163
- tokens.push(atomic);
1164
- continue;
1165
- }
1166
- const font = spanFont(span);
1167
- // A fixed space is set as text: no break, no justification glue.
1168
- if (span.fixedSpace) {
1169
- if (span.text.length > 0) {
1170
- tokens.push({ text: span.text, bold: span.bold, italic: span.italic, kind: 'text', width: textWidth(span.text, font, false) + track(span.text) });
1173
+ // A reference whose label is words: one token per word and space (#702).
1174
+ const label = refLabelTokens(span, normalFont, boldFont, italicFont, boldItalicFont, letterSpacingPx);
1175
+ if (label) {
1176
+ tokens.push(...label);
1177
+ continue;
1171
1178
  }
1172
- continue;
1173
- }
1174
- // Chinese marks (#193) ride on the span's words like its script.
1175
- const marks = spanMarks(span, getMeasureWritingMode() === 'vertical-rl');
1176
- const scriptFields = {
1177
- ...scriptFieldsOf(span),
1178
- ...(marks ? { cjkMarks: marks } : {}),
1179
- ...(span.inserted ? { inserted: true } : {}),
1180
- };
1181
- // Small capitals: the words keep their case (hyphenation reads it) and
1182
- // measure as they will be painted.
1183
- const sc = !!span.smallCaps;
1184
- const scFields = sc ? { smallCaps: true } : {};
1185
- // Split on word boundaries while preserving spaces. Each word is
1186
- // hyphenated on its own (the dictionary works word by word anyway), so
1187
- // its soft hyphens can be told apart from the ones the source carries.
1188
- // A no-break space is no boundary: it stays inside the word it glues
1189
- // ("37 °C"), which the line then never breaks at (EF-66).
1190
- const matched = span.text.match(WORDS_AND_SPACES_RE);
1191
- if (!matched)
1192
- continue;
1193
- const levelled = bidi ? cutAtLevels(matched, spanBase, bidi) : matched;
1194
- // In a Chinese or Japanese document a word with no letter is cut
1195
- // around its doubled ellipses and dashes: the document's own marks,
1196
- // set as Han text whatever sentence cites them (`cutDoubledMarks`).
1197
- const parts = contextOf && scriptState.hanMarks && faceReadsScript(font)
1198
- ? levelled.flatMap((part) => (isBreakingSpaceRun(part) ? undefined : cutDoubledMarks(part)) ?? [part])
1199
- : levelled;
1200
- let partStart = spanBase;
1201
- for (const part of parts) {
1202
- const partAt = partStart;
1203
- partStart += part.length;
1204
- const isSpace = isBreakingSpaceRun(part);
1205
- if (!isSpace && URL_LIKE_RE.test(part.replace(/\u00AD/g, '')) && !(letterSpacingPx !== 0 && joiningScriptIn(part))) {
1206
- const clean = part.replace(/\u00AD/g, '');
1207
- // An address is one script run: its pieces with no letter
1208
- // (`10.1006/`, `.2001`) are measured in the script they have in
1209
- // it, and an address with no letter at all in its context's (#725).
1210
- const inScript = scriptMeasure(contextOf?.(clean, partAt, part.length, font), font, sc);
1211
- const spacing = inScript.untracked ? 0 : letterSpacingPx;
1212
- const upTo = inScript.upTo ?? ((text) => textWidth(text, font, sc));
1213
- const breakPoints = urlBreakIndices(clean).map((charIndex) => ({
1214
- charIndex,
1215
- widthBefore: upTo(clean.slice(0, charIndex)) + (spacing === 0 ? 0 : spacing * graphemeCount(clean.slice(0, charIndex))),
1216
- }));
1217
- tokens.push({
1218
- text: clean,
1219
- bold: span.bold,
1220
- italic: span.italic,
1221
- captionLabel: span.captionLabel,
1222
- ...scriptFields,
1223
- ...scFields,
1224
- ...inScript.fields,
1225
- kind: 'text',
1226
- width: (inScript.whole ? inScript.whole(clean) : textWidth(clean, font, sc)) + (inScript.untracked ? 0 : track(clean)),
1227
- ...(breakPoints.length > 0 ? { breakPoints, hyphenWidth: 0, bareBreaks: true } : {}),
1228
- });
1179
+ const atomic = atomicSpanToken(span, normalFont, boldFont, italicFont, boldItalicFont, letterSpacingPx);
1180
+ if (atomic) {
1181
+ tokens.push(atomic);
1229
1182
  continue;
1230
1183
  }
1231
- const { clean, soft } = isSpace ? { clean: part, soft: [] } : syllabify(part, shouldHyphenate && inCompound?.[partAt] !== true);
1232
- // The characters this word touches, for a dash that opens it
1233
- // ("**riddles.**—I", "*\"no\"*—and"): the end of a word run just
1234
- // before, no space in between.
1235
- const before = tokens[tokens.length - 1];
1236
- const touching = dashBreaks && before && before.kind === 'text' && !before.mathRender && !before.swatch && !before.chip
1237
- ? before.text.slice(-2)
1238
- : undefined;
1239
- // A word that holds CJK characters: measured unit by unit, once each.
1240
- const cjk = cjkText && !isSpace && hasCJK(clean) ? cjkWordBreaks(clean, font, sc, letterSpacingPx, undefined, keepAll) : undefined;
1241
- // A word of a joining script is measured untracked, its break points
1242
- // (after a hyphen or a dash it carries) too.
1243
- // A word with no letter: measured in the script of its context, its
1244
- // pieces at a break too (#725). In the Arabic script it takes no
1245
- // tracking, as the Arabic words around it take none.
1246
- // A word with a letter: its pieces with no letter (`COVID-` | `19`)
1247
- // in the script they have inside it.
1248
- const measured = scriptMeasure(contextOf && !isSpace && !cjk ? contextOf(clean, partAt, part.length, font) : undefined, font, sc);
1249
- const untracked = measured.untracked;
1250
- const wordSpacing = isSpace ? letterSpacingPx : untracked ? 0 : wordLetterSpacing(clean, letterSpacingPx);
1251
- const inScript = measured.whole;
1252
- const contextFields = measured.fields;
1253
- const breakPoints = isSpace ? [] : wordBreakPoints(clean, soft, font, wordSpacing, sc, dashBreaks, touching, cjk, measured.upTo);
1254
- if (breakPoints.length > 0) {
1255
- tokens.push({
1256
- text: clean,
1257
- bold: span.bold,
1258
- italic: span.italic,
1259
- captionLabel: span.captionLabel,
1260
- ...scriptFields,
1261
- ...scFields,
1262
- ...contextFields,
1263
- kind: 'text',
1264
- width: cjk ? cjk.width : (inScript ? inScript(clean) : textWidth(clean, font, sc)) + (untracked ? 0 : track(clean)),
1265
- breakPoints,
1266
- hyphenWidth: measureTextWidth('-', font) + letterSpacingPx,
1267
- });
1184
+ const font = spanFont(span);
1185
+ // A fixed space is set as text: no break, no justification glue.
1186
+ if (span.fixedSpace) {
1187
+ if (span.text.length > 0) {
1188
+ tokens.push({ text: span.text, bold: span.bold, italic: span.italic, kind: 'text', width: textWidth(span.text, font, false) + track(span.text) });
1189
+ }
1190
+ continue;
1191
+ }
1192
+ // Chinese marks (#193) ride on the span's words like its script.
1193
+ const marks = spanMarks(span, getMeasureWritingMode() === 'vertical-rl');
1194
+ const scriptFields = {
1195
+ ...scriptFieldsOf(span),
1196
+ ...(marks ? { cjkMarks: marks } : {}),
1197
+ ...(span.inserted ? { inserted: true } : {}),
1198
+ };
1199
+ // Small capitals: the words keep their case (hyphenation reads it) and
1200
+ // measure as they will be painted.
1201
+ const sc = !!span.smallCaps;
1202
+ const scFields = sc ? { smallCaps: true } : {};
1203
+ // Split on word boundaries while preserving spaces. Each word is
1204
+ // hyphenated on its own (the dictionary works word by word anyway), so
1205
+ // its soft hyphens can be told apart from the ones the source carries.
1206
+ // A no-break space is no boundary: it stays inside the word it glues
1207
+ // ("37 °C"), which the line then never breaks at (EF-66).
1208
+ const matched = span.text.match(WORDS_AND_SPACES_RE);
1209
+ if (!matched)
1210
+ continue;
1211
+ const levelled = bidi ? cutAtLevels(matched, spanBase, bidi) : matched;
1212
+ // In a Chinese or Japanese document a word with no letter is cut
1213
+ // around its doubled ellipses and dashes: the document's own marks,
1214
+ // set as Han text whatever sentence cites them (`cutDoubledMarks`).
1215
+ const parts = contextOf && scriptState.hanMarks && faceReadsScript(font)
1216
+ ? levelled.flatMap((part) => (isBreakingSpaceRun(part) ? undefined : cutDoubledMarks(part)) ?? [part])
1217
+ : levelled;
1218
+ let partStart = spanBase;
1219
+ for (const part of parts) {
1220
+ const partAt = partStart;
1221
+ partStart += part.length;
1222
+ const isSpace = isBreakingSpaceRun(part);
1223
+ if (!isSpace && URL_LIKE_RE.test(part.replace(/\u00AD/g, '')) && !(letterSpacingPx !== 0 && joiningScriptIn(part))) {
1224
+ const clean = part.replace(/\u00AD/g, '');
1225
+ // An address is one script run: its pieces with no letter
1226
+ // (`10.1006/`, `.2001`) are measured in the script they have in
1227
+ // it, and an address with no letter at all in its context's (#725).
1228
+ const inScript = scriptMeasure(contextOf?.(clean, partAt, part.length, font), font, sc);
1229
+ const spacing = inScript.untracked ? 0 : letterSpacingPx;
1230
+ const upTo = inScript.upTo ?? ((text) => textWidth(text, font, sc));
1231
+ const breakPoints = urlBreakIndices(clean).map((charIndex) => ({
1232
+ charIndex,
1233
+ widthBefore: upTo(clean.slice(0, charIndex)) + (spacing === 0 ? 0 : spacing * graphemeCount(clean.slice(0, charIndex))),
1234
+ }));
1235
+ tokens.push({
1236
+ text: clean,
1237
+ bold: span.bold,
1238
+ italic: span.italic,
1239
+ captionLabel: span.captionLabel,
1240
+ ...scriptFields,
1241
+ ...scFields,
1242
+ ...inScript.fields,
1243
+ kind: 'text',
1244
+ width: (inScript.whole ? inScript.whole(clean) : textWidth(clean, font, sc)) + (inScript.untracked ? 0 : track(clean)),
1245
+ ...(breakPoints.length > 0 ? { breakPoints, hyphenWidth: 0, bareBreaks: true } : {}),
1246
+ });
1247
+ continue;
1248
+ }
1249
+ const { clean, soft } = isSpace ? { clean: part, soft: [] } : syllabify(part, shouldHyphenate && inCompound?.[partAt] !== true);
1250
+ // The characters this word touches, for a dash that opens it
1251
+ // ("**riddles.**—I", "*\"no\"*—and"): the end of a word run just
1252
+ // before, no space in between.
1253
+ const before = tokens[tokens.length - 1];
1254
+ const touching = dashBreaks && before && before.kind === 'text' && !before.mathRender && !before.swatch && !before.chip
1255
+ ? before.text.slice(-2)
1256
+ : undefined;
1257
+ // A word that holds CJK characters: measured unit by unit, once each.
1258
+ const cjk = cjkText && !isSpace && hasCJK(clean) ? cjkWordBreaks(clean, font, sc, letterSpacingPx, undefined, keepAll) : undefined;
1259
+ // A word of a joining script is measured untracked, its break points
1260
+ // (after a hyphen or a dash it carries) too.
1261
+ // A word with no letter: measured in the script of its context, its
1262
+ // pieces at a break too (#725). In the Arabic script it takes no
1263
+ // tracking, as the Arabic words around it take none.
1264
+ // A word with a letter: its pieces with no letter (`COVID-` | `19`)
1265
+ // in the script they have inside it.
1266
+ const measured = scriptMeasure(contextOf && !isSpace && !cjk ? contextOf(clean, partAt, part.length, font) : undefined, font, sc);
1267
+ const untracked = measured.untracked;
1268
+ const wordSpacing = isSpace ? letterSpacingPx : untracked ? 0 : wordLetterSpacing(clean, letterSpacingPx);
1269
+ const inScript = measured.whole;
1270
+ const contextFields = measured.fields;
1271
+ const breakPoints = isSpace ? [] : wordBreakPoints(clean, soft, font, wordSpacing, sc, dashBreaks, touching, cjk, measured.upTo);
1272
+ if (breakPoints.length > 0) {
1273
+ tokens.push({
1274
+ text: clean,
1275
+ bold: span.bold,
1276
+ italic: span.italic,
1277
+ captionLabel: span.captionLabel,
1278
+ ...scriptFields,
1279
+ ...scFields,
1280
+ ...contextFields,
1281
+ kind: 'text',
1282
+ width: cjk ? cjk.width : (inScript ? inScript(clean) : textWidth(clean, font, sc)) + (untracked ? 0 : track(clean)),
1283
+ breakPoints,
1284
+ hyphenWidth: measureTextWidth('-', font) + letterSpacingPx,
1285
+ });
1286
+ }
1287
+ else {
1288
+ // A word of a joining script loses the soft hyphens it may not
1289
+ // break at (`syllabify`); any other keeps its text as written.
1290
+ const word = clean !== part && !isSpace && joiningScriptIn(part) ? clean : part;
1291
+ tokens.push({
1292
+ text: word,
1293
+ bold: span.bold,
1294
+ italic: span.italic,
1295
+ captionLabel: span.captionLabel,
1296
+ ...scriptFields,
1297
+ ...(isSpace ? {} : scFields),
1298
+ ...contextFields,
1299
+ kind: isSpace ? 'space' : 'text',
1300
+ width: (inScript ? inScript(word) : textWidth(word, font, sc && !isSpace)) + (untracked ? 0 : track(word)),
1301
+ });
1302
+ }
1268
1303
  }
1269
- else {
1270
- // A word of a joining script loses the soft hyphens it may not
1271
- // break at (`syllabify`); any other keeps its text as written.
1272
- const word = clean !== part && !isSpace && joiningScriptIn(part) ? clean : part;
1273
- tokens.push({
1274
- text: word,
1275
- bold: span.bold,
1276
- italic: span.italic,
1277
- captionLabel: span.captionLabel,
1278
- ...scriptFields,
1279
- ...(isSpace ? {} : scFields),
1280
- ...contextFields,
1281
- kind: isSpace ? 'space' : 'text',
1282
- width: (inScript ? inScript(word) : textWidth(word, font, sc && !isSpace)) + (untracked ? 0 : track(word)),
1283
- });
1304
+ }
1305
+ finally {
1306
+ if (ownLanguage !== undefined) {
1307
+ setMeasureLanguage(languageBefore);
1308
+ if (ownLanguage !== languageBefore)
1309
+ for (let k = spanTokensFrom; k < tokens.length; k++)
1310
+ tokens[k].lang = ownLanguage;
1284
1311
  }
1285
1312
  }
1286
1313
  }