postext 1.29.2 → 1.30.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 (275) hide show
  1. package/dist/__tests__/arabicLocale.test.js +5 -5
  2. package/dist/__tests__/arabicLocale.test.js.map +1 -1
  3. package/dist/__tests__/bundle/balloonPlacementPin.test.js +5 -4
  4. package/dist/__tests__/bundle/balloonPlacementPin.test.js.map +1 -1
  5. package/dist/__tests__/bundle/cjkRulesPin.test.js +8 -6
  6. package/dist/__tests__/bundle/cjkRulesPin.test.js.map +1 -1
  7. package/dist/__tests__/bundle/designOverflowPin.test.js +1 -1
  8. package/dist/__tests__/bundle/flowColumnsPin.test.js +4 -3
  9. package/dist/__tests__/bundle/flowColumnsPin.test.js.map +1 -1
  10. package/dist/__tests__/bundle/gridBalancingPin.test.js +5 -4
  11. package/dist/__tests__/bundle/gridBalancingPin.test.js.map +1 -1
  12. package/dist/__tests__/bundle/gridBreakRulesPin.test.js +14 -10
  13. package/dist/__tests__/bundle/gridBreakRulesPin.test.js.map +1 -1
  14. package/dist/__tests__/bundle/indexGroupSplitPin.test.js +4 -3
  15. package/dist/__tests__/bundle/indexGroupSplitPin.test.js.map +1 -1
  16. package/dist/__tests__/bundle/looseOrderPin.test.js +5 -4
  17. package/dist/__tests__/bundle/looseOrderPin.test.js.map +1 -1
  18. package/dist/__tests__/bundle/refLabelBreaksPin.test.js +5 -4
  19. package/dist/__tests__/bundle/refLabelBreaksPin.test.js.map +1 -1
  20. package/dist/__tests__/bundle/scriptContextPin.test.js +6 -5
  21. package/dist/__tests__/bundle/scriptContextPin.test.js.map +1 -1
  22. package/dist/__tests__/bundle/shapingLanguagePin.test.js +142 -46
  23. package/dist/__tests__/bundle/shapingLanguagePin.test.js.map +1 -1
  24. package/dist/__tests__/bundle/taskBoxPin.test.js +1 -1
  25. package/dist/__tests__/bundle/trailingStepPin.test.js +4 -3
  26. package/dist/__tests__/bundle/trailingStepPin.test.js.map +1 -1
  27. package/dist/__tests__/bundle/wideFloatStretchPin.test.js +4 -3
  28. package/dist/__tests__/bundle/wideFloatStretchPin.test.js.map +1 -1
  29. package/dist/__tests__/bundle.test.js +1 -1
  30. package/dist/__tests__/cellText.test.d.ts +2 -0
  31. package/dist/__tests__/cellText.test.d.ts.map +1 -0
  32. package/dist/__tests__/cellText.test.js +281 -0
  33. package/dist/__tests__/cellText.test.js.map +1 -0
  34. package/dist/__tests__/chineseLocale.test.js +13 -11
  35. package/dist/__tests__/chineseLocale.test.js.map +1 -1
  36. package/dist/__tests__/cjk/japaneseHtml.test.js +3 -3
  37. package/dist/__tests__/cjk/japaneseHtml.test.js.map +1 -1
  38. package/dist/__tests__/cjk/japaneseRuby.test.js +1 -1
  39. package/dist/__tests__/cjk/japaneseRuby.test.js.map +1 -1
  40. package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js +3 -3
  41. package/dist/__tests__/cjk/japaneseSegmentLanguage.test.js.map +1 -1
  42. package/dist/__tests__/codeBlocks.test.js +1 -1
  43. package/dist/__tests__/contextText.test.d.ts +2 -0
  44. package/dist/__tests__/contextText.test.d.ts.map +1 -0
  45. package/dist/__tests__/contextText.test.js +371 -0
  46. package/dist/__tests__/contextText.test.js.map +1 -0
  47. package/dist/__tests__/design/designTextRender.test.js +8 -1
  48. package/dist/__tests__/design/designTextRender.test.js.map +1 -1
  49. package/dist/__tests__/exports.test.js +6 -0
  50. package/dist/__tests__/exports.test.js.map +1 -1
  51. package/dist/__tests__/fontUsage.test.js +40 -0
  52. package/dist/__tests__/fontUsage.test.js.map +1 -1
  53. package/dist/__tests__/hardBreaks.test.js +1 -1
  54. package/dist/__tests__/headingInlineMarks.test.js +1 -1
  55. package/dist/__tests__/html-backend.test.js +6 -2
  56. package/dist/__tests__/html-backend.test.js.map +1 -1
  57. package/dist/__tests__/japaneseNotes.test.js +4 -2
  58. package/dist/__tests__/japaneseNotes.test.js.map +1 -1
  59. package/dist/__tests__/lineEndPieces.test.d.ts +2 -0
  60. package/dist/__tests__/lineEndPieces.test.d.ts.map +1 -0
  61. package/dist/__tests__/lineEndPieces.test.js +374 -0
  62. package/dist/__tests__/lineEndPieces.test.js.map +1 -0
  63. package/dist/__tests__/mathSizeMigration.test.js +2 -2
  64. package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
  65. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
  66. package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
  67. package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
  68. package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
  69. package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
  70. package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
  71. package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
  72. package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
  73. package/dist/__tests__/plainGeminateHead.test.d.ts +2 -0
  74. package/dist/__tests__/plainGeminateHead.test.d.ts.map +1 -0
  75. package/dist/__tests__/plainGeminateHead.test.js +92 -0
  76. package/dist/__tests__/plainGeminateHead.test.js.map +1 -0
  77. package/dist/__tests__/presets/showcaseShapingLanguage.test.d.ts +2 -0
  78. package/dist/__tests__/presets/showcaseShapingLanguage.test.d.ts.map +1 -0
  79. package/dist/__tests__/presets/showcaseShapingLanguage.test.js +52 -0
  80. package/dist/__tests__/presets/showcaseShapingLanguage.test.js.map +1 -0
  81. package/dist/__tests__/reshapedTracking.test.d.ts +2 -0
  82. package/dist/__tests__/reshapedTracking.test.d.ts.map +1 -0
  83. package/dist/__tests__/reshapedTracking.test.js +358 -0
  84. package/dist/__tests__/reshapedTracking.test.js.map +1 -0
  85. package/dist/__tests__/rtl/designRunsPainted.test.d.ts +2 -0
  86. package/dist/__tests__/rtl/designRunsPainted.test.d.ts.map +1 -0
  87. package/dist/__tests__/rtl/designRunsPainted.test.js +184 -0
  88. package/dist/__tests__/rtl/designRunsPainted.test.js.map +1 -0
  89. package/dist/__tests__/rtl/designTextPaint.test.js +5 -1
  90. package/dist/__tests__/rtl/designTextPaint.test.js.map +1 -1
  91. package/dist/__tests__/rtl/htmlRtl.test.js +3 -3
  92. package/dist/__tests__/rtl/htmlRtl.test.js.map +1 -1
  93. package/dist/__tests__/rtl/listMarkerDirection.test.js +28 -28
  94. package/dist/__tests__/rtl/listMarkerDirection.test.js.map +1 -1
  95. package/dist/__tests__/rtl/tocNumberPainted.test.d.ts +2 -0
  96. package/dist/__tests__/rtl/tocNumberPainted.test.d.ts.map +1 -0
  97. package/dist/__tests__/rtl/tocNumberPainted.test.js +113 -0
  98. package/dist/__tests__/rtl/tocNumberPainted.test.js.map +1 -0
  99. package/dist/__tests__/scriptContext.test.js +141 -1
  100. package/dist/__tests__/scriptContext.test.js.map +1 -1
  101. package/dist/__tests__/shapingLanguage.test.js +54 -41
  102. package/dist/__tests__/shapingLanguage.test.js.map +1 -1
  103. package/dist/__tests__/trackedLetters.test.d.ts +2 -0
  104. package/dist/__tests__/trackedLetters.test.d.ts.map +1 -0
  105. package/dist/__tests__/trackedLetters.test.js +380 -0
  106. package/dist/__tests__/trackedLetters.test.js.map +1 -0
  107. package/dist/__tests__/trackedLigatures.test.js +17 -5
  108. package/dist/__tests__/trackedLigatures.test.js.map +1 -1
  109. package/dist/__tests__/verseLines.test.js +1 -1
  110. package/dist/__tests__/wordCacheOrder.test.d.ts +2 -0
  111. package/dist/__tests__/wordCacheOrder.test.d.ts.map +1 -0
  112. package/dist/__tests__/wordCacheOrder.test.js +352 -0
  113. package/dist/__tests__/wordCacheOrder.test.js.map +1 -0
  114. package/dist/__tests__/wordPieces.test.d.ts +2 -0
  115. package/dist/__tests__/wordPieces.test.d.ts.map +1 -0
  116. package/dist/__tests__/wordPieces.test.js +762 -0
  117. package/dist/__tests__/wordPieces.test.js.map +1 -0
  118. package/dist/bidi.d.ts +12 -0
  119. package/dist/bidi.d.ts.map +1 -1
  120. package/dist/bidi.js +23 -0
  121. package/dist/bidi.js.map +1 -1
  122. package/dist/bundle/configVersion.d.ts +49 -23
  123. package/dist/bundle/configVersion.d.ts.map +1 -1
  124. package/dist/bundle/configVersion.js +54 -25
  125. package/dist/bundle/configVersion.js.map +1 -1
  126. package/dist/canvas-backend/blockRender.d.ts.map +1 -1
  127. package/dist/canvas-backend/blockRender.js +58 -27
  128. package/dist/canvas-backend/blockRender.js.map +1 -1
  129. package/dist/canvas-backend/cellText.d.ts +12 -0
  130. package/dist/canvas-backend/cellText.d.ts.map +1 -0
  131. package/dist/canvas-backend/cellText.js +114 -0
  132. package/dist/canvas-backend/cellText.js.map +1 -0
  133. package/dist/canvas-backend/chip.d.ts.map +1 -1
  134. package/dist/canvas-backend/chip.js +2 -1
  135. package/dist/canvas-backend/chip.js.map +1 -1
  136. package/dist/canvas-backend/contextState.d.ts +7 -0
  137. package/dist/canvas-backend/contextState.d.ts.map +1 -1
  138. package/dist/canvas-backend/contextState.js +7 -0
  139. package/dist/canvas-backend/contextState.js.map +1 -1
  140. package/dist/canvas-backend/contextText.d.ts +76 -0
  141. package/dist/canvas-backend/contextText.d.ts.map +1 -0
  142. package/dist/canvas-backend/contextText.js +102 -0
  143. package/dist/canvas-backend/contextText.js.map +1 -0
  144. package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
  145. package/dist/canvas-backend/headerFooter.js +8 -1
  146. package/dist/canvas-backend/headerFooter.js.map +1 -1
  147. package/dist/canvas-backend/index.d.ts.map +1 -1
  148. package/dist/canvas-backend/index.js +19 -13
  149. package/dist/canvas-backend/index.js.map +1 -1
  150. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  151. package/dist/canvas-backend/renderResourceBlock.js +12 -6
  152. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  153. package/dist/canvas-backend/scriptText.d.ts.map +1 -1
  154. package/dist/canvas-backend/scriptText.js +3 -2
  155. package/dist/canvas-backend/scriptText.js.map +1 -1
  156. package/dist/canvas-backend/segmentText.d.ts.map +1 -1
  157. package/dist/canvas-backend/segmentText.js +8 -2
  158. package/dist/canvas-backend/segmentText.js.map +1 -1
  159. package/dist/canvas-backend/trackedText.d.ts +75 -1
  160. package/dist/canvas-backend/trackedText.d.ts.map +1 -1
  161. package/dist/canvas-backend/trackedText.js +185 -22
  162. package/dist/canvas-backend/trackedText.js.map +1 -1
  163. package/dist/canvas-backend/verticalText.d.ts +9 -0
  164. package/dist/canvas-backend/verticalText.d.ts.map +1 -1
  165. package/dist/canvas-backend/verticalText.js +29 -7
  166. package/dist/canvas-backend/verticalText.js.map +1 -1
  167. package/dist/canvas-backend/wordPieces.d.ts +31 -0
  168. package/dist/canvas-backend/wordPieces.d.ts.map +1 -0
  169. package/dist/canvas-backend/wordPieces.js +132 -0
  170. package/dist/canvas-backend/wordPieces.js.map +1 -0
  171. package/dist/canvas-backend/wordRuns.d.ts +3 -1
  172. package/dist/canvas-backend/wordRuns.d.ts.map +1 -1
  173. package/dist/canvas-backend/wordRuns.js +10 -6
  174. package/dist/canvas-backend/wordRuns.js.map +1 -1
  175. package/dist/comics/lettering/__tests__/fitAsPainted.test.d.ts +2 -0
  176. package/dist/comics/lettering/__tests__/fitAsPainted.test.d.ts.map +1 -0
  177. package/dist/comics/lettering/__tests__/fitAsPainted.test.js +97 -0
  178. package/dist/comics/lettering/__tests__/fitAsPainted.test.js.map +1 -0
  179. package/dist/comics/lettering/shape-text.d.ts.map +1 -1
  180. package/dist/comics/lettering/shape-text.js +39 -11
  181. package/dist/comics/lettering/shape-text.js.map +1 -1
  182. package/dist/defaults/bodyText.d.ts.map +1 -1
  183. package/dist/defaults/bodyText.js +6 -5
  184. package/dist/defaults/bodyText.js.map +1 -1
  185. package/dist/design/bidiText.d.ts +45 -6
  186. package/dist/design/bidiText.d.ts.map +1 -1
  187. package/dist/design/bidiText.js +78 -10
  188. package/dist/design/bidiText.js.map +1 -1
  189. package/dist/design/cjkText.d.ts +3 -0
  190. package/dist/design/cjkText.d.ts.map +1 -1
  191. package/dist/design/cjkText.js +1 -1
  192. package/dist/design/cjkText.js.map +1 -1
  193. package/dist/design/layout.d.ts.map +1 -1
  194. package/dist/design/layout.js +36 -5
  195. package/dist/design/layout.js.map +1 -1
  196. package/dist/design/richText.d.ts +9 -1
  197. package/dist/design/richText.d.ts.map +1 -1
  198. package/dist/design/richText.js +8 -2
  199. package/dist/design/richText.js.map +1 -1
  200. package/dist/fonts/usage.d.ts +2 -0
  201. package/dist/fonts/usage.d.ts.map +1 -1
  202. package/dist/fonts/usage.js +18 -0
  203. package/dist/fonts/usage.js.map +1 -1
  204. package/dist/html-backend.d.ts.map +1 -1
  205. package/dist/html-backend.js +664 -84
  206. package/dist/html-backend.js.map +1 -1
  207. package/dist/index.d.ts +3 -1
  208. package/dist/index.d.ts.map +1 -1
  209. package/dist/index.js +4 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/knuthPlass/breakpoints.d.ts.map +1 -1
  212. package/dist/knuthPlass/breakpoints.js +4 -2
  213. package/dist/knuthPlass/breakpoints.js.map +1 -1
  214. package/dist/knuthPlass/pretextAdapter.d.ts +6 -3
  215. package/dist/knuthPlass/pretextAdapter.d.ts.map +1 -1
  216. package/dist/knuthPlass/pretextAdapter.js +11 -3
  217. package/dist/knuthPlass/pretextAdapter.js.map +1 -1
  218. package/dist/knuthPlass/richAdapter.d.ts +13 -1
  219. package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
  220. package/dist/knuthPlass/richAdapter.js +53 -5
  221. package/dist/knuthPlass/richAdapter.js.map +1 -1
  222. package/dist/knuthPlass/tracking.d.ts.map +1 -1
  223. package/dist/knuthPlass/tracking.js +3 -2
  224. package/dist/knuthPlass/tracking.js.map +1 -1
  225. package/dist/knuthPlass/types.d.ts +6 -0
  226. package/dist/knuthPlass/types.d.ts.map +1 -1
  227. package/dist/locale.d.ts +21 -17
  228. package/dist/locale.d.ts.map +1 -1
  229. package/dist/locale.js +23 -19
  230. package/dist/locale.js.map +1 -1
  231. package/dist/measure/canvas.d.ts +21 -0
  232. package/dist/measure/canvas.d.ts.map +1 -1
  233. package/dist/measure/canvas.js +165 -85
  234. package/dist/measure/canvas.js.map +1 -1
  235. package/dist/measure/cjkCompose.d.ts.map +1 -1
  236. package/dist/measure/cjkCompose.js +31 -5
  237. package/dist/measure/cjkCompose.js.map +1 -1
  238. package/dist/measure/plain.d.ts.map +1 -1
  239. package/dist/measure/plain.js +31 -6
  240. package/dist/measure/plain.js.map +1 -1
  241. package/dist/measure/pretextLanguage.d.ts.map +1 -1
  242. package/dist/measure/pretextLanguage.js +49 -7
  243. package/dist/measure/pretextLanguage.js.map +1 -1
  244. package/dist/measure/rich.d.ts +18 -1
  245. package/dist/measure/rich.d.ts.map +1 -1
  246. package/dist/measure/rich.js +200 -29
  247. package/dist/measure/rich.js.map +1 -1
  248. package/dist/measure/script.d.ts +17 -1
  249. package/dist/measure/script.d.ts.map +1 -1
  250. package/dist/measure/script.js +17 -1
  251. package/dist/measure/script.js.map +1 -1
  252. package/dist/measure/trackedLigatures.d.ts +59 -0
  253. package/dist/measure/trackedLigatures.d.ts.map +1 -0
  254. package/dist/measure/trackedLigatures.js +144 -0
  255. package/dist/measure/trackedLigatures.js.map +1 -0
  256. package/dist/pipeline/build.d.ts.map +1 -1
  257. package/dist/pipeline/build.js +4 -3
  258. package/dist/pipeline/build.js.map +1 -1
  259. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  260. package/dist/pipeline/calloutLayout.js +26 -7
  261. package/dist/pipeline/calloutLayout.js.map +1 -1
  262. package/dist/pipeline/lists.d.ts +13 -4
  263. package/dist/pipeline/lists.d.ts.map +1 -1
  264. package/dist/pipeline/lists.js +49 -12
  265. package/dist/pipeline/lists.js.map +1 -1
  266. package/dist/pipeline/toc.d.ts.map +1 -1
  267. package/dist/pipeline/toc.js +33 -9
  268. package/dist/pipeline/toc.js.map +1 -1
  269. package/dist/types.d.ts +35 -26
  270. package/dist/types.d.ts.map +1 -1
  271. package/dist/wordPieces.d.ts +120 -0
  272. package/dist/wordPieces.d.ts.map +1 -0
  273. package/dist/wordPieces.js +319 -0
  274. package/dist/wordPieces.js.map +1 -0
  275. package/package.json +1 -1
@@ -13,7 +13,7 @@ import { hasOverfullLine } from './plain';
13
13
  import { justifiedSpaceRatios } from './spaceRatios';
14
14
  import { quoteFamily } from './font';
15
15
  import { trimChipLineEdges } from './chipEdges';
16
- import { directChipRuns } from '../design/bidiText';
16
+ import { directChipRuns, measuresRunsAsPainted, paintedRunWidth } from '../design/bidiText';
17
17
  import { cjkJoinBreaks, hasCJK } from './cjk';
18
18
  import { composeCjkParagraph, cjkWordBreaks, composesAsCjk, composesDashRow, spanMarks } from './cjkCompose';
19
19
  import { getCjkComposition } from './cjkPunctuation';
@@ -150,9 +150,11 @@ function pieceScriptFields(token, from, to) {
150
150
  /** The width of a word of a joining script (Arabic…), always painted
151
151
  * right to left: measured so too in a face that draws its punctuation by
152
152
  * script (#725, `rtlTextWidth`), so the bracket or the stop at its edge
153
- * takes the forms it is painted in; as any text otherwise. */
153
+ * takes the forms it is painted in, and in every face in a document
154
+ * shaped in its own language (#770, see `rtlTextWidth`); as any text
155
+ * otherwise. */
154
156
  function rtlWordWidth(text, font) {
155
- return getMeasureScriptContext().enabled && !measuringVertically() && faceReadsScript(font) ? rtlTextWidth(text, font) : textWidth(text, font, false);
157
+ return getMeasureScriptContext().enabled && !measuringVertically() && (faceReadsScript(font) || getMeasureLanguage() !== undefined) ? rtlTextWidth(text, font) : textWidth(text, font, false);
156
158
  }
157
159
  /** How a token's text is measured: in its script when it is a word with
158
160
  * no letter given one (#725), else as any text (`textWidth`). */
@@ -175,6 +177,103 @@ function tokenTextWidth(token, font) {
175
177
  return (text) => (isLetterless(text) ? scriptTextWidth(text, font, word, false) : textWidth(text, font, token.smallCaps));
176
178
  return (text) => textWidth(text, font, token.smallCaps);
177
179
  }
180
+ /** How many characters of the rest of a word are measured with what
181
+ * follows the break (see {@link ownRest}): past any kerned pair,
182
+ * ligature or contextual form a face draws across one cut. */
183
+ const REST_WINDOW = 32;
184
+ /**
185
+ * The rest of a word from the break at `from`, measured as the text a
186
+ * line opens with (#770): the function gives the width of the word's
187
+ * characters `[from, to)` set as a text of their own, `lead` (the hyphen
188
+ * a compound repeats at the line's start) before them, with the tracking
189
+ * the word was measured with.
190
+ *
191
+ * Up to postext 1.29 that rest was the whole word less what stayed on the
192
+ * line above, and its pieces what the whole word grew by. The browser
193
+ * measured those strings whole, so whatever the face draws across the
194
+ * break stayed in the rest's box, and nothing paints it there. Crimson
195
+ * Pro sets the dot of a Catalan `l·l` 0.586 em wide: `instal·lar` broken
196
+ * as `instal-` | `lar` left `lar` a box of 28.54 px at 20 px for a text
197
+ * painted 21.62 px wide. EB Garamond's `of-` | `fice` left 24.7 px for
198
+ * 25.9 (the `ffi` ligature undone by the break), and Fira Sans's `lar`
199
+ * 20.74 px for 24.36 (its `l·` ligature). Measured in Chrome 154.
200
+ *
201
+ * The pieces after the break are measured as that text grows, so a rest
202
+ * that breaks again is right too, and the pieces a line carries add up to
203
+ * the text painted. Only the first {@link REST_WINDOW} characters are
204
+ * measured from the break; a piece that ends past them is what the word
205
+ * grew by from there on, which no break before it changes. A word many
206
+ * lines long then costs what a line of it does.
207
+ *
208
+ * `token` is the word as it stands (itself the rest of an earlier break,
209
+ * or the whole word); a document that keeps the host's language never
210
+ * asks.
211
+ */
212
+ function ownRest(token, from, font, lead = '') {
213
+ const text = token.text;
214
+ const spacing = token.ownPieces ?? 0;
215
+ const tracked = (width, piece) => (spacing === 0 ? width : width + spacing * graphemeCount(piece));
216
+ const whole = tokenTextWidth(token, font);
217
+ const before = (idx) => (idx >= text.length ? token.width : tracked(whole(text.slice(0, idx)), text.slice(0, idx)));
218
+ const rest = tokenTextWidth({ ...scriptOf(token), ...pieceScriptFields(token, from, text.length) }, font);
219
+ const own = (piece) => tracked(rest(piece), piece);
220
+ let end = Math.min(text.length, from + REST_WINDOW);
221
+ // Not between the two halves of a character outside the BMP.
222
+ if (end < text.length && (text.charCodeAt(end) & 0xFC00) === 0xDC00)
223
+ end++;
224
+ let window;
225
+ let anchor;
226
+ return (to) => {
227
+ if (to <= end)
228
+ return own(lead + text.slice(from, to));
229
+ window ??= own(lead + text.slice(from, end));
230
+ anchor ??= before(end);
231
+ return window + before(to) - anchor;
232
+ };
233
+ }
234
+ /** The break points of `token` after `at`, for its rest measured as a
235
+ * text of its own (`width`, see {@link ownRest}): each as far into that
236
+ * text as it is, the dot of an `l·l` measured there too. */
237
+ function ownRestBreaks(token, at, width, shift = 0) {
238
+ return (token.breakPoints ?? [])
239
+ .filter((b) => b.charIndex > at)
240
+ .map((b) => {
241
+ const { restShift: _shift, ...rest } = b;
242
+ void _shift;
243
+ const widthBefore = width(b.charIndex);
244
+ return {
245
+ ...rest,
246
+ charIndex: b.charIndex - at + shift,
247
+ widthBefore,
248
+ ...(b.replacesWidth !== undefined ? { replacesWidth: widthBefore - width(b.charIndex - 1) } : {}),
249
+ };
250
+ });
251
+ }
252
+ /**
253
+ * Knuth–Plass in a document shaped in its own language (#770): give each
254
+ * break point of the words measured so ({@link RichToken.ownPieces}) what
255
+ * the rest of its word is wider, opening the next line as a text of its
256
+ * own, than the pieces after the break add up to
257
+ * (`RichBreakPoint.restShift`). `lead`: the hyphen a compound repeats at
258
+ * that line's start (`repeatHyphen`) is part of the text measured, and its
259
+ * own width, which the breaker counts apart, is taken off.
260
+ * One measure a break point, two for a rest longer than
261
+ * {@link REST_WINDOW} characters.
262
+ */
263
+ function markRestShifts(tokens, fontOf, repeatHyphen) {
264
+ for (const token of tokens) {
265
+ if (token.ownPieces === undefined || !token.breakPoints)
266
+ continue;
267
+ const font = fontOf(token);
268
+ for (const bp of token.breakPoints) {
269
+ if (bp.greedyOnly)
270
+ continue;
271
+ const repeated = repeatHyphen && bp.bare === true && !token.bareBreaks;
272
+ const own = ownRest(token, bp.charIndex, font, repeated ? '-' : '')(token.text.length);
273
+ bp.restShift = own - (repeated ? token.hyphenWidth ?? 0 : 0) - (token.width - bp.widthBefore);
274
+ }
275
+ }
276
+ }
178
277
  /** Small capitals are synthesised, the same way on every backend: a
179
278
  * lowercase letter is set as its capital at this fraction of the text
180
279
  * size, while capitals, digits and punctuation keep the full size — the
@@ -360,8 +459,12 @@ export function chipToken(span, normalFont, boldFont, italicFont, boldItalicFont
360
459
  }
361
460
  // Right-to-left runs cut where the direction changes, and their order
362
461
  // (`VDTChip.order`).
363
- const directed = directChipRuns(runs, (t, f) => measureTextWidth(t, f) + wordLetterSpacing(t, letterSpacingPx) * graphemeCount(t));
364
- const textWidth = runs.reduce((sum, r) => sum + r.width, 0);
462
+ // In a document shaped in its own language each is as wide as it is
463
+ // painted, in its own direction, and the chip as wide as its runs (#770,
464
+ // `measuresRunsAsPainted`).
465
+ const asPainted = measuresRunsAsPainted();
466
+ const directed = directChipRuns(runs, (t, f, rtl) => (asPainted ? paintedRunWidth(t, f, rtl) : measureTextWidth(t, f)) + wordLetterSpacing(t, letterSpacingPx) * graphemeCount(t), asPainted);
467
+ const textWidth = (asPainted ? directed.runs : runs).reduce((sum, r) => sum + r.width, 0);
365
468
  const paddingX = box?.paddingXPx ?? 0;
366
469
  const paddingY = box?.paddingYPx ?? 0;
367
470
  // A style may pad the top and the bottom apart (EF-172).
@@ -542,7 +645,14 @@ function syllabify(word, hyphenate, byDictionary = false) {
542
645
  function wordBreakPoints(clean, soft, font, letterSpacingPx, smallCaps, dashBreaks = false, touching, cjk,
543
646
  /** How the word's pieces are measured when not as any text: in the
544
647
  * script of a word with no letter (#725). */
545
- measure) {
648
+ measure,
649
+ /** A document shaped in its own language (#770): the head of a break
650
+ * inside an `l·l` is as wide as the text painted, `col` and its hyphen.
651
+ * Taking the dot's own width off `col·` is short of it where the face
652
+ * draws the two together: Lora ligates `l·`, and left `col-` 1.32 px
653
+ * narrower than painted at 20 px (36.14 px for 37.46); Fira Sans,
654
+ * 0.2 px. */
655
+ ownHeads = false) {
546
656
  const widthBefore = cjk
547
657
  ? cjk.widthBefore
548
658
  : (idx) => (measure ? measure(clean.slice(0, idx)) : textWidth(clean.slice(0, idx), font, smallCaps)) + (letterSpacingPx === 0 ? 0 : letterSpacingPx * graphemeCount(clean.slice(0, idx)));
@@ -573,7 +683,7 @@ measure) {
573
683
  }
574
684
  for (const s of soft) {
575
685
  const geminate = endsInsideGeminate(clean.slice(0, s.index))
576
- ? { replacesWidth: textWidth(GEMINATE_DOT, font, smallCaps) + letterSpacingPx }
686
+ ? { replacesWidth: ownHeads ? widthBefore(s.index) - widthBefore(s.index - 1) : textWidth(GEMINATE_DOT, font, smallCaps) + letterSpacingPx }
577
687
  : {};
578
688
  add(s.author ? { charIndex: s.index, author: true, ...geminate } : { charIndex: s.index, ...geminate });
579
689
  }
@@ -613,6 +723,11 @@ export function emergencySplit(token, font, letterSpacingPx, lineMaxWidth, sylla
613
723
  const measure = tokenTextWidth(token, font);
614
724
  const widthBefore = (idx) => measure(text.slice(0, idx)) + (spacing === 0 ? 0 : spacing * graphemeCount(text.slice(0, idx)));
615
725
  const flags = { bold: token.bold, italic: token.italic, captionLabel: token.captionLabel, ...scriptOf(token) };
726
+ // In a document shaped in its own language the rest is measured as the
727
+ // text that opens the next line (#770, `ownRest`), with the tracking
728
+ // this division measures the word with.
729
+ const own = getMeasureLanguage() !== undefined && !measuringVertically() && !hasCJK(text) ? { ...token, ownPieces: spacing } : undefined;
730
+ const restFrom = (at) => (own ? tailAfter(own, at, 0, font) : tailAfter(token, at, widthBefore(at)));
616
731
  // A group glued by no-break spaces too wide for any line: the space is
617
732
  // the least bad place to part it (EF-66). The line ends before it and the
618
733
  // next one opens after it.
@@ -624,7 +739,7 @@ export function emergencySplit(token, font, letterSpacingPx, lineMaxWidth, sylla
624
739
  continue;
625
740
  return {
626
741
  head: { ...flags, ...pieceScriptFields(token, 0, idx), text: text.slice(0, idx), kind: 'text', width: headWidth },
627
- tail: { ...tailAfter(token, idx + 1, widthBefore(idx + 1)), ...flags, ...pieceScriptFields(token, idx + 1, text.length) },
742
+ tail: { ...restFrom(idx + 1), ...flags, ...pieceScriptFields(token, idx + 1, text.length) },
628
743
  };
629
744
  }
630
745
  // No hyphen where the cut falls next to an ideograph, or right after a
@@ -698,27 +813,34 @@ export function emergencySplit(token, font, letterSpacingPx, lineMaxWidth, sylla
698
813
  head: geminate
699
814
  ? { ...flags, text: withLineEndHyphen(text.slice(0, at)), kind: 'text', width: widthBefore(at - 1) + markW(at) }
700
815
  : { ...flags, ...pieceScriptFields(token, 0, at), text: text.slice(0, at) + mark, kind: 'text', width: widthBefore(at) + markW(at) },
701
- tail: { ...tailAfter(token, at, widthBefore(at)), ...flags, ...pieceScriptFields(token, at, text.length) },
816
+ tail: { ...restFrom(at), ...flags, ...pieceScriptFields(token, at, text.length) },
702
817
  ...(afterHyphen(at) ? { hard: true } : {}),
703
818
  };
704
819
  }
705
820
  /** What is left of `token` after a cut at `at` (`cutWidth` px in): its text
706
821
  * and width from there, the break points after the cut moved with it, and
707
822
  * what they need (the hyphen's width; a web address's joints stay bare),
708
- * so the next line breaks the rest where it could break the whole. */
709
- function tailAfter(token, at, cutWidth) {
710
- const residual = (token.breakPoints ?? [])
711
- .filter((b) => b.charIndex > at)
712
- .map((b) => ({ ...b, charIndex: b.charIndex - at, widthBefore: b.widthBefore - cutWidth }));
823
+ * so the next line breaks the rest where it could break the whole.
824
+ * With `font`, for a word whose halves are measured as the texts painted
825
+ * (`RichToken.ownPieces`, #770): the rest and its break points are
826
+ * measured from the cut on ({@link ownRest}), and `cutWidth` is not read. */
827
+ function tailAfter(token, at, cutWidth, font) {
828
+ const own = font !== undefined && token.ownPieces !== undefined ? ownRest(token, at, font) : undefined;
829
+ const residual = own
830
+ ? ownRestBreaks(token, at, own)
831
+ : (token.breakPoints ?? [])
832
+ .filter((b) => b.charIndex > at)
833
+ .map((b) => ({ ...b, charIndex: b.charIndex - at, widthBefore: b.widthBefore - cutWidth }));
713
834
  return {
714
835
  text: token.text.slice(at),
715
836
  bold: token.bold,
716
837
  italic: token.italic,
717
838
  kind: 'text',
718
- width: token.width - cutWidth,
839
+ width: own ? own(token.text.length) : token.width - cutWidth,
719
840
  ...(residual.length > 0 ? { breakPoints: residual, hyphenWidth: token.hyphenWidth } : {}),
720
841
  ...(token.bareBreaks ? { bareBreaks: true } : {}),
721
842
  ...(token.dashJoin ? { dashJoin: true } : {}),
843
+ ...(own ? { ownPieces: token.ownPieces } : {}),
722
844
  };
723
845
  }
724
846
  /** For each character of the spans' joined text, whether it sits in a
@@ -1100,6 +1222,9 @@ keepAll = false) {
1100
1222
  // in a face that draws its punctuation by script; the paragraph's text
1101
1223
  // and isolates are read when the first such word turns up.
1102
1224
  const scriptState = getMeasureScriptContext();
1225
+ // A document shaped in its own language measures the two halves of a
1226
+ // word broken at a line end as the texts painted (#770).
1227
+ const ownPieces = getMeasureLanguage() !== undefined && !measuringVertically();
1103
1228
  let paragraphText;
1104
1229
  let isolates;
1105
1230
  // `script` undefined: a word with letters. Right to left, it is measured
@@ -1109,9 +1234,14 @@ keepAll = false) {
1109
1234
  const contextOf = !scriptState.enabled || measuringVertically()
1110
1235
  ? undefined
1111
1236
  : (word, at, length, font) => {
1112
- if (!faceReadsScript(font) || hasCJK(word))
1113
- return undefined;
1114
1237
  const rtl = bidi !== undefined && (bidi.levels[at] & 1) === 1;
1238
+ // A face whose marks are the same in every script: no word of it
1239
+ // is given one, and a right-to-left word is still measured as it
1240
+ // is painted in a document shaped in its own language (#770).
1241
+ if (!faceReadsScript(font))
1242
+ return rtl && getMeasureLanguage() !== undefined && !hasCJK(word) ? { script: undefined, rtl } : undefined;
1243
+ if (hasCJK(word))
1244
+ return undefined;
1115
1245
  if (!isLetterless(word)) {
1116
1246
  if (rtl)
1117
1247
  return { script: undefined, rtl };
@@ -1243,6 +1373,9 @@ keepAll = false) {
1243
1373
  kind: 'text',
1244
1374
  width: (inScript.whole ? inScript.whole(clean) : textWidth(clean, font, sc)) + (inScript.untracked ? 0 : track(clean)),
1245
1375
  ...(breakPoints.length > 0 ? { breakPoints, hyphenWidth: 0, bareBreaks: true } : {}),
1376
+ // The rest of an address divided at a joint is measured as the
1377
+ // text that opens the next line (#770).
1378
+ ...(ownPieces && breakPoints.length > 0 ? { ownPieces: spacing } : {}),
1246
1379
  });
1247
1380
  continue;
1248
1381
  }
@@ -1268,7 +1401,7 @@ keepAll = false) {
1268
1401
  const wordSpacing = isSpace ? letterSpacingPx : untracked ? 0 : wordLetterSpacing(clean, letterSpacingPx);
1269
1402
  const inScript = measured.whole;
1270
1403
  const contextFields = measured.fields;
1271
- const breakPoints = isSpace ? [] : wordBreakPoints(clean, soft, font, wordSpacing, sc, dashBreaks, touching, cjk, measured.upTo);
1404
+ const breakPoints = isSpace ? [] : wordBreakPoints(clean, soft, font, wordSpacing, sc, dashBreaks, touching, cjk, measured.upTo, ownPieces && !cjk);
1272
1405
  if (breakPoints.length > 0) {
1273
1406
  tokens.push({
1274
1407
  text: clean,
@@ -1282,6 +1415,9 @@ keepAll = false) {
1282
1415
  width: cjk ? cjk.width : (inScript ? inScript(clean) : textWidth(clean, font, sc)) + (untracked ? 0 : track(clean)),
1283
1416
  breakPoints,
1284
1417
  hyphenWidth: measureTextWidth('-', font) + letterSpacingPx,
1418
+ // A word that holds CJK characters is measured unit by unit:
1419
+ // its rest is already the text that opens the next line.
1420
+ ...(ownPieces && !cjk ? { ownPieces: wordSpacing } : {}),
1285
1421
  });
1286
1422
  }
1287
1423
  else {
@@ -1503,11 +1639,15 @@ function isDivisible(token) {
1503
1639
  /** Cut a word at one of its break points: the head ends the line (with
1504
1640
  * `mark`, `markWidth` px wide, appended — or in place of the middle dot of
1505
1641
  * an `l·l`), the tail keeps the break points after the cut. */
1506
- function splitToken(token, bp, mark, markWidth) {
1642
+ function splitToken(token, bp, mark, markWidth, font) {
1507
1643
  const flags = { bold: token.bold, italic: token.italic, captionLabel: token.captionLabel, ...scriptOf(token) };
1508
- const residual = (token.breakPoints ?? [])
1509
- .filter((b) => b.charIndex > bp.charIndex)
1510
- .map((b) => ({ ...b, charIndex: b.charIndex - bp.charIndex, widthBefore: b.widthBefore - bp.widthBefore }));
1644
+ // The rest measured as the text that opens the next line (#770).
1645
+ const own = font !== undefined && token.ownPieces !== undefined ? ownRest(token, bp.charIndex, font) : undefined;
1646
+ const residual = own
1647
+ ? ownRestBreaks(token, bp.charIndex, own)
1648
+ : (token.breakPoints ?? [])
1649
+ .filter((b) => b.charIndex > bp.charIndex)
1650
+ .map((b) => ({ ...b, charIndex: b.charIndex - bp.charIndex, widthBefore: b.widthBefore - bp.widthBefore }));
1511
1651
  return {
1512
1652
  head: mark !== '' && bp.replacesWidth !== undefined
1513
1653
  ? { ...flags, text: withLineEndHyphen(token.text.slice(0, bp.charIndex)), kind: 'text', width: bp.widthBefore - bp.replacesWidth + markWidth }
@@ -1517,17 +1657,27 @@ function splitToken(token, bp, mark, markWidth) {
1517
1657
  ...pieceScriptFields(token, bp.charIndex, token.text.length),
1518
1658
  text: token.text.slice(bp.charIndex),
1519
1659
  kind: 'text',
1520
- width: token.width - bp.widthBefore,
1660
+ width: own ? own(token.text.length) : token.width - bp.widthBefore,
1521
1661
  ...(residual.length > 0 ? { breakPoints: residual, hyphenWidth: token.hyphenWidth } : {}),
1522
1662
  ...(token.bareBreaks ? { bareBreaks: true } : {}),
1523
1663
  ...(token.dashJoin ? { dashJoin: true } : {}),
1664
+ ...(own ? { ownPieces: token.ownPieces } : {}),
1524
1665
  },
1525
1666
  };
1526
1667
  }
1527
1668
  /** `token` (the tail of a compound broken after its hyphen) opening with
1528
1669
  * that hyphen repeated, `hyphenWidth` px wide; its break points move with
1529
1670
  * it. */
1530
- function withLeadingHyphen(token, hyphenWidth) {
1671
+ function withLeadingHyphen(token, hyphenWidth, font) {
1672
+ // A word measured as the text painted (#770): the hyphen is part of the
1673
+ // text the line opens with, and the word is measured with it.
1674
+ if (font !== undefined && token.ownPieces !== undefined) {
1675
+ const own = ownRest(token, 0, font, '-');
1676
+ const breakPoints = ownRestBreaks(token, 0, own, 1);
1677
+ const { breakPoints: _points, ...rest } = token;
1678
+ void _points;
1679
+ return { ...rest, text: `-${token.text}`, width: own(token.text.length), ...(breakPoints.length > 0 ? { breakPoints } : {}) };
1680
+ }
1531
1681
  return {
1532
1682
  ...token,
1533
1683
  text: `-${token.text}`,
@@ -1674,6 +1824,24 @@ function measureRichText(spans, plainText, normalFont, boldFont, italicFont, bol
1674
1824
  if (options?.labelColumnPx !== undefined)
1675
1825
  setLabelTabs(tokens, (t) => t.labelTab, options.labelColumnPx, (t, w) => { t.width = w; });
1676
1826
  const repeatHyphen = options?.repeatHyphen === true;
1827
+ // Words whose halves at a line end are measured as the texts painted, in
1828
+ // a document shaped in its own language (#770, `RichToken.ownPieces`):
1829
+ // the font each is measured in, and the pieces of the rest of one that
1830
+ // opens a line, for the lines Knuth–Plass sets (`reconstructRichLines`).
1831
+ const ownTokens = tokens.some((t) => t.ownPieces !== undefined);
1832
+ const fontOfToken = (t) => tokenFont(t, normalFont, boldFont, italicFont, boldItalicFont);
1833
+ const ownFont = (t) => (t.ownPieces !== undefined ? fontOfToken(t) : undefined);
1834
+ const restPieces = (tokenIndex, from, cuts, lead) => {
1835
+ const token = tokens[tokenIndex];
1836
+ const width = ownRest(token, from, fontOfToken(token), lead);
1837
+ let before = 0;
1838
+ return cuts.map((to) => {
1839
+ const upTo = width(to);
1840
+ const piece = upTo - before;
1841
+ before = upTo;
1842
+ return piece;
1843
+ });
1844
+ };
1677
1845
  const hasSmallCaps = tokens.some((t) => t.smallCaps);
1678
1846
  const normalSpaceWidth = textAlign === 'justify' ? normalSpaceWidthFor(normalFont) + letterSpacingPx : 0;
1679
1847
  if (tokens.length === 0) {
@@ -1704,6 +1872,9 @@ function measureRichText(spans, plainText, normalFont, boldFont, italicFont, bol
1704
1872
  const minShrinkRatio = ragged ? 1 : options.minShrinkRatio ?? 0.8;
1705
1873
  // The runt threshold counts word spaces on ragged text too.
1706
1874
  const spaceWidth = ragged ? normalSpaceWidthFor(normalFont) + letterSpacingPx : normalSpaceWidth;
1875
+ // The rest of a word broken at a line end, as the text painted (#770).
1876
+ if (ownTokens)
1877
+ markRestShifts(tokens, fontOfToken, repeatHyphen);
1707
1878
  const items = richTokensToItems(tokens, spaceWidth, maxStretchRatio, minShrinkRatio, repeatHyphen);
1708
1879
  const lineWidthFn = lineWidthFunction(maxWidthPx, options);
1709
1880
  const lineIndentFn = indentOf;
@@ -1729,7 +1900,7 @@ function measureRichText(spans, plainText, normalFont, boldFont, italicFont, bol
1729
1900
  ...(ragged && hyphenationZonePx !== undefined ? { hyphenationZone: hyphenationZonePx } : {}),
1730
1901
  });
1731
1902
  if (breaks.length > 0) {
1732
- const kpLines = reconstructRichLines(items, breaks, tokens, lineHeightPx, lineWidthFn, lineIndentFn, normalSpaceWidth, textAlign, trackingPerChar, lineBaselineOffset(lineHeightPx, normalFont));
1903
+ const kpLines = reconstructRichLines(items, breaks, tokens, lineHeightPx, lineWidthFn, lineIndentFn, normalSpaceWidth, textAlign, trackingPerChar, lineBaselineOffset(lineHeightPx, normalFont), ownTokens ? restPieces : undefined);
1733
1904
  if (!hasOverfullLine(kpLines, lineWidthFn, ragged)) {
1734
1905
  if (hasSmallCaps)
1735
1906
  expandSmallCaps(kpLines, normalFont, boldFont, italicFont, boldItalicFont, letterSpacingPx);
@@ -1788,7 +1959,7 @@ function measureRichText(spans, plainText, normalFont, boldFont, italicFont, bol
1788
1959
  const lineRepeated = repeatPending && tokens[tokenIdx]?.kind === 'text';
1789
1960
  if (lineRepeated) {
1790
1961
  const tail = tokens[tokenIdx];
1791
- tokens[tokenIdx] = withLeadingHyphen(tail, tail.hyphenWidth ?? measureTextWidth('-', tokenFont(tail, normalFont, boldFont, italicFont, boldItalicFont)) + letterSpacingPx);
1962
+ tokens[tokenIdx] = withLeadingHyphen(tail, tail.hyphenWidth ?? measureTextWidth('-', tokenFont(tail, normalFont, boldFont, italicFont, boldItalicFont)) + letterSpacingPx, ownFont(tail));
1792
1963
  }
1793
1964
  repeatPending = false;
1794
1965
  // This line ends after a compound's hyphen the next one repeats.
@@ -1881,7 +2052,7 @@ function measureRichText(spans, plainText, normalFont, boldFont, italicFont, bol
1881
2052
  }
1882
2053
  if (chosen) {
1883
2054
  const mark = plainBreak(chosen) ? '' : '-';
1884
- const split = splitToken(token, chosen, mark, markW(chosen));
2055
+ const split = splitToken(token, chosen, mark, markW(chosen), ownFont(token));
1885
2056
  lineTokens.push(split.head);
1886
2057
  lineWidth += split.head.width;
1887
2058
  lineHyphenated = chosen.cjk !== true;
@@ -1936,7 +2107,7 @@ function measureRichText(spans, plainText, normalFont, boldFont, italicFont, bol
1936
2107
  const origIdx = tokenIdx - (lineTokens.length - back.at);
1937
2108
  while (lineTokens.length > back.at)
1938
2109
  lineWidth -= lineTokens.pop().width;
1939
- const split = splitToken(back.token, back.bp, '', 0);
2110
+ const split = splitToken(back.token, back.bp, '', 0, ownFont(back.token));
1940
2111
  lineTokens.push(split.head);
1941
2112
  lineWidth += split.head.width;
1942
2113
  tokens[origIdx] = split.tail;
@@ -1976,7 +2147,7 @@ function measureRichText(spans, plainText, normalFont, boldFont, italicFont, bol
1976
2147
  const origIdx = tokenIdx - (lineTokens.length - syllable.at);
1977
2148
  while (lineTokens.length > syllable.at)
1978
2149
  lineWidth -= lineTokens.pop().width;
1979
- const split = splitToken(syllable.token, syllable.bp, '-', syllable.token.hyphenWidth ?? 0);
2150
+ const split = splitToken(syllable.token, syllable.bp, '-', syllable.token.hyphenWidth ?? 0, ownFont(syllable.token));
1980
2151
  lineTokens.push(split.head);
1981
2152
  lineWidth += split.head.width;
1982
2153
  tokens[origIdx] = split.tail;