postext 1.14.1 → 1.15.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 (489) hide show
  1. package/dist/__tests__/arabic/blockLanguageDigits.test.d.ts +2 -0
  2. package/dist/__tests__/arabic/blockLanguageDigits.test.d.ts.map +1 -0
  3. package/dist/__tests__/arabic/blockLanguageDigits.test.js +54 -0
  4. package/dist/__tests__/arabic/blockLanguageDigits.test.js.map +1 -0
  5. package/dist/__tests__/arabic/emphasis.test.d.ts +2 -0
  6. package/dist/__tests__/arabic/emphasis.test.d.ts.map +1 -0
  7. package/dist/__tests__/arabic/emphasis.test.js +206 -0
  8. package/dist/__tests__/arabic/emphasis.test.js.map +1 -0
  9. package/dist/__tests__/arabic/footnotes.test.d.ts +2 -0
  10. package/dist/__tests__/arabic/footnotes.test.d.ts.map +1 -0
  11. package/dist/__tests__/arabic/footnotes.test.js +118 -0
  12. package/dist/__tests__/arabic/footnotes.test.js.map +1 -0
  13. package/dist/__tests__/arabic/harakat.test.d.ts +2 -0
  14. package/dist/__tests__/arabic/harakat.test.d.ts.map +1 -0
  15. package/dist/__tests__/arabic/harakat.test.js +167 -0
  16. package/dist/__tests__/arabic/harakat.test.js.map +1 -0
  17. package/dist/__tests__/arabic/ornateBrackets.test.d.ts +2 -0
  18. package/dist/__tests__/arabic/ornateBrackets.test.d.ts.map +1 -0
  19. package/dist/__tests__/arabic/ornateBrackets.test.js +71 -0
  20. package/dist/__tests__/arabic/ornateBrackets.test.js.map +1 -0
  21. package/dist/__tests__/arabic/tashkil.test.d.ts +2 -0
  22. package/dist/__tests__/arabic/tashkil.test.d.ts.map +1 -0
  23. package/dist/__tests__/arabic/tashkil.test.js +106 -0
  24. package/dist/__tests__/arabic/tashkil.test.js.map +1 -0
  25. package/dist/__tests__/arabicLocale.test.d.ts +2 -0
  26. package/dist/__tests__/arabicLocale.test.d.ts.map +1 -0
  27. package/dist/__tests__/arabicLocale.test.js +232 -0
  28. package/dist/__tests__/arabicLocale.test.js.map +1 -0
  29. package/dist/__tests__/arabicNumberWords.test.d.ts +2 -0
  30. package/dist/__tests__/arabicNumberWords.test.d.ts.map +1 -0
  31. package/dist/__tests__/arabicNumberWords.test.js +176 -0
  32. package/dist/__tests__/arabicNumberWords.test.js.map +1 -0
  33. package/dist/__tests__/arabicNumbering.test.d.ts +2 -0
  34. package/dist/__tests__/arabicNumbering.test.d.ts.map +1 -0
  35. package/dist/__tests__/arabicNumbering.test.js +257 -0
  36. package/dist/__tests__/arabicNumbering.test.js.map +1 -0
  37. package/dist/__tests__/arabicWords.test.d.ts +2 -0
  38. package/dist/__tests__/arabicWords.test.d.ts.map +1 -0
  39. package/dist/__tests__/arabicWords.test.js +302 -0
  40. package/dist/__tests__/arabicWords.test.js.map +1 -0
  41. package/dist/__tests__/bidi.test.d.ts +2 -0
  42. package/dist/__tests__/bidi.test.d.ts.map +1 -0
  43. package/dist/__tests__/bidi.test.js +282 -0
  44. package/dist/__tests__/bidi.test.js.map +1 -0
  45. package/dist/__tests__/blockHints.test.d.ts +2 -0
  46. package/dist/__tests__/blockHints.test.d.ts.map +1 -0
  47. package/dist/__tests__/blockHints.test.js +79 -0
  48. package/dist/__tests__/blockHints.test.js.map +1 -0
  49. package/dist/__tests__/chineseLocale.test.js +2 -2
  50. package/dist/__tests__/chineseLocale.test.js.map +1 -1
  51. package/dist/__tests__/cjk/grid.test.js +2 -2
  52. package/dist/__tests__/cjk/grid.test.js.map +1 -1
  53. package/dist/__tests__/cjk/justifyRender.test.js +2 -1
  54. package/dist/__tests__/cjk/justifyRender.test.js.map +1 -1
  55. package/dist/__tests__/defaults/folio.test.js +16 -1
  56. package/dist/__tests__/defaults/folio.test.js.map +1 -1
  57. package/dist/__tests__/direction.test.d.ts +2 -0
  58. package/dist/__tests__/direction.test.d.ts.map +1 -0
  59. package/dist/__tests__/direction.test.js +117 -0
  60. package/dist/__tests__/direction.test.js.map +1 -0
  61. package/dist/__tests__/exports.test.js +29 -0
  62. package/dist/__tests__/exports.test.js.map +1 -1
  63. package/dist/__tests__/headingLetterSpacing.test.js +1 -1
  64. package/dist/__tests__/headingLetterSpacing.test.js.map +1 -1
  65. package/dist/__tests__/headingNumberPosition.test.d.ts +2 -0
  66. package/dist/__tests__/headingNumberPosition.test.d.ts.map +1 -0
  67. package/dist/__tests__/headingNumberPosition.test.js +87 -0
  68. package/dist/__tests__/headingNumberPosition.test.js.map +1 -0
  69. package/dist/__tests__/htmlCopyText.test.d.ts +2 -0
  70. package/dist/__tests__/htmlCopyText.test.d.ts.map +1 -0
  71. package/dist/__tests__/htmlCopyText.test.js +153 -0
  72. package/dist/__tests__/htmlCopyText.test.js.map +1 -0
  73. package/dist/__tests__/kashida.test.d.ts +2 -0
  74. package/dist/__tests__/kashida.test.d.ts.map +1 -0
  75. package/dist/__tests__/kashida.test.js +281 -0
  76. package/dist/__tests__/kashida.test.js.map +1 -0
  77. package/dist/__tests__/kashidaPatterns.test.d.ts +2 -0
  78. package/dist/__tests__/kashidaPatterns.test.d.ts.map +1 -0
  79. package/dist/__tests__/kashidaPatterns.test.js +143 -0
  80. package/dist/__tests__/kashidaPatterns.test.js.map +1 -0
  81. package/dist/__tests__/links.test.js +3 -1
  82. package/dist/__tests__/links.test.js.map +1 -1
  83. package/dist/__tests__/parse/containers.test.js +1 -1
  84. package/dist/__tests__/parse/containers.test.js.map +1 -1
  85. package/dist/__tests__/rtl/designText.test.d.ts +2 -0
  86. package/dist/__tests__/rtl/designText.test.d.ts.map +1 -0
  87. package/dist/__tests__/rtl/designText.test.js +301 -0
  88. package/dist/__tests__/rtl/designText.test.js.map +1 -0
  89. package/dist/__tests__/rtl/designTextPaint.test.d.ts +2 -0
  90. package/dist/__tests__/rtl/designTextPaint.test.d.ts.map +1 -0
  91. package/dist/__tests__/rtl/designTextPaint.test.js +140 -0
  92. package/dist/__tests__/rtl/designTextPaint.test.js.map +1 -0
  93. package/dist/__tests__/rtl/htmlRtl.test.d.ts +2 -0
  94. package/dist/__tests__/rtl/htmlRtl.test.d.ts.map +1 -0
  95. package/dist/__tests__/rtl/htmlRtl.test.js +175 -0
  96. package/dist/__tests__/rtl/htmlRtl.test.js.map +1 -0
  97. package/dist/__tests__/rtl/mirroredFrame.test.d.ts +2 -0
  98. package/dist/__tests__/rtl/mirroredFrame.test.d.ts.map +1 -0
  99. package/dist/__tests__/rtl/mirroredFrame.test.js +313 -0
  100. package/dist/__tests__/rtl/mirroredFrame.test.js.map +1 -0
  101. package/dist/__tests__/rtl/mirroredPaint.test.d.ts +2 -0
  102. package/dist/__tests__/rtl/mirroredPaint.test.d.ts.map +1 -0
  103. package/dist/__tests__/rtl/mirroredPaint.test.js +116 -0
  104. package/dist/__tests__/rtl/mirroredPaint.test.js.map +1 -0
  105. package/dist/__tests__/rtl/oppositeBlocks.test.d.ts +2 -0
  106. package/dist/__tests__/rtl/oppositeBlocks.test.d.ts.map +1 -0
  107. package/dist/__tests__/rtl/oppositeBlocks.test.js +273 -0
  108. package/dist/__tests__/rtl/oppositeBlocks.test.js.map +1 -0
  109. package/dist/__tests__/rtl/segmentLang.test.d.ts +2 -0
  110. package/dist/__tests__/rtl/segmentLang.test.d.ts.map +1 -0
  111. package/dist/__tests__/rtl/segmentLang.test.js +54 -0
  112. package/dist/__tests__/rtl/segmentLang.test.js.map +1 -0
  113. package/dist/__tests__/rtlLines.test.d.ts +2 -0
  114. package/dist/__tests__/rtlLines.test.d.ts.map +1 -0
  115. package/dist/__tests__/rtlLines.test.js +180 -0
  116. package/dist/__tests__/rtlLines.test.js.map +1 -0
  117. package/dist/__tests__/tocNumberColumn.test.d.ts +2 -0
  118. package/dist/__tests__/tocNumberColumn.test.d.ts.map +1 -0
  119. package/dist/__tests__/tocNumberColumn.test.js +67 -0
  120. package/dist/__tests__/tocNumberColumn.test.js.map +1 -0
  121. package/dist/__tests__/verse.test.d.ts +2 -0
  122. package/dist/__tests__/verse.test.d.ts.map +1 -0
  123. package/dist/__tests__/verse.test.js +232 -0
  124. package/dist/__tests__/verse.test.js.map +1 -0
  125. package/dist/__tests__/vertical/geometry.test.js +2 -2
  126. package/dist/__tests__/vertical/geometry.test.js.map +1 -1
  127. package/dist/__tests__/vertical/htmlTurnedCells.test.js +2 -2
  128. package/dist/__tests__/vertical/htmlTurnedCells.test.js.map +1 -1
  129. package/dist/arabicMarks.d.ts +80 -0
  130. package/dist/arabicMarks.d.ts.map +1 -0
  131. package/dist/arabicMarks.js +267 -0
  132. package/dist/arabicMarks.js.map +1 -0
  133. package/dist/arabicNumberWords.d.ts +43 -0
  134. package/dist/arabicNumberWords.d.ts.map +1 -0
  135. package/dist/arabicNumberWords.js +163 -0
  136. package/dist/arabicNumberWords.js.map +1 -0
  137. package/dist/arabicNumerals.d.ts +54 -0
  138. package/dist/arabicNumerals.d.ts.map +1 -0
  139. package/dist/arabicNumerals.js +143 -0
  140. package/dist/arabicNumerals.js.map +1 -0
  141. package/dist/bidi.d.ts +186 -0
  142. package/dist/bidi.d.ts.map +1 -0
  143. package/dist/bidi.js +851 -0
  144. package/dist/bidi.js.map +1 -0
  145. package/dist/bidiData.d.ts +13 -0
  146. package/dist/bidiData.d.ts.map +1 -0
  147. package/dist/bidiData.js +318 -0
  148. package/dist/bidiData.js.map +1 -0
  149. package/dist/canvas-backend/blockRender.d.ts.map +1 -1
  150. package/dist/canvas-backend/blockRender.js +58 -27
  151. package/dist/canvas-backend/blockRender.js.map +1 -1
  152. package/dist/canvas-backend/chip.d.ts.map +1 -1
  153. package/dist/canvas-backend/chip.js +16 -2
  154. package/dist/canvas-backend/chip.js.map +1 -1
  155. package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
  156. package/dist/canvas-backend/headerFooter.js +7 -3
  157. package/dist/canvas-backend/headerFooter.js.map +1 -1
  158. package/dist/canvas-backend/index.d.ts.map +1 -1
  159. package/dist/canvas-backend/index.js +9 -4
  160. package/dist/canvas-backend/index.js.map +1 -1
  161. package/dist/canvas-backend/mirrorFrame.d.ts +38 -0
  162. package/dist/canvas-backend/mirrorFrame.d.ts.map +1 -0
  163. package/dist/canvas-backend/mirrorFrame.js +129 -0
  164. package/dist/canvas-backend/mirrorFrame.js.map +1 -0
  165. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  166. package/dist/canvas-backend/renderResourceBlock.js +16 -5
  167. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  168. package/dist/canvas-backend/runDirection.d.ts +21 -0
  169. package/dist/canvas-backend/runDirection.d.ts.map +1 -0
  170. package/dist/canvas-backend/runDirection.js +46 -0
  171. package/dist/canvas-backend/runDirection.js.map +1 -0
  172. package/dist/canvas-backend/wordRuns.d.ts +49 -0
  173. package/dist/canvas-backend/wordRuns.d.ts.map +1 -0
  174. package/dist/canvas-backend/wordRuns.js +111 -0
  175. package/dist/canvas-backend/wordRuns.js.map +1 -0
  176. package/dist/columnClip.d.ts +4 -1
  177. package/dist/columnClip.d.ts.map +1 -1
  178. package/dist/columnClip.js +8 -3
  179. package/dist/columnClip.js.map +1 -1
  180. package/dist/configWarnings.d.ts +3 -1
  181. package/dist/configWarnings.d.ts.map +1 -1
  182. package/dist/configWarnings.js +59 -5
  183. package/dist/configWarnings.js.map +1 -1
  184. package/dist/defaults/bodyText.d.ts +28 -6
  185. package/dist/defaults/bodyText.d.ts.map +1 -1
  186. package/dist/defaults/bodyText.js +158 -30
  187. package/dist/defaults/bodyText.js.map +1 -1
  188. package/dist/defaults/calloutStyles.js +1 -1
  189. package/dist/defaults/calloutStyles.js.map +1 -1
  190. package/dist/defaults/captionStyle.js +5 -5
  191. package/dist/defaults/captionStyle.js.map +1 -1
  192. package/dist/defaults/folio.d.ts +4 -0
  193. package/dist/defaults/folio.d.ts.map +1 -1
  194. package/dist/defaults/folio.js +12 -1
  195. package/dist/defaults/folio.js.map +1 -1
  196. package/dist/defaults/footnotes.d.ts.map +1 -1
  197. package/dist/defaults/footnotes.js +12 -2
  198. package/dist/defaults/footnotes.js.map +1 -1
  199. package/dist/defaults/headerFooter.d.ts.map +1 -1
  200. package/dist/defaults/headerFooter.js +1 -0
  201. package/dist/defaults/headerFooter.js.map +1 -1
  202. package/dist/defaults/headingStyles.d.ts.map +1 -1
  203. package/dist/defaults/headingStyles.js +2 -1
  204. package/dist/defaults/headingStyles.js.map +1 -1
  205. package/dist/defaults/headings.d.ts.map +1 -1
  206. package/dist/defaults/headings.js +9 -2
  207. package/dist/defaults/headings.js.map +1 -1
  208. package/dist/defaults/index.d.ts +1 -1
  209. package/dist/defaults/index.d.ts.map +1 -1
  210. package/dist/defaults/index.js +7 -1
  211. package/dist/defaults/index.js.map +1 -1
  212. package/dist/defaults/indexConfig.d.ts +6 -2
  213. package/dist/defaults/indexConfig.d.ts.map +1 -1
  214. package/dist/defaults/indexConfig.js +25 -5
  215. package/dist/defaults/indexConfig.js.map +1 -1
  216. package/dist/defaults/page.d.ts +7 -4
  217. package/dist/defaults/page.d.ts.map +1 -1
  218. package/dist/defaults/page.js +10 -7
  219. package/dist/defaults/page.js.map +1 -1
  220. package/dist/defaults/paragraphStyles.js +2 -2
  221. package/dist/defaults/paragraphStyles.js.map +1 -1
  222. package/dist/defaults/parts.d.ts.map +1 -1
  223. package/dist/defaults/parts.js +2 -1
  224. package/dist/defaults/parts.js.map +1 -1
  225. package/dist/defaults/resourceTypes.d.ts +1 -1
  226. package/dist/defaults/resourceTypes.d.ts.map +1 -1
  227. package/dist/defaults/resourceTypes.js +10 -2
  228. package/dist/defaults/resourceTypes.js.map +1 -1
  229. package/dist/defaults/shared.d.ts +9 -0
  230. package/dist/defaults/shared.d.ts.map +1 -1
  231. package/dist/defaults/shared.js +15 -0
  232. package/dist/defaults/shared.js.map +1 -1
  233. package/dist/defaults/tableStyle.d.ts.map +1 -1
  234. package/dist/defaults/tableStyle.js +5 -1
  235. package/dist/defaults/tableStyle.js.map +1 -1
  236. package/dist/design/bidiText.d.ts +89 -0
  237. package/dist/design/bidiText.d.ts.map +1 -0
  238. package/dist/design/bidiText.js +165 -0
  239. package/dist/design/bidiText.js.map +1 -0
  240. package/dist/design/layout.d.ts +20 -0
  241. package/dist/design/layout.d.ts.map +1 -1
  242. package/dist/design/layout.js +46 -5
  243. package/dist/design/layout.js.map +1 -1
  244. package/dist/design/placeholders.d.ts +7 -1
  245. package/dist/design/placeholders.d.ts.map +1 -1
  246. package/dist/design/placeholders.js +6 -3
  247. package/dist/design/placeholders.js.map +1 -1
  248. package/dist/design/richText.d.ts +5 -2
  249. package/dist/design/richText.d.ts.map +1 -1
  250. package/dist/design/richText.js +35 -2
  251. package/dist/design/richText.js.map +1 -1
  252. package/dist/emphasisOverline.d.ts +27 -0
  253. package/dist/emphasisOverline.d.ts.map +1 -0
  254. package/dist/emphasisOverline.js +107 -0
  255. package/dist/emphasisOverline.js.map +1 -0
  256. package/dist/frontmatter.d.ts +25 -6
  257. package/dist/frontmatter.d.ts.map +1 -1
  258. package/dist/frontmatter.js +51 -10
  259. package/dist/frontmatter.js.map +1 -1
  260. package/dist/html-backend.d.ts +4 -1
  261. package/dist/html-backend.d.ts.map +1 -1
  262. package/dist/html-backend.js +363 -87
  263. package/dist/html-backend.js.map +1 -1
  264. package/dist/hyphenate.d.ts.map +1 -1
  265. package/dist/hyphenate.js +4 -1
  266. package/dist/hyphenate.js.map +1 -1
  267. package/dist/index.d.ts +11 -8
  268. package/dist/index.d.ts.map +1 -1
  269. package/dist/index.js +6 -4
  270. package/dist/index.js.map +1 -1
  271. package/dist/knuthPlass/breakpoints.d.ts.map +1 -1
  272. package/dist/knuthPlass/breakpoints.js +3 -0
  273. package/dist/knuthPlass/breakpoints.js.map +1 -1
  274. package/dist/knuthPlass/pretextAdapter.d.ts.map +1 -1
  275. package/dist/knuthPlass/pretextAdapter.js +9 -3
  276. package/dist/knuthPlass/pretextAdapter.js.map +1 -1
  277. package/dist/knuthPlass/richAdapter.d.ts +11 -0
  278. package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
  279. package/dist/knuthPlass/richAdapter.js +11 -4
  280. package/dist/knuthPlass/richAdapter.js.map +1 -1
  281. package/dist/knuthPlass/tracking.d.ts +2 -1
  282. package/dist/knuthPlass/tracking.d.ts.map +1 -1
  283. package/dist/knuthPlass/tracking.js +5 -2
  284. package/dist/knuthPlass/tracking.js.map +1 -1
  285. package/dist/knuthPlass/types.d.ts +4 -0
  286. package/dist/knuthPlass/types.d.ts.map +1 -1
  287. package/dist/locale.d.ts +63 -9
  288. package/dist/locale.d.ts.map +1 -1
  289. package/dist/locale.js +129 -12
  290. package/dist/locale.js.map +1 -1
  291. package/dist/measure/bidiLines.d.ts +91 -0
  292. package/dist/measure/bidiLines.d.ts.map +1 -0
  293. package/dist/measure/bidiLines.js +293 -0
  294. package/dist/measure/bidiLines.js.map +1 -0
  295. package/dist/measure/cache.d.ts.map +1 -1
  296. package/dist/measure/cache.js +10 -4
  297. package/dist/measure/cache.js.map +1 -1
  298. package/dist/measure/joining.d.ts +39 -0
  299. package/dist/measure/joining.d.ts.map +1 -0
  300. package/dist/measure/joining.js +91 -0
  301. package/dist/measure/joining.js.map +1 -0
  302. package/dist/measure/joiningGroups.d.ts +7 -0
  303. package/dist/measure/joiningGroups.d.ts.map +1 -0
  304. package/dist/measure/joiningGroups.js +110 -0
  305. package/dist/measure/joiningGroups.js.map +1 -0
  306. package/dist/measure/kashida.d.ts +97 -0
  307. package/dist/measure/kashida.d.ts.map +1 -0
  308. package/dist/measure/kashida.js +285 -0
  309. package/dist/measure/kashida.js.map +1 -0
  310. package/dist/measure/kashidaPatterns.d.ts +109 -0
  311. package/dist/measure/kashidaPatterns.d.ts.map +1 -0
  312. package/dist/measure/kashidaPatterns.js +519 -0
  313. package/dist/measure/kashidaPatterns.js.map +1 -0
  314. package/dist/measure/links.d.ts.map +1 -1
  315. package/dist/measure/links.js +5 -2
  316. package/dist/measure/links.js.map +1 -1
  317. package/dist/measure/plain.d.ts.map +1 -1
  318. package/dist/measure/plain.js +31 -0
  319. package/dist/measure/plain.js.map +1 -1
  320. package/dist/measure/rich.d.ts +24 -0
  321. package/dist/measure/rich.d.ts.map +1 -1
  322. package/dist/measure/rich.js +250 -22
  323. package/dist/measure/rich.js.map +1 -1
  324. package/dist/measure/types.d.ts +14 -0
  325. package/dist/measure/types.d.ts.map +1 -1
  326. package/dist/measure/types.js.map +1 -1
  327. package/dist/numberWords.d.ts +17 -4
  328. package/dist/numberWords.d.ts.map +1 -1
  329. package/dist/numberWords.js +19 -13
  330. package/dist/numberWords.js.map +1 -1
  331. package/dist/numbering.d.ts +49 -11
  332. package/dist/numbering.d.ts.map +1 -1
  333. package/dist/numbering.js +117 -21
  334. package/dist/numbering.js.map +1 -1
  335. package/dist/parse/annotations.d.ts +6 -3
  336. package/dist/parse/annotations.d.ts.map +1 -1
  337. package/dist/parse/annotations.js +31 -7
  338. package/dist/parse/annotations.js.map +1 -1
  339. package/dist/parse/attrs.d.ts +3 -3
  340. package/dist/parse/attrs.d.ts.map +1 -1
  341. package/dist/parse/attrs.js +5 -4
  342. package/dist/parse/attrs.js.map +1 -1
  343. package/dist/parse/blockParser.d.ts.map +1 -1
  344. package/dist/parse/blockParser.js +158 -14
  345. package/dist/parse/blockParser.js.map +1 -1
  346. package/dist/parse/index.d.ts +1 -1
  347. package/dist/parse/index.d.ts.map +1 -1
  348. package/dist/parse/index.js.map +1 -1
  349. package/dist/parse/inlineFormatting.d.ts.map +1 -1
  350. package/dist/parse/inlineFormatting.js +5 -4
  351. package/dist/parse/inlineFormatting.js.map +1 -1
  352. package/dist/parse/types.d.ts +63 -1
  353. package/dist/parse/types.d.ts.map +1 -1
  354. package/dist/pipeline/build.d.ts.map +1 -1
  355. package/dist/pipeline/build.js +161 -43
  356. package/dist/pipeline/build.js.map +1 -1
  357. package/dist/pipeline/buildBlockKind.d.ts +2 -0
  358. package/dist/pipeline/buildBlockKind.d.ts.map +1 -1
  359. package/dist/pipeline/buildBlockKind.js +67 -2
  360. package/dist/pipeline/buildBlockKind.js.map +1 -1
  361. package/dist/pipeline/buildHelpers.d.ts +22 -1
  362. package/dist/pipeline/buildHelpers.d.ts.map +1 -1
  363. package/dist/pipeline/buildHelpers.js +55 -5
  364. package/dist/pipeline/buildHelpers.js.map +1 -1
  365. package/dist/pipeline/calloutLayout.d.ts +6 -1
  366. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  367. package/dist/pipeline/calloutLayout.js +63 -18
  368. package/dist/pipeline/calloutLayout.js.map +1 -1
  369. package/dist/pipeline/citations.d.ts +2 -1
  370. package/dist/pipeline/citations.d.ts.map +1 -1
  371. package/dist/pipeline/citations.js +29 -9
  372. package/dist/pipeline/citations.js.map +1 -1
  373. package/dist/pipeline/cjkGrid.d.ts +3 -2
  374. package/dist/pipeline/cjkGrid.d.ts.map +1 -1
  375. package/dist/pipeline/cjkGrid.js +3 -2
  376. package/dist/pipeline/cjkGrid.js.map +1 -1
  377. package/dist/pipeline/config.d.ts +13 -1
  378. package/dist/pipeline/config.d.ts.map +1 -1
  379. package/dist/pipeline/config.js +32 -3
  380. package/dist/pipeline/config.js.map +1 -1
  381. package/dist/pipeline/contentWarnings.d.ts +10 -0
  382. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  383. package/dist/pipeline/contentWarnings.js +88 -0
  384. package/dist/pipeline/contentWarnings.js.map +1 -1
  385. package/dist/pipeline/continuation.d.ts.map +1 -1
  386. package/dist/pipeline/continuation.js +1 -1
  387. package/dist/pipeline/continuation.js.map +1 -1
  388. package/dist/pipeline/crossRefs.d.ts.map +1 -1
  389. package/dist/pipeline/crossRefs.js +2 -0
  390. package/dist/pipeline/crossRefs.js.map +1 -1
  391. package/dist/pipeline/floatPlacement.d.ts.map +1 -1
  392. package/dist/pipeline/floatPlacement.js +2 -1
  393. package/dist/pipeline/floatPlacement.js.map +1 -1
  394. package/dist/pipeline/footnotes.d.ts +13 -5
  395. package/dist/pipeline/footnotes.d.ts.map +1 -1
  396. package/dist/pipeline/footnotes.js +19 -8
  397. package/dist/pipeline/footnotes.js.map +1 -1
  398. package/dist/pipeline/headerFooter.d.ts +4 -0
  399. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  400. package/dist/pipeline/headerFooter.js +41 -13
  401. package/dist/pipeline/headerFooter.js.map +1 -1
  402. package/dist/pipeline/headingDesignCuts.d.ts +1 -1
  403. package/dist/pipeline/headingDesignCuts.d.ts.map +1 -1
  404. package/dist/pipeline/headingDesignCuts.js +2 -1
  405. package/dist/pipeline/headingDesignCuts.js.map +1 -1
  406. package/dist/pipeline/headingStyles.d.ts +10 -0
  407. package/dist/pipeline/headingStyles.d.ts.map +1 -1
  408. package/dist/pipeline/headingStyles.js +46 -0
  409. package/dist/pipeline/headingStyles.js.map +1 -1
  410. package/dist/pipeline/indexDirective.d.ts.map +1 -1
  411. package/dist/pipeline/indexDirective.js +33 -6
  412. package/dist/pipeline/indexDirective.js.map +1 -1
  413. package/dist/pipeline/indexGroups.d.ts +23 -1
  414. package/dist/pipeline/indexGroups.d.ts.map +1 -1
  415. package/dist/pipeline/indexGroups.js +50 -1
  416. package/dist/pipeline/indexGroups.js.map +1 -1
  417. package/dist/pipeline/lists.d.ts +16 -4
  418. package/dist/pipeline/lists.d.ts.map +1 -1
  419. package/dist/pipeline/lists.js +51 -12
  420. package/dist/pipeline/lists.js.map +1 -1
  421. package/dist/pipeline/measureContentBlock.d.ts +5 -0
  422. package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
  423. package/dist/pipeline/measureContentBlock.js +63 -4
  424. package/dist/pipeline/measureContentBlock.js.map +1 -1
  425. package/dist/pipeline/mirrorFrame.d.ts +37 -0
  426. package/dist/pipeline/mirrorFrame.d.ts.map +1 -0
  427. package/dist/pipeline/mirrorFrame.js +143 -0
  428. package/dist/pipeline/mirrorFrame.js.map +1 -0
  429. package/dist/pipeline/outline.d.ts +2 -2
  430. package/dist/pipeline/outline.d.ts.map +1 -1
  431. package/dist/pipeline/outline.js +33 -6
  432. package/dist/pipeline/outline.js.map +1 -1
  433. package/dist/pipeline/paragraphContainers.d.ts +5 -0
  434. package/dist/pipeline/paragraphContainers.d.ts.map +1 -1
  435. package/dist/pipeline/paragraphContainers.js +12 -0
  436. package/dist/pipeline/paragraphContainers.js.map +1 -1
  437. package/dist/pipeline/parts.d.ts +1 -1
  438. package/dist/pipeline/parts.d.ts.map +1 -1
  439. package/dist/pipeline/parts.js +3 -2
  440. package/dist/pipeline/parts.js.map +1 -1
  441. package/dist/pipeline/placeholders.d.ts +13 -6
  442. package/dist/pipeline/placeholders.d.ts.map +1 -1
  443. package/dist/pipeline/placeholders.js +15 -11
  444. package/dist/pipeline/placeholders.js.map +1 -1
  445. package/dist/pipeline/placement.d.ts +3 -2
  446. package/dist/pipeline/placement.d.ts.map +1 -1
  447. package/dist/pipeline/placement.js +18 -6
  448. package/dist/pipeline/placement.js.map +1 -1
  449. package/dist/pipeline/resourceLayout.d.ts.map +1 -1
  450. package/dist/pipeline/resourceLayout.js +70 -7
  451. package/dist/pipeline/resourceLayout.js.map +1 -1
  452. package/dist/pipeline/resourceNumbering.d.ts +5 -3
  453. package/dist/pipeline/resourceNumbering.d.ts.map +1 -1
  454. package/dist/pipeline/resourceNumbering.js +11 -8
  455. package/dist/pipeline/resourceNumbering.js.map +1 -1
  456. package/dist/pipeline/styles.d.ts +6 -1
  457. package/dist/pipeline/styles.d.ts.map +1 -1
  458. package/dist/pipeline/styles.js +44 -14
  459. package/dist/pipeline/styles.js.map +1 -1
  460. package/dist/pipeline/tashkil.d.ts +27 -0
  461. package/dist/pipeline/tashkil.d.ts.map +1 -0
  462. package/dist/pipeline/tashkil.js +161 -0
  463. package/dist/pipeline/tashkil.js.map +1 -0
  464. package/dist/pipeline/toc.d.ts +12 -0
  465. package/dist/pipeline/toc.d.ts.map +1 -1
  466. package/dist/pipeline/toc.js +64 -4
  467. package/dist/pipeline/toc.js.map +1 -1
  468. package/dist/pipeline/verse.d.ts +65 -0
  469. package/dist/pipeline/verse.d.ts.map +1 -0
  470. package/dist/pipeline/verse.js +252 -0
  471. package/dist/pipeline/verse.js.map +1 -0
  472. package/dist/pipeline/verticalMetrics.d.ts +1 -1
  473. package/dist/pipeline/verticalMetrics.d.ts.map +1 -1
  474. package/dist/pipeline/verticalMetrics.js +7 -5
  475. package/dist/pipeline/verticalMetrics.js.map +1 -1
  476. package/dist/table/model.d.ts +4 -3
  477. package/dist/table/model.d.ts.map +1 -1
  478. package/dist/table/model.js.map +1 -1
  479. package/dist/types.d.ts +286 -36
  480. package/dist/types.d.ts.map +1 -1
  481. package/dist/uprightArabic.d.ts +39 -0
  482. package/dist/uprightArabic.d.ts.map +1 -0
  483. package/dist/uprightArabic.js +105 -0
  484. package/dist/uprightArabic.js.map +1 -0
  485. package/dist/vdt.d.ts +276 -11
  486. package/dist/vdt.d.ts.map +1 -1
  487. package/dist/vdt.js +46 -9
  488. package/dist/vdt.js.map +1 -1
  489. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  import { footnoteRuleSegments } from './columnRule';
2
- import { tableCellFillRects, tableFrameOutline } from './vdt';
2
+ import { lineTextAlign, tableCellFillRects, tableFrameOutline } from './vdt';
3
3
  import { dimensionToPx } from './units';
4
4
  import { documentInkHex, isSingleInkSvgUrl, singleInkColorMatrix } from './svg/singleInk';
5
5
  import { lineInkExtent, lineTrailingTracking } from './lineInk';
6
6
  import { CHARACTER_GRID_COLOR, cjkGridCells } from './pipeline/cjkGrid';
7
7
  import { renderLangOf } from './locale';
8
8
  import { hasCJK } from './measure/cjk';
9
- import { DEFAULT_CENTRAL_BASELINE } from './vdt';
9
+ import { joiningScriptIn } from './measure/joining';
10
+ import { DEFAULT_CENTRAL_BASELINE, verticalFlowOf } from './vdt';
10
11
  import { holdsTurnedMark, segmentOrientation, verticalRuns } from './writingMode';
11
12
  import { graphemesOf } from './measure/graphemes';
12
13
  import { fontFamilyOf } from './measure/vertical';
@@ -44,6 +45,93 @@ function lineCjkDecl(line) {
44
45
  function segmentCjk(seg, lineDecl) {
45
46
  return lineDecl ? 'line' : hasCJK(seg.text) ? 'own' : false;
46
47
  }
48
+ /** The `dir` attribute of a box of text running `own`, inside boxes that
49
+ * run `inherited` (#379): only where the two differ, so the markup of a
50
+ * left-to-right document, whose text never runs right to left, carries
51
+ * none. A box with `dir` is also a bidi isolate (the HTML default), so its
52
+ * neutrals — a full stop, a bracket — resolve inside it, as the engine
53
+ * resolved them in the run it measured. */
54
+ function dirAttr(own, inherited) {
55
+ return own === inherited ? '' : ` dir="${own}"`;
56
+ }
57
+ const PLAIN_BOX = { attrs: '', decl: '' };
58
+ /**
59
+ * The box of a word segment of a line (#379): its direction against the
60
+ * one it inherits, the language the author named for its text
61
+ * (`VDTLineSegment.lang`) where it is not the root's (`rootLang`), its
62
+ * letters untracked when they are of a joining script on a tracked line
63
+ * (`tracking`), and its styled runs (`VDTLineSegment.runs`) painted by
64
+ * `paint` around the segment's own `font` / `color`.
65
+ */
66
+ function segmentBox(seg, inherited, rootLang, tracking, font, color, paint) {
67
+ const attrs = dirAttr(seg.rtl ? 'rtl' : 'ltr', inherited) + (seg.lang !== undefined && seg.lang !== rootLang ? ` lang="${esc(seg.lang)}"` : '');
68
+ const decl = tracking !== 0 && joiningScriptIn(seg.text) ? 'letter-spacing:0;' : '';
69
+ const html = seg.runs && seg.runs.length > 0 ? wordRunsHtml(seg, font, color, paint)
70
+ : seg.kashida && seg.kashida.length > 0 ? kashidaTextHtml(seg.text, seg.kashida) : undefined;
71
+ if (!attrs && !decl && html === undefined)
72
+ return PLAIN_BOX;
73
+ return html === undefined ? { attrs, decl } : { attrs, decl, html };
74
+ }
75
+ /**
76
+ * A word whose letters change style inside it (`VDTLineSegment.runs`: a
77
+ * bold letter, a coloured haraka) as one run of text with inline spans for
78
+ * the styled parts. The browser shapes the word whole across the spans, so
79
+ * the letters of a joining script stay joined, where boxes positioned one
80
+ * by one would break them apart. A run in the segment's own font and colour
81
+ * is plain text; one in another face takes no line height, so it sits on
82
+ * the segment's baseline without growing its box.
83
+ */
84
+ function wordRunsHtml(seg, font, color, paint) {
85
+ // Where each run starts in the segment's text, for its inserted
86
+ // tatweels (`VDTLineSegment.kashida`).
87
+ let at = 0;
88
+ return seg.runs.map((run) => {
89
+ const start = at;
90
+ at += run.text.length;
91
+ const text = kashidaTextHtml(run.text, seg.kashida, start);
92
+ const probe = {
93
+ kind: 'text', text: run.text, width: 0,
94
+ ...(run.bold ? { bold: true } : {}),
95
+ ...(run.italic ? { italic: true } : {}),
96
+ ...(seg.fontString ? { fontString: seg.fontString } : {}),
97
+ };
98
+ const own = paint(probe);
99
+ const runColor = run.color ?? own.color;
100
+ const decl = (own.font !== font ? `font:${own.font};line-height:0;` : '') + (runColor !== color ? `color:${runColor};` : '');
101
+ return decl ? `<span style="${decl}">${text}</span>` : text;
102
+ }).join('');
103
+ }
104
+ /**
105
+ * `text` escaped, with the tatweels kashida justification inserted into it
106
+ * (#375; `offsets` into the segment's text, of which `text` starts at
107
+ * `base`) in spans that cannot be selected: they are painted, joined to
108
+ * the letters around them (the browser shapes one font's text whole
109
+ * across spans), but a copy of the page reads the words as written, as
110
+ * the PDF's text does. A tatweel the author typed is not among the
111
+ * offsets and copies. Plain `esc(text)` when none falls in it.
112
+ */
113
+ function kashidaTextHtml(text, offsets, base = 0) {
114
+ if (!offsets || offsets.length === 0)
115
+ return esc(text);
116
+ let out = '';
117
+ let last = 0;
118
+ for (let k = 0; k < offsets.length; k++) {
119
+ const i = offsets[k] - base;
120
+ if (i < last || i >= text.length)
121
+ continue;
122
+ // Consecutive tatweels (a longer elongation) share one span.
123
+ let j = i + 1;
124
+ while (k + 1 < offsets.length && offsets[k + 1] - base === j && j < text.length) {
125
+ j++;
126
+ k++;
127
+ }
128
+ out += `${esc(text.slice(last, i))}<span style="${KASHIDA_DECL}">${esc(text.slice(i, j))}</span>`;
129
+ last = j;
130
+ }
131
+ return out + esc(text.slice(last));
132
+ }
133
+ /** Inserted tatweels: painted, never selected or copied. */
134
+ const KASHIDA_DECL = '-webkit-user-select:none;user-select:none;';
47
135
  /**
48
136
  * The resources this document anchors in its HTML (`id="pt-res-<id>"`):
49
137
  * those whose embed (a first slice, for a split table) is on one of its
@@ -231,25 +319,27 @@ tracking = 0,
231
319
  /** Whether the browser's punctuation spacing is off for the segment
232
320
  * (see {@link CJK_TEXT_DECL}): set by its line (`'line'`) or on its own
233
321
  * box (`'own'`); a box in another face repeats the features either way. */
234
- cjk = false) {
322
+ cjk = false,
323
+ /** Its direction where it differs from its line's (see {@link segmentBox}). */
324
+ box = PLAIN_BOX) {
235
325
  const spacingDecl = seg.tracking !== undefined ? `letter-spacing:${tracking + seg.tracking}px;` : '';
236
326
  // A compressed CJK mark is painted before its box (`inkOffset`).
237
327
  const left = seg.inkOffset !== undefined ? x + seg.inkOffset : x;
238
328
  const scaleDecl = seg.inkScale !== undefined ? inkScaleDecl(seg.inkScale) : '';
239
- const pos = `position:absolute;left:${left.toFixed(3)}px;top:${top};white-space:pre;${spacingDecl}${cjk === 'own' ? CJK_TEXT_DECL : ''}${scaleDecl}`;
329
+ const pos = `position:absolute;left:${left.toFixed(3)}px;top:${top};white-space:pre;${spacingDecl}${cjk === 'own' ? CJK_TEXT_DECL : ''}${scaleDecl}${box.decl}`;
240
330
  // Text set with emphasis dots is emphasis (#193); the dots are the
241
331
  // line's marks.
242
- const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : esc(seg.text);
332
+ const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : box.html ?? esc(seg.text);
243
333
  const featuresDecl = !fontDecl ? '' : scaleDecl ? DASH_FEATURES_DECL : cjk ? CJK_FEATURES_DECL : '';
244
334
  if (seg.refResourceId !== undefined) {
245
335
  // Anchors carry an explicit color so the UA link blue never leaks in.
246
336
  const inner = `<a href="${refAnchorHref(refKey(seg))}" style="text-decoration:none;${fontDecl}${featuresDecl}${fontDecl ? 'line-height:0;' : ''}color:${color};">${text}</a>`;
247
- return `<span style="${pos}">${inner}</span>`;
337
+ return `<span${box.attrs} style="${pos}">${inner}</span>`;
248
338
  }
249
339
  if (fontDecl) {
250
- return `<span style="${pos}"><span style="${fontDecl}${featuresDecl}line-height:0;${colorDecl}">${text}</span></span>`;
340
+ return `<span${box.attrs} style="${pos}"><span style="${fontDecl}${featuresDecl}line-height:0;${colorDecl}">${text}</span></span>`;
251
341
  }
252
- return `<span style="${pos}${colorDecl}">${text}</span>`;
342
+ return `<span${box.attrs} style="${pos}${colorDecl}">${text}</span>`;
253
343
  }
254
344
  /**
255
345
  * {@link renderTextSegment} for a segment of a line set word by word (not
@@ -260,38 +350,40 @@ cjk = false) {
260
350
  * punctuation spacing off on its own box (see {@link CJK_TEXT_DECL}), its
261
351
  * text in `<em>` when it is set with emphasis dots.
262
352
  */
263
- function renderWordTextSegment(seg, x, top, fontDecl, colorDecl, color, cjk) {
353
+ function renderWordTextSegment(seg, x, top, fontDecl, colorDecl, color, cjk,
354
+ /** Its direction, tracking and styled runs (see {@link segmentBox}). */
355
+ box = PLAIN_BOX) {
264
356
  if (cjk || seg.cjkMarks)
265
- return renderMarkedWordSegment(seg, x, top, fontDecl, colorDecl, color, cjk);
266
- const pos = `position:absolute;left:${x.toFixed(3)}px;top:${top};white-space:pre;`;
267
- const text = esc(seg.text);
357
+ return renderMarkedWordSegment(seg, x, top, fontDecl, colorDecl, color, cjk, box);
358
+ const pos = `position:absolute;left:${x.toFixed(3)}px;top:${top};white-space:pre;${box.decl}`;
359
+ const text = box.html ?? esc(seg.text);
268
360
  if (seg.refResourceId !== undefined) {
269
361
  // Anchors carry an explicit color so the UA link blue never leaks in.
270
362
  const inner = `<a href="${refAnchorHref(refKey(seg))}" style="text-decoration:none;${fontDecl}${fontDecl ? 'line-height:0;' : ''}color:${color};">${text}</a>`;
271
- return `<span style="${pos}">${inner}</span>`;
363
+ return `<span${box.attrs} style="${pos}">${inner}</span>`;
272
364
  }
273
365
  if (fontDecl) {
274
- return `<span style="${pos}"><span style="${fontDecl}line-height:0;${colorDecl}">${text}</span></span>`;
366
+ return `<span${box.attrs} style="${pos}"><span style="${fontDecl}line-height:0;${colorDecl}">${text}</span></span>`;
275
367
  }
276
- return `<span style="${pos}${colorDecl}">${text}</span>`;
368
+ return `<span${box.attrs} style="${pos}${colorDecl}">${text}</span>`;
277
369
  }
278
370
  /** {@link renderWordTextSegment} for a segment that holds CJK text (`cjk`)
279
371
  * or carries Chinese marks. */
280
- function renderMarkedWordSegment(seg, x, top, fontDecl, colorDecl, color, cjk) {
281
- const pos = `position:absolute;left:${x.toFixed(3)}px;top:${top};white-space:pre;${cjk ? CJK_TEXT_DECL : ''}`;
372
+ function renderMarkedWordSegment(seg, x, top, fontDecl, colorDecl, color, cjk, box = PLAIN_BOX) {
373
+ const pos = `position:absolute;left:${x.toFixed(3)}px;top:${top};white-space:pre;${cjk ? CJK_TEXT_DECL : ''}${box.decl}`;
282
374
  // Text set with emphasis dots is emphasis (#193); the dots are the
283
375
  // line's marks.
284
- const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : esc(seg.text);
376
+ const text = seg.cjkMarks?.dots ? `<em style="font-style:inherit;">${esc(seg.text)}</em>` : box.html ?? esc(seg.text);
285
377
  const featuresDecl = fontDecl && cjk ? CJK_FEATURES_DECL : '';
286
378
  if (seg.refResourceId !== undefined) {
287
379
  // Anchors carry an explicit color so the UA link blue never leaks in.
288
380
  const inner = `<a href="${refAnchorHref(refKey(seg))}" style="text-decoration:none;${fontDecl}${featuresDecl}${fontDecl ? 'line-height:0;' : ''}color:${color};">${text}</a>`;
289
- return `<span style="${pos}">${inner}</span>`;
381
+ return `<span${box.attrs} style="${pos}">${inner}</span>`;
290
382
  }
291
383
  if (fontDecl) {
292
- return `<span style="${pos}"><span style="${fontDecl}${featuresDecl}line-height:0;${colorDecl}">${text}</span></span>`;
384
+ return `<span${box.attrs} style="${pos}"><span style="${fontDecl}${featuresDecl}line-height:0;${colorDecl}">${text}</span></span>`;
293
385
  }
294
- return `<span style="${pos}${colorDecl}">${text}</span>`;
386
+ return `<span${box.attrs} style="${pos}${colorDecl}">${text}</span>`;
295
387
  }
296
388
  /** A `:ref` painted as several runs (a label in small capitals: one run per
297
389
  * case) from `segs[start]` on. Every run is placed as measured, and all of
@@ -314,11 +406,16 @@ function renderRefRuns(segs, start, x, color, paint, linked = true) {
314
406
  }
315
407
  /** Wraps each run of segments of one link (`VDTLineSegment.href`) in an
316
408
  * `<a>`: `at` returns the markup to emit before a segment with that link
317
- * (closing the previous anchor, opening its own), `end` the markup that
318
- * closes the line. The segments stay absolutely positioned inside it; the
409
+ * (closing the previous anchor, opening its own), `space` the markup to
410
+ * emit before a word space, `end` the markup that closes the line. The segments stay absolutely positioned inside it; the
319
411
  * anchor takes the text colour, so a link reads as the surrounding text. */
320
412
  function linkRuns() {
321
413
  let open;
414
+ const end = () => {
415
+ const close = open !== undefined ? '</a>' : '';
416
+ open = undefined;
417
+ return close;
418
+ };
322
419
  return {
323
420
  at(href) {
324
421
  if (href === open)
@@ -329,11 +426,11 @@ function linkRuns() {
329
426
  ? `<a href="${esc(href)}" rel="noopener noreferrer" style="color:inherit;text-decoration:none;">`
330
427
  : '');
331
428
  },
332
- end() {
333
- const close = open !== undefined ? '</a>' : '';
334
- open = undefined;
335
- return close;
336
- },
429
+ /** Before a word space: closes the open anchor unless the word after
430
+ * the space (`next`) is part of the same link, so a link's text never
431
+ * ends on a space (#403). */
432
+ space: (next) => (next === open ? '' : end()),
433
+ end,
337
434
  };
338
435
  }
339
436
  /** A segment's link, unless it is a `:ref` (which links to its resource):
@@ -350,6 +447,60 @@ function segmentHref(seg) {
350
447
  return `#${pageElementId(seg.pageLink)}`;
351
448
  return undefined;
352
449
  }
450
+ /**
451
+ * A word space of a line, or what separates it from the next, as text a
452
+ * selection copies (#403). Every word sits in its own absolutely positioned
453
+ * box, so without these the browser copies a line's words run together. The
454
+ * box is placed where the space falls (`x`) and paints nothing: the layout
455
+ * is unchanged. A space stretched by justification takes the stretch as
456
+ * `word-spacing`, so a selection's highlight spans the gap the reader sees.
457
+ * The markup is in logical order like the words', so a right-to-left line
458
+ * copies as written. Empty `text` (a gap the composer added, see
459
+ * `VDTLineSegment.autospace`) emits nothing.
460
+ */
461
+ function copyTextHtml(text, x, stretch = 0) {
462
+ if (!text)
463
+ return '';
464
+ const spacing = Math.abs(stretch) >= 0.0005 ? `word-spacing:${stretch.toFixed(3)}px;` : '';
465
+ return `<span style="position:absolute;left:${x.toFixed(3)}px;top:0;white-space:pre;${spacing}">${esc(text)}</span>`;
466
+ }
467
+ /** {@link copyTextHtml} for what follows a line (see {@link lineEndText}):
468
+ * transparent, so a selection's highlight never runs past the line's end.
469
+ * The text still copies; clipped (`overflow:hidden` on a box of no width)
470
+ * it would not, Chrome leaves clipped text out of a selection's string. */
471
+ const LINE_END_DECL = 'opacity:0;';
472
+ function lineEndHtml(text, x) {
473
+ if (!text)
474
+ return '';
475
+ return `<span style="position:absolute;left:${x.toFixed(3)}px;top:0;${LINE_END_DECL}white-space:pre;">${esc(text)}</span>`;
476
+ }
477
+ /**
478
+ * What a copy puts between a line and the one after it in the same block
479
+ * (`next`), or after a block's last line (#403): nothing after a line that
480
+ * ends inside a word or on a hyphen or dash (`VDTLine.hyphenated`); a
481
+ * newline after a line of verse and after the last line of a paragraph (or
482
+ * of a heading, a caption, a cell); between two lines of a block, a space
483
+ * where the break consumed one — the plain text skips a character between
484
+ * them (`plainEnd` / `plainStart`) — and nothing where it did not (between
485
+ * two ideographs). Lines without those offsets, and a block's last line
486
+ * whose paragraph goes on in the next column or page (`isLastLine:
487
+ * false`), take a space unless the break has a CJK character on either
488
+ * side.
489
+ */
490
+ function lineEndText(line, next) {
491
+ if (line.hyphenated)
492
+ return '';
493
+ if (line.verse || (!next && line.isLastLine !== false))
494
+ return '\n';
495
+ if (next && line.plainEnd !== undefined && next.plainStart !== undefined)
496
+ return next.plainStart > line.plainEnd ? ' ' : '';
497
+ return cjkAt(Array.from(line.text.trimEnd()).pop()) || cjkAt(next && Array.from(next.text.trimStart())[0]) ? '' : ' ';
498
+ }
499
+ /** Whether `char` is CJK; no CJK character sits below U+1100, so a Latin
500
+ * page never gets as far as the test. */
501
+ function cjkAt(char) {
502
+ return char !== undefined && char.codePointAt(0) >= 0x1100 && hasCJK(char);
503
+ }
353
504
  /**
354
505
  * The segments of a horizontal line. A line of the CJK composer goes to
355
506
  * {@link renderComposedSegments}. Any other line was set word by word and
@@ -361,25 +512,36 @@ function segmentHref(seg) {
361
512
  * line's `text` holds the text of every segment, except the leader of a
362
513
  * contents entry (`tocEntry`).
363
514
  */
364
- function renderSegments(line, block, targets) {
515
+ function renderSegments(line, block, targets, rootDir = 'ltr', rootLang,
516
+ /** What a copy puts after the line (see {@link lineEndText}). */
517
+ end = '') {
365
518
  if (line.cjkComposed)
366
- return renderComposedSegments(line, block, targets);
519
+ return renderComposedSegments(line, block, targets, rootDir, end);
367
520
  // The tracking after the last glyph is advance, not ink: centring and
368
521
  // right alignment leave it out (EF-153), as the canvas does.
369
- const trailing = lineTrailingTracking(line, (block.letterSpacing ?? 0) + (line.letterSpacing ?? 0));
522
+ const tracking = (block.letterSpacing ?? 0) + (line.letterSpacing ?? 0);
523
+ const trailing = lineTrailingTracking(line, tracking);
524
+ // A line of a block set against its frame's direction aligns in its span
525
+ // (`VDTLine.measure`) from that span's right, its start side (#371).
526
+ const span = line.measure;
527
+ const align = lineTextAlign(line, block.textAlign);
528
+ // Where the span starts inside the line's box (0 but for such a line).
529
+ const origin = span ? span.x - line.bbox.x : 0;
370
530
  if (!line.segments || line.segments.length === 0) {
371
- const plainIndent = line.bbox.x - block.bbox.x;
531
+ const plainIndent = span ? span.x - block.bbox.x : line.bbox.x - block.bbox.x;
532
+ const room = span ? span.width : block.bbox.width - plainIndent;
372
533
  const plainWidth = line.bbox.width - trailing;
373
- const plainLeft = block.textAlign === 'right'
374
- ? Math.max(0, block.bbox.width - plainIndent - plainWidth)
375
- : block.textAlign === 'center'
376
- ? Math.max(0, (block.bbox.width - plainIndent - plainWidth) / 2)
377
- : 0;
378
- return `<span style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${esc(line.text)}</span>`;
534
+ const plainLeft = origin + (align === 'right'
535
+ ? Math.max(0, room - plainWidth)
536
+ : align === 'center'
537
+ ? Math.max(0, (room - plainWidth) / 2)
538
+ : 0);
539
+ const plainDir = dirAttr(block.direction ?? rootDir, rootDir);
540
+ return `<span${plainDir} style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${esc(line.text)}</span>` + lineEndHtml(end, plainLeft + plainWidth);
379
541
  }
380
542
  // Match canvas justification: stretch inter-word spaces to fill effective width.
381
- const lineIndent = line.bbox.x - block.bbox.x;
382
- const effectiveWidth = block.bbox.width - lineIndent;
543
+ const lineIndent = span ? span.x - block.bbox.x : line.bbox.x - block.bbox.x;
544
+ const effectiveWidth = span ? span.width : block.bbox.width - lineIndent;
383
545
  let wordWidth = 0;
384
546
  let spaceCount = 0;
385
547
  for (const seg of line.segments) {
@@ -399,13 +561,18 @@ function renderSegments(line, block, targets) {
399
561
  ? (effectiveWidth - wordWidth) / spaceCount
400
562
  : 0;
401
563
  // Centred / right alignment — math display blocks, ragged-left paragraph
402
- // styles. Distribute the leading gap.
564
+ // styles. Distribute the leading gap. A justified line fills its span.
403
565
  const slack = Math.max(0, effectiveWidth - (contentWidth - trailing));
404
- const leadingGap = block.textAlign === 'center' ? slack / 2 : block.textAlign === 'right' ? slack : 0;
566
+ const leadingGap = origin + (useJustify ? 0 : align === 'center' ? slack / 2 : align === 'right' ? slack : 0);
405
567
  const parts = [];
406
568
  const links = linkRuns();
407
569
  let x = leadingGap;
408
570
  const segs = line.segments;
571
+ // Segments advance along the line in `line.order` when it has one (a
572
+ // line with right-to-left runs, or any line of a mirrored page): their
573
+ // x are taken in that order; the markup stays in logical order, so the
574
+ // text copies as written.
575
+ const at = line.order ? orderedOffsets(segs, line.order, leadingGap, useJustify ? justifiedSpaceWidth : undefined) : undefined;
409
576
  const cjk = block.tocEntry !== undefined || hasCJK(line.text);
410
577
  const paintText = (seg, at, inLink = false) => {
411
578
  const font = quoteFontString(pickSegmentFont(seg, block));
@@ -413,11 +580,16 @@ function renderSegments(line, block, targets) {
413
580
  const fontDecl = font !== quoteFontString(block.fontString) ? `font:${font};` : '';
414
581
  const colorDecl = color !== block.color ? `color:${color};` : '';
415
582
  const top = seg.baselineShift ? `${seg.baselineShift.toFixed(3)}px` : '0';
416
- return renderWordTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, color, cjk && hasCJK(seg.text));
583
+ return renderWordTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, color, cjk && hasCJK(seg.text), segmentBox(seg, rootDir, rootLang, tracking, font, color, (probe) => ({ font: quoteFontString(pickSegmentFont(probe, block)), color: pickSegmentColor(probe, block) })));
417
584
  };
418
585
  for (let i = 0; i < segs.length; i++) {
419
586
  const seg = segs[i];
587
+ if (at)
588
+ x = at[i];
420
589
  if (seg.kind === 'space') {
590
+ // The space as text, for copying (#403).
591
+ parts.push(links.space(segs[i + 1] && segmentHref(segs[i + 1])));
592
+ parts.push(copyTextHtml(seg.text, x, useJustify ? justifiedSpaceWidth - seg.width : 0));
421
593
  x += useJustify ? justifiedSpaceWidth : seg.width;
422
594
  continue;
423
595
  }
@@ -433,7 +605,7 @@ function renderSegments(line, block, targets) {
433
605
  continue;
434
606
  }
435
607
  if (seg.chip) {
436
- parts.push(renderChip(seg.chip, x, line.baseline - line.bbox.y, quoteFontString(block.fontString), block.color, (run) => pickSegmentColor({ kind: 'text', text: run.text, width: run.width, bold: run.bold, italic: run.italic }, block)));
608
+ parts.push(renderChip(seg.chip, x, line.baseline - line.bbox.y, quoteFontString(block.fontString), block.color, (run) => pickSegmentColor({ kind: 'text', text: run.text, width: run.width, bold: run.bold, italic: run.italic }, block), rootDir));
437
609
  x += seg.width;
438
610
  continue;
439
611
  }
@@ -448,15 +620,31 @@ function renderSegments(line, block, targets) {
448
620
  x += seg.width;
449
621
  }
450
622
  parts.push(links.end());
623
+ parts.push(lineEndHtml(end, x));
451
624
  // Emphasis dots, proper-name and book-title lines (#193).
452
625
  if (line.marks)
453
626
  parts.push(lineMarksHtml(line, block.color));
454
627
  return parts.join('');
455
628
  }
629
+ /** The x of each segment (by index) of a line whose segments advance in
630
+ * `order` from `start`, spaces taking `spaceWidth` when the line is
631
+ * justified. */
632
+ function orderedOffsets(segs, order, start, spaceWidth) {
633
+ const out = new Array(segs.length).fill(start);
634
+ let x = start;
635
+ for (const i of order) {
636
+ const seg = segs[i];
637
+ if (!seg)
638
+ continue;
639
+ out[i] = x;
640
+ x += seg.kind === 'space' && spaceWidth !== undefined ? spaceWidth : seg.width;
641
+ }
642
+ return out;
643
+ }
456
644
  /** {@link renderSegments} for a line of the CJK composer: hung marks and
457
645
  * Han–Latin spaces kept out of the justification, each segment with its
458
646
  * own tracking, ink offset and scale, warichu notes and ruby readings. */
459
- function renderComposedSegments(line, block, targets) {
647
+ function renderComposedSegments(line, block, targets, rootDir = 'ltr', end = '') {
460
648
  // The tracking after the last glyph is advance, not ink: centring and
461
649
  // right alignment leave it out (EF-153), as the canvas does.
462
650
  const lineTracking = (block.letterSpacing ?? 0) + (line.letterSpacing ?? 0);
@@ -469,7 +657,7 @@ function renderComposedSegments(line, block, targets) {
469
657
  : block.textAlign === 'center'
470
658
  ? Math.max(0, (block.bbox.width - plainIndent - plainWidth) / 2)
471
659
  : 0;
472
- return `<span style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${esc(line.text)}</span>`;
660
+ return `<span style="position:absolute;left:${plainLeft.toFixed(3)}px;top:0;white-space:pre;">${esc(line.text)}</span>` + lineEndHtml(end, plainLeft + plainWidth);
473
661
  }
474
662
  // Match canvas justification: stretch inter-word spaces to fill effective width.
475
663
  const lineIndent = line.bbox.x - block.bbox.x;
@@ -511,12 +699,20 @@ function renderComposedSegments(line, block, targets) {
511
699
  const fontDecl = font !== quoteFontString(block.fontString) ? `font:${font};` : '';
512
700
  const colorDecl = color !== block.color ? `color:${color};` : '';
513
701
  const top = seg.baselineShift ? `${seg.baselineShift.toFixed(3)}px` : '0';
514
- return renderTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, color, lineTracking, segmentCjk(seg, lineDecl));
702
+ // The composer sets left-to-right text: in a right-to-left document
703
+ // its boxes say so (#379).
704
+ const dir = dirAttr(seg.rtl ? 'rtl' : 'ltr', rootDir);
705
+ return renderTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, color, lineTracking, segmentCjk(seg, lineDecl), dir ? { attrs: dir, decl: '' } : PLAIN_BOX);
515
706
  };
516
707
  for (let i = 0; i < segs.length; i++) {
517
708
  const seg = segs[i];
518
709
  if (seg.kind === 'space') {
519
- x += useJustify && !seg.autospace ? justifiedSpaceWidth : seg.width;
710
+ // The space as text, for copying (#403): a Han–Latin gap only where
711
+ // the author typed one (its `text`).
712
+ const gap = useJustify && !seg.autospace ? justifiedSpaceWidth : seg.width;
713
+ parts.push(links.space(segs[i + 1] && segmentHref(segs[i + 1])));
714
+ parts.push(copyTextHtml(seg.text, x, gap - seg.width));
715
+ x += gap;
520
716
  continue;
521
717
  }
522
718
  parts.push(links.at(segmentHref(seg)));
@@ -531,7 +727,7 @@ function renderComposedSegments(line, block, targets) {
531
727
  continue;
532
728
  }
533
729
  if (seg.chip) {
534
- parts.push(renderChip(seg.chip, x, line.baseline - line.bbox.y, quoteFontString(block.fontString), block.color, (run) => pickSegmentColor({ kind: 'text', text: run.text, width: run.width, bold: run.bold, italic: run.italic }, block)));
730
+ parts.push(renderChip(seg.chip, x, line.baseline - line.bbox.y, quoteFontString(block.fontString), block.color, (run) => pickSegmentColor({ kind: 'text', text: run.text, width: run.width, bold: run.bold, italic: run.italic }, block), rootDir));
535
731
  x += seg.width;
536
732
  continue;
537
733
  }
@@ -555,6 +751,7 @@ function renderComposedSegments(line, block, targets) {
555
751
  x += seg.width;
556
752
  }
557
753
  parts.push(links.end());
754
+ parts.push(lineEndHtml(end, x));
558
755
  // Emphasis dots, proper-name and book-title lines (#193).
559
756
  if (line.marks)
560
757
  parts.push(lineMarksHtml(line, block.color));
@@ -574,7 +771,9 @@ function renderSwatch(x, baselineOffset, side, fill, ink) {
574
771
  * gap margin), then the text runs as ordinary text spans on the line's
575
772
  * baseline (`baselineOffset` from the line top), so the words stay real,
576
773
  * selectable text. `lineFont` / `lineColor` are the line box's own. */
577
- function renderChip(chip, x, baselineOffset, lineFont, lineColor, inkFor) {
774
+ function renderChip(chip, x, baselineOffset, lineFont, lineColor, inkFor,
775
+ /** The direction the document's root declares (see {@link dirAttr}). */
776
+ rootDir = 'ltr') {
578
777
  const bx = x + chip.marginLeft;
579
778
  const parts = [];
580
779
  if (chip.background || (chip.borderColor && chip.borderWidth > 0)) {
@@ -586,19 +785,27 @@ function renderChip(chip, x, baselineOffset, lineFont, lineColor, inkFor) {
586
785
  `"></span>`);
587
786
  }
588
787
  let tx = bx + chip.borderWidth + chip.paddingX;
589
- for (const run of chip.runs) {
788
+ // The runs in the chip's paint order (`VDTChip.order`); a right-to-left
789
+ // run's box reads right to left.
790
+ const order = chip.order && chip.order.length === chip.runs.length ? chip.order : undefined;
791
+ for (let k = 0; k < chip.runs.length; k++) {
792
+ const run = chip.runs[order ? order[k] : k];
590
793
  const font = quoteFontString(run.fontString);
591
794
  const color = chip.color ?? inkFor(run);
592
795
  const fontDecl = font !== lineFont ? `font:${font};` : '';
593
796
  const colorDecl = color !== lineColor ? `color:${color};` : '';
594
797
  const top = run.baselineShift ? `${run.baselineShift.toFixed(3)}px` : '0';
595
798
  // A chip's runs were measured one by one, as a whole each.
596
- parts.push(renderTextSegment({ kind: 'text', text: run.text, width: run.width }, tx, top, fontDecl, colorDecl, color, 0, hasCJK(run.text) ? 'own' : false));
799
+ // A run whose direction differs from what it inherits says so, so its
800
+ // neutral characters are ordered and its brackets mirrored as the
801
+ // engine resolved them.
802
+ const dir = dirAttr(run.rtl ? 'rtl' : 'ltr', rootDir);
803
+ parts.push(renderTextSegment({ kind: 'text', text: run.text, width: run.width }, tx, top, fontDecl, colorDecl, color, 0, hasCJK(run.text) ? 'own' : false, dir ? { attrs: dir, decl: '' } : PLAIN_BOX));
597
804
  tx += run.width;
598
805
  }
599
806
  return parts.join('');
600
807
  }
601
- function renderBullet(block) {
808
+ function renderBullet(block, rootDir = 'ltr') {
602
809
  if (block.type !== 'listItem' ||
603
810
  !block.bulletText ||
604
811
  block.bulletOffsetX === undefined) {
@@ -622,7 +829,10 @@ function renderBullet(block) {
622
829
  const onBaseline = block.bulletBaselineY !== undefined;
623
830
  const lineFont = quoteFontString(block.fontString);
624
831
  const baselineShift = onBaseline ? block.bulletBaselineY - firstLine.baseline : 0;
625
- const markerDiv = (cls, x, font, color, text) => `<div class="${cls}" aria-hidden="true" style="` +
832
+ // A marker reads in its item's direction: `١.` with its full stop on the
833
+ // left in an Arabic list (#379).
834
+ const markerDir = dirAttr(block.direction ?? rootDir, rootDir);
835
+ const markerDiv = (cls, x, font, color, text) => `<div class="${cls}" aria-hidden="true"${markerDir} style="` +
626
836
  `position:absolute;` +
627
837
  `left:${x}px;` +
628
838
  `top:${firstLine.bbox.y + baselineShift}px;` +
@@ -648,7 +858,9 @@ function renderBullet(block) {
648
858
  }
649
859
  return html;
650
860
  }
651
- function renderLine(line, block, targets) {
861
+ function renderLine(line, block, targets, rootDir = 'ltr', rootLang,
862
+ /** The block's line after this one (see {@link lineEndText}). */
863
+ next) {
652
864
  const font = quoteFontString(block.fontString);
653
865
  const strikethroughDecl = block.strikethroughText ? 'text-decoration:line-through;' : '';
654
866
  // Tracking — the block's (column balancing, a runt set short) and the
@@ -666,7 +878,7 @@ function renderLine(line, block, targets) {
666
878
  strikethroughDecl +
667
879
  trackingDecl +
668
880
  (lineCjkDecl(line) ? CJK_TEXT_DECL : '') +
669
- `">${renderSegments(line, block, targets)}</div>`);
881
+ `">${renderSegments(line, block, targets, rootDir, rootLang, lineEndText(line, next))}</div>`);
670
882
  }
671
883
  // ---------------------------------------------------------------------------
672
884
  // Vertical text (`VDTPage.flow`)
@@ -779,7 +991,7 @@ function verticalAnnotationRuns(runs, x, color, v, axisOf) {
779
991
  /** A body line of a vertical page: its text segments down an upright box,
780
992
  * its formulas, swatches and chips sideways where the canvas paints them
781
993
  * (see the section comment). */
782
- function renderVerticalLine(line, block, v, targets) {
994
+ function renderVerticalLine(line, block, v, targets, next) {
783
995
  const lineTracking = (block.letterSpacing ?? 0) + (line.letterSpacing ?? 0);
784
996
  const trailing = lineTrailingTracking(line, lineTracking);
785
997
  const lineIndent = line.bbox.x - block.bbox.x;
@@ -808,8 +1020,12 @@ function renderVerticalLine(line, block, v, targets) {
808
1020
  const justifiedSpaceWidth = useJustify ? (effectiveWidth - wordWidth) / spaceCount : 0;
809
1021
  const slack = Math.max(0, effectiveWidth - (contentWidth - trailing));
810
1022
  let x = block.textAlign === 'center' ? slack / 2 : block.textAlign === 'right' ? slack : 0;
1023
+ const spaceAxis = axisOf(block.fontString);
811
1024
  for (const seg of segs) {
812
1025
  if (seg.kind === 'space') {
1026
+ // The space as text, for copying (#403).
1027
+ if (seg.text)
1028
+ inner.push(verticalSpan(x, spaceAxis, esc(seg.text)));
813
1029
  x += useJustify && !seg.autospace ? justifiedSpaceWidth : seg.width;
814
1030
  continue;
815
1031
  }
@@ -868,6 +1084,9 @@ function renderVerticalLine(line, block, v, targets) {
868
1084
  // flow with the line's box.
869
1085
  if (line.marks)
870
1086
  sideways.push(verticalLineMarksHtml(line, block.color));
1087
+ const end = lineEndText(line, next);
1088
+ if (end)
1089
+ inner.push(verticalSpan(x, spaceAxis, esc(end), LINE_END_DECL));
871
1090
  const width = Math.max(line.bbox.width, effectiveWidth);
872
1091
  const decl = `font:${blockFont};color:${block.color};`
873
1092
  + (block.strikethroughText ? 'text-decoration:line-through;' : '')
@@ -952,7 +1171,13 @@ function pickResourceFont(seg, fonts) {
952
1171
  * Alignment and justification are baked into the measured geometry, so
953
1172
  * segments paint sequentially from the line origin — `:ref` segments in the
954
1173
  * link colour, `captionLabel` segments in the label colour. */
955
- function renderResourceLine(line, fonts, color, linkColor, labelColor = color, targets) {
1174
+ function renderResourceLine(line, fonts, color, linkColor, labelColor = color, targets,
1175
+ /** The direction the document's root declares (see {@link dirAttr}). */
1176
+ rootDir = 'ltr',
1177
+ /** The language the document's root declares. */
1178
+ rootLang,
1179
+ /** What a copy puts after the line (see {@link lineEndText}). */
1180
+ end = '') {
956
1181
  const baseFont = quoteFontString(fonts.normal);
957
1182
  const parts = [];
958
1183
  const lineDecl = lineCjkDecl(line);
@@ -960,6 +1185,10 @@ function renderResourceLine(line, fonts, color, linkColor, labelColor = color, t
960
1185
  const segs = line.segments;
961
1186
  const composed = line.cjkComposed === true;
962
1187
  const cjk = !composed && hasCJK(line.text);
1188
+ // A caption or cell line with right-to-left runs, or on a mirrored
1189
+ // page, advances through its segments in `order` (#379).
1190
+ const at = line.order && line.order.length === segs.length ? orderedOffsets(segs, line.order, 0, undefined) : undefined;
1191
+ const tracking = line.letterSpacing ?? 0;
963
1192
  const segColorOf = (seg) => (seg.refResourceId !== undefined
964
1193
  ? linkColor
965
1194
  : seg.captionLabel
@@ -971,17 +1200,24 @@ function renderResourceLine(line, fonts, color, linkColor, labelColor = color, t
971
1200
  const fontDecl = font !== baseFont ? `font:${font};` : '';
972
1201
  const colorDecl = segColor !== color ? `color:${segColor};` : '';
973
1202
  const top = seg.baselineShift ? `${seg.baselineShift.toFixed(3)}px` : '0';
1203
+ // Its direction, untracked joining letters and styled runs (#379).
1204
+ const box = segmentBox(seg, rootDir, rootLang, tracking, font, segColor, (probe) => ({ font: quoteFontString(pickResourceFont(probe, fonts)), color: segColor }));
974
1205
  // A line set word by word as every line was before the CJK
975
1206
  // features (see `renderSegments`).
976
1207
  return composed
977
- ? renderTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, segColor, line.letterSpacing ?? 0, segmentCjk(seg, lineDecl))
978
- : renderWordTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, segColor, cjk && hasCJK(seg.text));
1208
+ ? renderTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, segColor, line.letterSpacing ?? 0, segmentCjk(seg, lineDecl), box)
1209
+ : renderWordTextSegment(inLink ? { ...seg, refResourceId: undefined } : seg, at, top, fontDecl, colorDecl, segColor, cjk && hasCJK(seg.text), box);
979
1210
  };
980
1211
  const links = linkRuns();
981
1212
  let x = 0;
982
1213
  for (let i = 0; i < segs.length; i++) {
983
1214
  const seg = segs[i];
1215
+ if (at)
1216
+ x = at[i];
984
1217
  if (seg.kind === 'space') {
1218
+ // The space as text, for copying (#403).
1219
+ parts.push(links.space(segs[i + 1] && segmentHref(segs[i + 1])));
1220
+ parts.push(copyTextHtml(seg.text, x));
985
1221
  x += seg.width;
986
1222
  continue;
987
1223
  }
@@ -992,7 +1228,7 @@ function renderResourceLine(line, fonts, color, linkColor, labelColor = color, t
992
1228
  continue;
993
1229
  }
994
1230
  if (seg.chip) {
995
- parts.push(renderChip(seg.chip, x, line.baseline - line.bbox.y, baseFont, color, () => color));
1231
+ parts.push(renderChip(seg.chip, x, line.baseline - line.bbox.y, baseFont, color, () => color, rootDir));
996
1232
  x += seg.width;
997
1233
  continue;
998
1234
  }
@@ -1007,9 +1243,11 @@ function renderResourceLine(line, fonts, color, linkColor, labelColor = color, t
1007
1243
  x += seg.width;
1008
1244
  }
1009
1245
  parts.push(links.end());
1246
+ parts.push(lineEndHtml(end, x));
1010
1247
  }
1011
1248
  else {
1012
1249
  parts.push(`<span style="position:absolute;left:0;top:0;white-space:pre;">${esc(line.text)}</span>`);
1250
+ parts.push(lineEndHtml(end, line.bbox.width));
1013
1251
  }
1014
1252
  return (`<div class="pt-line" style="` +
1015
1253
  `position:absolute;` +
@@ -1134,9 +1372,9 @@ function renderResourceTable(rb, bx, by, options) {
1134
1372
  for (const cell of t.cells) {
1135
1373
  const fonts = cell.isHeader ? headerFonts : bodyFonts;
1136
1374
  const color = cell.isHeader ? t.headerColor : t.color;
1137
- for (const line of cell.lines) {
1138
- parts.push(renderResourceLine(line, fonts, color, rb.linkColor, color, options.linkTargets));
1139
- }
1375
+ cell.lines.forEach((line, i) => {
1376
+ parts.push(renderResourceLine(line, fonts, color, rb.linkColor, color, options.linkTargets, options.dir, options.rootLang, lineEndText(line, cell.lines[i + 1])));
1377
+ });
1140
1378
  }
1141
1379
  return parts.join('');
1142
1380
  }
@@ -1183,18 +1421,19 @@ function renderResourceBlockHtml(block, paint) {
1183
1421
  italic: rb.captionItalicFontString,
1184
1422
  boldItalic: rb.captionBoldItalicFontString,
1185
1423
  };
1186
- for (const line of rb.captionLines) {
1187
- parts.push(renderResourceLine(line, captionFonts, rb.captionColor, rb.linkColor, rb.captionLabelColor, options.linkTargets));
1188
- }
1424
+ rb.captionLines.forEach((line, i) => {
1425
+ parts.push(renderResourceLine(line, captionFonts, rb.captionColor, rb.linkColor, rb.captionLabelColor, options.linkTargets, options.dir, options.rootLang, lineEndText(line, rb.captionLines[i + 1])));
1426
+ });
1189
1427
  const noteFonts = {
1190
1428
  normal: rb.noteFontString,
1191
1429
  bold: rb.noteBoldFontString,
1192
1430
  italic: rb.noteItalicFontString,
1193
1431
  boldItalic: rb.noteBoldItalicFontString,
1194
1432
  };
1195
- for (const line of [...rb.noteLines, ...(rb.continuesLines ?? [])]) {
1196
- parts.push(renderResourceLine(line, noteFonts, rb.noteColor, rb.linkColor, rb.noteColor, options.linkTargets));
1197
- }
1433
+ const noteLines = [...rb.noteLines, ...(rb.continuesLines ?? [])];
1434
+ noteLines.forEach((line, i) => {
1435
+ parts.push(renderResourceLine(line, noteFonts, rb.noteColor, rb.linkColor, rb.noteColor, options.linkTargets, options.dir, options.rootLang, lineEndText(line, noteLines[i + 1])));
1436
+ });
1198
1437
  if (!rot)
1199
1438
  return anchor + parts.join('');
1200
1439
  return (anchor +
@@ -1262,7 +1501,11 @@ function renderDesignTextBlock(block, options) {
1262
1501
  // A justified line: its word spaces widened as the canvas and the PDF
1263
1502
  // advance its runs (EF-109).
1264
1503
  const wordSpacingDecl = line.wordSpacingPx ? `word-spacing:${line.wordSpacingPx.toFixed(3)}px;` : '';
1265
- lineParts.push(`<span style="` +
1504
+ // A right-to-left text: the line box reads at that base direction, and
1505
+ // the browser orders its runs (written in logical order) as the engine
1506
+ // did (`VDTDesignTextLine.order`), which keeps copied text logical.
1507
+ const lineDir = dirAttr(block.direction === 'rtl' ? 'rtl' : 'ltr', options?.dir ?? 'ltr');
1508
+ lineParts.push(`<span${lineDir} style="` +
1266
1509
  `position:absolute;` +
1267
1510
  `left:${line.xOffset.toFixed(3)}px;` +
1268
1511
  `top:${top.toFixed(3)}px;` +
@@ -1402,10 +1645,11 @@ function renderBlockInner(block, options) {
1402
1645
  return renderResourceBlockHtml(block, options);
1403
1646
  const parts = [];
1404
1647
  const v = options.vertical;
1405
- parts.push(v ? renderVerticalBullet(block, v) : renderBullet(block));
1406
- for (const line of block.lines) {
1407
- parts.push(v ? renderVerticalLine(line, block, v, options.linkTargets) : renderLine(line, block, options.linkTargets));
1408
- }
1648
+ parts.push(v ? renderVerticalBullet(block, v) : renderBullet(block, options.dir));
1649
+ block.lines.forEach((line, i) => {
1650
+ const next = block.lines[i + 1];
1651
+ parts.push(v ? renderVerticalLine(line, block, v, options.linkTargets, next) : renderLine(line, block, options.linkTargets, options.dir, options.rootLang, next));
1652
+ });
1409
1653
  return parts.join('');
1410
1654
  }
1411
1655
  /**
@@ -1449,8 +1693,9 @@ verticalRegion, verticalDigits) {
1449
1693
  ? { ...pageOptions, ink: { id: `pt-ink-${ink.hex.replace(/[^0-9a-z]/gi, '')}-${page.index}`, matrix: ink.matrix } }
1450
1694
  : pageOptions;
1451
1695
  // A vertical page's flow sets its text down the column.
1452
- const options = page.flow
1453
- ? { ...inked, vertical: { region: verticalRegion ?? 'mainland', uprightDigits: verticalDigits ?? 2, ...(page.flow.centralBaselines ? { axes: page.flow.centralBaselines } : {}), ...(page.flow.dashAdvances ? { dashes: page.flow.dashAdvances } : {}) } }
1696
+ const vflow = verticalFlowOf(page);
1697
+ const options = vflow
1698
+ ? { ...inked, vertical: { region: verticalRegion ?? 'mainland', uprightDigits: verticalDigits ?? 2, ...(vflow.centralBaselines ? { axes: vflow.centralBaselines } : {}), ...(vflow.dashAdvances ? { dashes: vflow.dashAdvances } : {}) } }
1454
1699
  : inked;
1455
1700
  const blocks = [];
1456
1701
  for (const col of page.columns) {
@@ -1480,7 +1725,7 @@ verticalRegion, verticalDigits) {
1480
1725
  const defsHtml = options.ink ? inkFilterDefs(options.ink) : '';
1481
1726
  // The separators above the footnotes of the columns.
1482
1727
  const footnoteRulesHtml = footnoteRuleSegments(page).map((r) => `<div class="pt-footnote-rule" style="position:absolute;left:${r.x}px;top:${r.y - r.lineWidthPx / 2}px;width:${r.width}px;height:${r.lineWidthPx}px;background:${r.color};"></div>`).join('');
1483
- const gridHtml = gridCells ? renderCharacterGridSvg(gridCells, page.flow ? page.height : page.width, page.flow ? page.width : page.height) : '';
1728
+ const gridHtml = gridCells ? renderCharacterGridSvg(gridCells, vflow ? page.height : page.width, vflow ? page.width : page.height) : '';
1484
1729
  const decorationHtml = defsHtml + gridHtml + openerHtml + footnoteRulesHtml + slotParts.join('');
1485
1730
  // A vertical page's flow: one box turned a quarter turn clockwise, its
1486
1731
  // text lines turned back and set vertically (see `renderVerticalLine`).
@@ -1489,10 +1734,19 @@ verticalRegion, verticalDigits) {
1489
1734
  .map((a) => zeroSizeAnchor(anchorElementId(a.id), a.x, a.y))
1490
1735
  .join('');
1491
1736
  const flowHtml = gridHtml + openerHtml + anchorsHtml + blocksHtml + footnoteRulesHtml;
1492
- const innerHtml = defsHtml + (page.flow
1737
+ // A right-to-left page's flow: one box turned over the sheet's vertical
1738
+ // axis, every text run and picture in it turned back about its own box
1739
+ // (`MIRRORED_FLOW_STYLE`), so the layout lands mirrored and reads as
1740
+ // written.
1741
+ const innerHtml = defsHtml + (vflow
1493
1742
  ? `<div class="pt-flow" style="position:absolute;left:0;top:0;width:${page.height}px;height:${page.width}px;transform:translate(${page.width}px,0) rotate(90deg);transform-origin:0 0;">${flowHtml}</div>`
1494
- : flowHtml) + slotParts.join('');
1495
- const outerHtml = `<div class="pt-page" id="${pageElementId((options.pageIndexOffset ?? 0) + page.index)}" data-page="${page.index}" style="` +
1743
+ : page.flow?.writingMode === 'horizontal-tb'
1744
+ ? `<div class="pt-flow pt-flow-mirrored" style="position:absolute;left:0;top:0;width:${page.width}px;height:${page.height}px;transform:scaleX(-1);transform-origin:${page.flow.mirror.originX / 2}px 0;">${MIRRORED_FLOW_STYLE}${flowHtml}</div>`
1745
+ : flowHtml) + slotParts.join('');
1746
+ // A right-to-left document's pages say so, with its language, for a host
1747
+ // that mounts them apart from the document's root (#379).
1748
+ const pageAttrs = options.dir === 'rtl' ? ` dir="rtl"${options.lang ? ` lang="${options.lang}"` : ''}` : '';
1749
+ const outerHtml = `<div class="pt-page" id="${pageElementId((options.pageIndexOffset ?? 0) + page.index)}" data-page="${page.index}"${pageAttrs} style="` +
1496
1750
  `position:relative;` +
1497
1751
  `width:${page.width}px;` +
1498
1752
  `height:${page.height}px;` +
@@ -1502,6 +1756,15 @@ verticalRegion, verticalDigits) {
1502
1756
  `">${innerHtml}</div>`;
1503
1757
  return { outerHtml, innerHtml, blocks, decorationHtml };
1504
1758
  }
1759
+ /** What turns the runs and pictures of a mirrored flow back (see
1760
+ * `VDTMirroredFlowFrame`): every text run of the output is an absolutely
1761
+ * positioned box set `white-space:pre` (line segments, plain lines, list
1762
+ * markers, design text lines), and pictures are `<img>` or inline `<svg>`
1763
+ * (formulas). Each turns about its own centre, so it stays where the
1764
+ * mirrored layout put it. A box that sets a transform of its own (a
1765
+ * stretched dash, a turned design picture) keeps it and stays mirrored. */
1766
+ const MIRRORED_FLOW_STYLE = '<style>.pt-flow-mirrored [style*="white-space:pre"],.pt-flow-mirrored img,' +
1767
+ '.pt-flow-mirrored svg:not(.pt-char-grid){transform:scaleX(-1);}</style>';
1505
1768
  /** The character grid (稿纸) as one SVG path over the page, under the
1506
1769
  * text (see `cjkGridCells`). */
1507
1770
  function renderCharacterGridSvg(cells, width, height) {
@@ -1528,7 +1791,10 @@ function renderCharacterGridSvg(cells, width, height) {
1528
1791
  * or a `line-height` on an ancestor would otherwise widen or change the
1529
1792
  * glyph runs and make lines overprint. The `.pt-doc` root carries them
1530
1793
  * before its own layout declarations; a host that mounts the pages'
1531
- * `innerHtml` in containers of its own sets them on its root.
1794
+ * `innerHtml` in containers of its own sets them on its root. The root of
1795
+ * a right-to-left document (`ResolvedConfig.direction`) sets
1796
+ * `direction:rtl` in their place, and `dir="rtl"`: such a host does the
1797
+ * same (#379).
1532
1798
  */
1533
1799
  export const HTML_TEXT_RESET = 'letter-spacing:normal;word-spacing:normal;text-transform:none;text-indent:0;' +
1534
1800
  'white-space:normal;word-break:normal;overflow-wrap:normal;' +
@@ -1549,9 +1815,16 @@ export function renderToHtmlIndexed(doc, options = {}) {
1549
1815
  const gap = options.columnGap ?? 24;
1550
1816
  const padding = options.padding ?? 24;
1551
1817
  const background = options.background ?? doc.config.page.backgroundColor.hex ?? 'transparent';
1552
- // A right-bound book lays its pages out right to left in a row.
1818
+ // A right-to-left document runs right to left from its root (#379): the
1819
+ // browser resolves the text of every box in that direction unless the
1820
+ // box declares its own (see `dirAttr`).
1821
+ const rtl = doc.config.direction === 'rtl';
1822
+ const docLang = renderLangOf(doc.config);
1823
+ // A right-bound book lays its pages out right to left in a row: a row
1824
+ // already runs that way in a right-to-left root.
1825
+ const reversed = (doc.binding === 'right') !== rtl;
1553
1826
  const docStyle = mode === 'multi'
1554
- ? `display:flex;flex-direction:${doc.binding === 'right' ? 'row-reverse' : 'row'};gap:${gap}px;align-items:flex-start;padding:${padding}px;box-sizing:border-box;width:max-content;`
1827
+ ? `display:flex;flex-direction:${reversed ? 'row-reverse' : 'row'};gap:${gap}px;align-items:flex-start;padding:${padding}px;box-sizing:border-box;width:max-content;`
1555
1828
  : `display:flex;flex-direction:column;align-items:center;padding:${padding}px 0;box-sizing:border-box;`;
1556
1829
  // Single-ink diagrams: SVG pictures are filtered to the ink when the host
1557
1830
  // asks (its URLs are not recoloured already).
@@ -1569,6 +1842,8 @@ export function renderToHtmlIndexed(doc, options = {}) {
1569
1842
  const anchorPaint = {
1570
1843
  ...(doc.anchors ? { anchors: doc.anchors } : {}),
1571
1844
  pageIndexOffset: doc.pageIndexOffset ?? 0,
1845
+ ...(docLang ? { rootLang: docLang } : {}),
1846
+ ...(rtl ? { dir: 'rtl', ...(docLang ? { lang: docLang } : {}) } : {}),
1572
1847
  };
1573
1848
  const bleedInset = doc.trimOffset > 0
1574
1849
  ? Math.max(0, doc.trimOffset - dimensionToPx(doc.config.page.cutLines.bleed, doc.config.page.dpi))
@@ -1601,9 +1876,10 @@ export function renderToHtmlIndexed(doc, options = {}) {
1601
1876
  }
1602
1877
  // The host's inherited text properties are reset first (EF-96). A
1603
1878
  // Chinese, Japanese or Korean document declares its language, so the
1604
- // browser picks the region's glyph forms (see `renderLangOf`).
1605
- const docLang = renderLangOf(doc.config);
1606
- const html = `<div class="pt-doc"${docLang ? ` lang="${docLang}"` : ''} data-mode="${mode}" style="${HTML_TEXT_RESET}${docStyle}">` +
1879
+ // browser picks the region's glyph forms, and so does a right-to-left one,
1880
+ // with its direction (see `renderLangOf`).
1881
+ const reset = rtl ? HTML_TEXT_RESET.replace('direction:ltr;', 'direction:rtl;') : HTML_TEXT_RESET;
1882
+ const html = `<div class="pt-doc"${docLang ? ` lang="${docLang}"` : ''}${rtl ? ' dir="rtl"' : ''} data-mode="${mode}" style="${reset}${docStyle}">` +
1607
1883
  pageHtmlParts.join('') +
1608
1884
  `</div>`;
1609
1885
  return { html, mode, pages: indexedPages };