postext 1.22.3 → 1.24.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 (427) hide show
  1. package/dist/__tests__/bundle/bitmapInfo.test.d.ts +2 -0
  2. package/dist/__tests__/bundle/bitmapInfo.test.d.ts.map +1 -0
  3. package/dist/__tests__/bundle/bitmapInfo.test.js +78 -0
  4. package/dist/__tests__/bundle/bitmapInfo.test.js.map +1 -0
  5. package/dist/__tests__/bundle/designOverflowPin.test.d.ts +2 -0
  6. package/dist/__tests__/bundle/designOverflowPin.test.d.ts.map +1 -0
  7. package/dist/__tests__/bundle/designOverflowPin.test.js +87 -0
  8. package/dist/__tests__/bundle/designOverflowPin.test.js.map +1 -0
  9. package/dist/__tests__/bundle.test.js +1 -1
  10. package/dist/__tests__/bundle.test.js.map +1 -1
  11. package/dist/__tests__/codeBlocks.test.d.ts +2 -0
  12. package/dist/__tests__/codeBlocks.test.d.ts.map +1 -0
  13. package/dist/__tests__/codeBlocks.test.js +550 -0
  14. package/dist/__tests__/codeBlocks.test.js.map +1 -0
  15. package/dist/__tests__/design/designTextOverflow.test.d.ts +2 -0
  16. package/dist/__tests__/design/designTextOverflow.test.d.ts.map +1 -0
  17. package/dist/__tests__/design/designTextOverflow.test.js +102 -0
  18. package/dist/__tests__/design/designTextOverflow.test.js.map +1 -0
  19. package/dist/__tests__/exports.test.js +30 -0
  20. package/dist/__tests__/exports.test.js.map +1 -1
  21. package/dist/__tests__/frontmatterInvalid.test.d.ts +2 -0
  22. package/dist/__tests__/frontmatterInvalid.test.d.ts.map +1 -0
  23. package/dist/__tests__/frontmatterInvalid.test.js +101 -0
  24. package/dist/__tests__/frontmatterInvalid.test.js.map +1 -0
  25. package/dist/__tests__/hardBreaks.test.d.ts +2 -0
  26. package/dist/__tests__/hardBreaks.test.d.ts.map +1 -0
  27. package/dist/__tests__/hardBreaks.test.js +226 -0
  28. package/dist/__tests__/hardBreaks.test.js.map +1 -0
  29. package/dist/__tests__/headingFirstLineIndent.test.d.ts +2 -0
  30. package/dist/__tests__/headingFirstLineIndent.test.d.ts.map +1 -0
  31. package/dist/__tests__/headingFirstLineIndent.test.js +185 -0
  32. package/dist/__tests__/headingFirstLineIndent.test.js.map +1 -0
  33. package/dist/__tests__/headingInlineMarks.test.js +1 -1
  34. package/dist/__tests__/headingInlineMarks.test.js.map +1 -1
  35. package/dist/__tests__/headingLetterSpacing.test.js +1 -1
  36. package/dist/__tests__/headingLetterSpacing.test.js.map +1 -1
  37. package/dist/__tests__/lineNumbers.test.d.ts +2 -0
  38. package/dist/__tests__/lineNumbers.test.d.ts.map +1 -0
  39. package/dist/__tests__/lineNumbers.test.js +304 -0
  40. package/dist/__tests__/lineNumbers.test.js.map +1 -0
  41. package/dist/__tests__/mathSizeMigration.test.js +2 -2
  42. package/dist/__tests__/mathSizeMigration.test.js.map +1 -1
  43. package/dist/__tests__/paragraphDropCap.test.d.ts +2 -0
  44. package/dist/__tests__/paragraphDropCap.test.d.ts.map +1 -0
  45. package/dist/__tests__/paragraphDropCap.test.js +442 -0
  46. package/dist/__tests__/paragraphDropCap.test.js.map +1 -0
  47. package/dist/__tests__/paragraphStyleIndent.test.js +26 -2
  48. package/dist/__tests__/paragraphStyleIndent.test.js.map +1 -1
  49. package/dist/__tests__/pipeline/bitmapResolution.test.d.ts +2 -0
  50. package/dist/__tests__/pipeline/bitmapResolution.test.d.ts.map +1 -0
  51. package/dist/__tests__/pipeline/bitmapResolution.test.js +139 -0
  52. package/dist/__tests__/pipeline/bitmapResolution.test.js.map +1 -0
  53. package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
  54. package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js.map +1 -1
  55. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
  56. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js.map +1 -1
  57. package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
  58. package/dist/__tests__/pipeline/colonListRoom.test.js.map +1 -1
  59. package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
  60. package/dist/__tests__/pipeline/compoundBreaks.test.js.map +1 -1
  61. package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
  62. package/dist/__tests__/pipeline/dashBreaks.test.js.map +1 -1
  63. package/dist/__tests__/pipeline/designTextTruncated.test.d.ts +2 -0
  64. package/dist/__tests__/pipeline/designTextTruncated.test.d.ts.map +1 -0
  65. package/dist/__tests__/pipeline/designTextTruncated.test.js +116 -0
  66. package/dist/__tests__/pipeline/designTextTruncated.test.js.map +1 -0
  67. package/dist/__tests__/pipeline/floatPlacement.test.js +3 -3
  68. package/dist/__tests__/pipeline/floatPlacement.test.js.map +1 -1
  69. package/dist/__tests__/pipeline/floatShrink.test.d.ts +2 -0
  70. package/dist/__tests__/pipeline/floatShrink.test.d.ts.map +1 -0
  71. package/dist/__tests__/pipeline/floatShrink.test.js +298 -0
  72. package/dist/__tests__/pipeline/floatShrink.test.js.map +1 -0
  73. package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
  74. package/dist/__tests__/pipeline/headingSplitOrphans.test.js.map +1 -1
  75. package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
  76. package/dist/__tests__/pipeline/inlineFigureSpace.test.js.map +1 -1
  77. package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
  78. package/dist/__tests__/pipeline/paragraphContainerTail.test.js.map +1 -1
  79. package/dist/__tests__/pipeline/paragraphStyleFaces.test.js +7 -1
  80. package/dist/__tests__/pipeline/paragraphStyleFaces.test.js.map +1 -1
  81. package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
  82. package/dist/__tests__/pipeline/raggedOptimal.test.js.map +1 -1
  83. package/dist/__tests__/pipeline/textWrap.test.d.ts +2 -0
  84. package/dist/__tests__/pipeline/textWrap.test.d.ts.map +1 -0
  85. package/dist/__tests__/pipeline/textWrap.test.js +453 -0
  86. package/dist/__tests__/pipeline/textWrap.test.js.map +1 -0
  87. package/dist/__tests__/preflight.test.js +30 -1
  88. package/dist/__tests__/preflight.test.js.map +1 -1
  89. package/dist/__tests__/presets/showcaseDesignOverflow.test.d.ts +2 -0
  90. package/dist/__tests__/presets/showcaseDesignOverflow.test.d.ts.map +1 -0
  91. package/dist/__tests__/presets/showcaseDesignOverflow.test.js +58 -0
  92. package/dist/__tests__/presets/showcaseDesignOverflow.test.js.map +1 -0
  93. package/dist/__tests__/tabStops.test.d.ts +2 -0
  94. package/dist/__tests__/tabStops.test.d.ts.map +1 -0
  95. package/dist/__tests__/tabStops.test.js +394 -0
  96. package/dist/__tests__/tabStops.test.js.map +1 -0
  97. package/dist/__tests__/table/booktabs.test.d.ts +2 -0
  98. package/dist/__tests__/table/booktabs.test.d.ts.map +1 -0
  99. package/dist/__tests__/table/booktabs.test.js +290 -0
  100. package/dist/__tests__/table/booktabs.test.js.map +1 -0
  101. package/dist/__tests__/textWrapMeasure.test.d.ts +2 -0
  102. package/dist/__tests__/textWrapMeasure.test.d.ts.map +1 -0
  103. package/dist/__tests__/textWrapMeasure.test.js +162 -0
  104. package/dist/__tests__/textWrapMeasure.test.js.map +1 -0
  105. package/dist/__tests__/verseLines.test.d.ts +2 -0
  106. package/dist/__tests__/verseLines.test.d.ts.map +1 -0
  107. package/dist/__tests__/verseLines.test.js +505 -0
  108. package/dist/__tests__/verseLines.test.js.map +1 -0
  109. package/dist/bitmapResolution.d.ts +42 -0
  110. package/dist/bitmapResolution.d.ts.map +1 -0
  111. package/dist/bitmapResolution.js +82 -0
  112. package/dist/bitmapResolution.js.map +1 -0
  113. package/dist/bundle/codec.d.ts +9 -2
  114. package/dist/bundle/codec.d.ts.map +1 -1
  115. package/dist/bundle/codec.js +24 -2
  116. package/dist/bundle/codec.js.map +1 -1
  117. package/dist/bundle/configVersion.d.ts +119 -8
  118. package/dist/bundle/configVersion.d.ts.map +1 -1
  119. package/dist/bundle/configVersion.js +395 -6
  120. package/dist/bundle/configVersion.js.map +1 -1
  121. package/dist/bundle/index.d.ts +3 -3
  122. package/dist/bundle/index.d.ts.map +1 -1
  123. package/dist/bundle/index.js +2 -2
  124. package/dist/bundle/index.js.map +1 -1
  125. package/dist/bundle/manifest.d.ts +12 -2
  126. package/dist/bundle/manifest.d.ts.map +1 -1
  127. package/dist/bundle/manifest.js +132 -6
  128. package/dist/bundle/manifest.js.map +1 -1
  129. package/dist/bundle/types.d.ts +28 -2
  130. package/dist/bundle/types.d.ts.map +1 -1
  131. package/dist/canvas-backend/blockRender.d.ts.map +1 -1
  132. package/dist/canvas-backend/blockRender.js +45 -9
  133. package/dist/canvas-backend/blockRender.js.map +1 -1
  134. package/dist/canvas-backend/index.d.ts.map +1 -1
  135. package/dist/canvas-backend/index.js +3 -0
  136. package/dist/canvas-backend/index.js.map +1 -1
  137. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  138. package/dist/canvas-backend/renderResourceBlock.js +15 -1
  139. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  140. package/dist/cjkMarks.js +1 -1
  141. package/dist/cjkMarks.js.map +1 -1
  142. package/dist/code/highlight.d.ts +34 -0
  143. package/dist/code/highlight.d.ts.map +1 -0
  144. package/dist/code/highlight.js +406 -0
  145. package/dist/code/highlight.js.map +1 -0
  146. package/dist/configWarnings.d.ts.map +1 -1
  147. package/dist/configWarnings.js +220 -4
  148. package/dist/configWarnings.js.map +1 -1
  149. package/dist/defaults/bodyText.d.ts.map +1 -1
  150. package/dist/defaults/bodyText.js +25 -0
  151. package/dist/defaults/bodyText.js.map +1 -1
  152. package/dist/defaults/calloutStyles.d.ts.map +1 -1
  153. package/dist/defaults/calloutStyles.js +7 -0
  154. package/dist/defaults/calloutStyles.js.map +1 -1
  155. package/dist/defaults/codeStyle.d.ts +29 -0
  156. package/dist/defaults/codeStyle.d.ts.map +1 -0
  157. package/dist/defaults/codeStyle.js +284 -0
  158. package/dist/defaults/codeStyle.js.map +1 -0
  159. package/dist/defaults/dropCap.d.ts +49 -0
  160. package/dist/defaults/dropCap.d.ts.map +1 -0
  161. package/dist/defaults/dropCap.js +66 -0
  162. package/dist/defaults/dropCap.js.map +1 -0
  163. package/dist/defaults/headerFooter.d.ts +21 -6
  164. package/dist/defaults/headerFooter.d.ts.map +1 -1
  165. package/dist/defaults/headerFooter.js +37 -14
  166. package/dist/defaults/headerFooter.js.map +1 -1
  167. package/dist/defaults/headings.d.ts +5 -4
  168. package/dist/defaults/headings.d.ts.map +1 -1
  169. package/dist/defaults/headings.js +26 -4
  170. package/dist/defaults/headings.js.map +1 -1
  171. package/dist/defaults/index.d.ts +4 -1
  172. package/dist/defaults/index.d.ts.map +1 -1
  173. package/dist/defaults/index.js +20 -1
  174. package/dist/defaults/index.js.map +1 -1
  175. package/dist/defaults/layout.d.ts +30 -1
  176. package/dist/defaults/layout.d.ts.map +1 -1
  177. package/dist/defaults/layout.js +105 -1
  178. package/dist/defaults/layout.js.map +1 -1
  179. package/dist/defaults/lineNumbers.d.ts +19 -0
  180. package/dist/defaults/lineNumbers.d.ts.map +1 -0
  181. package/dist/defaults/lineNumbers.js +101 -0
  182. package/dist/defaults/lineNumbers.js.map +1 -0
  183. package/dist/defaults/paragraphStyles.d.ts.map +1 -1
  184. package/dist/defaults/paragraphStyles.js +20 -0
  185. package/dist/defaults/paragraphStyles.js.map +1 -1
  186. package/dist/defaults/parts.js +4 -4
  187. package/dist/defaults/parts.js.map +1 -1
  188. package/dist/defaults/tabStops.d.ts +29 -0
  189. package/dist/defaults/tabStops.d.ts.map +1 -0
  190. package/dist/defaults/tabStops.js +94 -0
  191. package/dist/defaults/tabStops.js.map +1 -0
  192. package/dist/defaults/tableStyle.d.ts.map +1 -1
  193. package/dist/defaults/tableStyle.js +44 -0
  194. package/dist/defaults/tableStyle.js.map +1 -1
  195. package/dist/defaults/toc.js +2 -2
  196. package/dist/defaults/verse.d.ts +11 -0
  197. package/dist/defaults/verse.d.ts.map +1 -0
  198. package/dist/defaults/verse.js +57 -0
  199. package/dist/defaults/verse.js.map +1 -0
  200. package/dist/design/layout.d.ts +15 -1
  201. package/dist/design/layout.d.ts.map +1 -1
  202. package/dist/design/layout.js +28 -4
  203. package/dist/design/layout.js.map +1 -1
  204. package/dist/fonts/usage.d.ts +7 -0
  205. package/dist/fonts/usage.d.ts.map +1 -1
  206. package/dist/fonts/usage.js +71 -0
  207. package/dist/fonts/usage.js.map +1 -1
  208. package/dist/frontmatter.d.ts +20 -0
  209. package/dist/frontmatter.d.ts.map +1 -1
  210. package/dist/frontmatter.js +69 -3
  211. package/dist/frontmatter.js.map +1 -1
  212. package/dist/html-backend.d.ts.map +1 -1
  213. package/dist/html-backend.js +107 -15
  214. package/dist/html-backend.js.map +1 -1
  215. package/dist/index.d.ts +18 -10
  216. package/dist/index.d.ts.map +1 -1
  217. package/dist/index.js +9 -4
  218. package/dist/index.js.map +1 -1
  219. package/dist/measure/bidiLines.d.ts +4 -3
  220. package/dist/measure/bidiLines.d.ts.map +1 -1
  221. package/dist/measure/bidiLines.js +14 -9
  222. package/dist/measure/bidiLines.js.map +1 -1
  223. package/dist/measure/cache.js +2 -2
  224. package/dist/measure/cache.js.map +1 -1
  225. package/dist/measure/cjkCompose.d.ts.map +1 -1
  226. package/dist/measure/cjkCompose.js +10 -6
  227. package/dist/measure/cjkCompose.js.map +1 -1
  228. package/dist/measure/hardBreaks.d.ts +46 -0
  229. package/dist/measure/hardBreaks.d.ts.map +1 -0
  230. package/dist/measure/hardBreaks.js +228 -0
  231. package/dist/measure/hardBreaks.js.map +1 -0
  232. package/dist/measure/index.d.ts +4 -3
  233. package/dist/measure/index.d.ts.map +1 -1
  234. package/dist/measure/index.js +5 -4
  235. package/dist/measure/index.js.map +1 -1
  236. package/dist/measure/leader.d.ts +20 -0
  237. package/dist/measure/leader.d.ts.map +1 -0
  238. package/dist/measure/leader.js +46 -0
  239. package/dist/measure/leader.js.map +1 -0
  240. package/dist/measure/links.js +1 -1
  241. package/dist/measure/links.js.map +1 -1
  242. package/dist/measure/plain.d.ts.map +1 -1
  243. package/dist/measure/plain.js +23 -26
  244. package/dist/measure/plain.js.map +1 -1
  245. package/dist/measure/rich.d.ts +19 -0
  246. package/dist/measure/rich.d.ts.map +1 -1
  247. package/dist/measure/rich.js +215 -39
  248. package/dist/measure/rich.js.map +1 -1
  249. package/dist/measure/tabs.d.ts +103 -0
  250. package/dist/measure/tabs.d.ts.map +1 -0
  251. package/dist/measure/tabs.js +123 -0
  252. package/dist/measure/tabs.js.map +1 -0
  253. package/dist/measure/types.d.ts +72 -2
  254. package/dist/measure/types.d.ts.map +1 -1
  255. package/dist/measure/types.js +87 -3
  256. package/dist/measure/types.js.map +1 -1
  257. package/dist/parse/blockParser.d.ts +13 -6
  258. package/dist/parse/blockParser.d.ts.map +1 -1
  259. package/dist/parse/blockParser.js +457 -87
  260. package/dist/parse/blockParser.js.map +1 -1
  261. package/dist/parse/codeFence.d.ts +18 -0
  262. package/dist/parse/codeFence.d.ts.map +1 -0
  263. package/dist/parse/codeFence.js +52 -0
  264. package/dist/parse/codeFence.js.map +1 -0
  265. package/dist/parse/index.d.ts +1 -1
  266. package/dist/parse/index.d.ts.map +1 -1
  267. package/dist/parse/index.js.map +1 -1
  268. package/dist/parse/indexMarks.d.ts +3 -1
  269. package/dist/parse/indexMarks.d.ts.map +1 -1
  270. package/dist/parse/indexMarks.js +22 -2
  271. package/dist/parse/indexMarks.js.map +1 -1
  272. package/dist/parse/inlineFormatting.d.ts +50 -0
  273. package/dist/parse/inlineFormatting.d.ts.map +1 -1
  274. package/dist/parse/inlineFormatting.js +94 -0
  275. package/dist/parse/inlineFormatting.js.map +1 -1
  276. package/dist/parse/sourceMapping.d.ts.map +1 -1
  277. package/dist/parse/sourceMapping.js +65 -1
  278. package/dist/parse/sourceMapping.js.map +1 -1
  279. package/dist/parse/types.d.ts +144 -9
  280. package/dist/parse/types.d.ts.map +1 -1
  281. package/dist/pipeline/build.d.ts.map +1 -1
  282. package/dist/pipeline/build.js +629 -47
  283. package/dist/pipeline/build.js.map +1 -1
  284. package/dist/pipeline/buildBlockKind.d.ts +5 -0
  285. package/dist/pipeline/buildBlockKind.d.ts.map +1 -1
  286. package/dist/pipeline/buildBlockKind.js +74 -2
  287. package/dist/pipeline/buildBlockKind.js.map +1 -1
  288. package/dist/pipeline/buildHelpers.d.ts +3 -0
  289. package/dist/pipeline/buildHelpers.d.ts.map +1 -1
  290. package/dist/pipeline/buildHelpers.js +7 -2
  291. package/dist/pipeline/buildHelpers.js.map +1 -1
  292. package/dist/pipeline/buildMeasurement.js +1 -1
  293. package/dist/pipeline/buildMeasurement.js.map +1 -1
  294. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  295. package/dist/pipeline/calloutLayout.js +33 -2
  296. package/dist/pipeline/calloutLayout.js.map +1 -1
  297. package/dist/pipeline/chips.d.ts.map +1 -1
  298. package/dist/pipeline/chips.js +4 -0
  299. package/dist/pipeline/chips.js.map +1 -1
  300. package/dist/pipeline/codeBlocks.d.ts +38 -0
  301. package/dist/pipeline/codeBlocks.d.ts.map +1 -0
  302. package/dist/pipeline/codeBlocks.js +146 -0
  303. package/dist/pipeline/codeBlocks.js.map +1 -0
  304. package/dist/pipeline/codeInline.d.ts +22 -0
  305. package/dist/pipeline/codeInline.d.ts.map +1 -0
  306. package/dist/pipeline/codeInline.js +93 -0
  307. package/dist/pipeline/codeInline.js.map +1 -0
  308. package/dist/pipeline/codeLines.d.ts +91 -0
  309. package/dist/pipeline/codeLines.d.ts.map +1 -0
  310. package/dist/pipeline/codeLines.js +399 -0
  311. package/dist/pipeline/codeLines.js.map +1 -0
  312. package/dist/pipeline/columnBalancing.d.ts.map +1 -1
  313. package/dist/pipeline/columnBalancing.js +5 -0
  314. package/dist/pipeline/columnBalancing.js.map +1 -1
  315. package/dist/pipeline/config.d.ts.map +1 -1
  316. package/dist/pipeline/config.js +8 -1
  317. package/dist/pipeline/config.js.map +1 -1
  318. package/dist/pipeline/contentWarnings.d.ts +25 -0
  319. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  320. package/dist/pipeline/contentWarnings.js +173 -19
  321. package/dist/pipeline/contentWarnings.js.map +1 -1
  322. package/dist/pipeline/continuation.d.ts.map +1 -1
  323. package/dist/pipeline/continuation.js +9 -2
  324. package/dist/pipeline/continuation.js.map +1 -1
  325. package/dist/pipeline/dropCap.d.ts +128 -0
  326. package/dist/pipeline/dropCap.d.ts.map +1 -0
  327. package/dist/pipeline/dropCap.js +412 -0
  328. package/dist/pipeline/dropCap.js.map +1 -0
  329. package/dist/pipeline/floatPlacement.d.ts +34 -4
  330. package/dist/pipeline/floatPlacement.d.ts.map +1 -1
  331. package/dist/pipeline/floatPlacement.js +35 -5
  332. package/dist/pipeline/floatPlacement.js.map +1 -1
  333. package/dist/pipeline/floatSlots.d.ts +7 -0
  334. package/dist/pipeline/floatSlots.d.ts.map +1 -1
  335. package/dist/pipeline/floatSlots.js.map +1 -1
  336. package/dist/pipeline/hardBreaks.d.ts +19 -0
  337. package/dist/pipeline/hardBreaks.d.ts.map +1 -0
  338. package/dist/pipeline/hardBreaks.js +36 -0
  339. package/dist/pipeline/hardBreaks.js.map +1 -0
  340. package/dist/pipeline/headerFooter.d.ts +4 -1
  341. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  342. package/dist/pipeline/headerFooter.js +23 -6
  343. package/dist/pipeline/headerFooter.js.map +1 -1
  344. package/dist/pipeline/lineNumbers.d.ts +37 -0
  345. package/dist/pipeline/lineNumbers.d.ts.map +1 -0
  346. package/dist/pipeline/lineNumbers.js +441 -0
  347. package/dist/pipeline/lineNumbers.js.map +1 -0
  348. package/dist/pipeline/measureContentBlock.d.ts +38 -5
  349. package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
  350. package/dist/pipeline/measureContentBlock.js +284 -69
  351. package/dist/pipeline/measureContentBlock.js.map +1 -1
  352. package/dist/pipeline/partPalette.d.ts +1 -1
  353. package/dist/pipeline/partPalette.d.ts.map +1 -1
  354. package/dist/pipeline/partPalette.js +24 -2
  355. package/dist/pipeline/partPalette.js.map +1 -1
  356. package/dist/pipeline/placement.d.ts.map +1 -1
  357. package/dist/pipeline/placement.js +7 -0
  358. package/dist/pipeline/placement.js.map +1 -1
  359. package/dist/pipeline/resourceLayout.d.ts +15 -0
  360. package/dist/pipeline/resourceLayout.d.ts.map +1 -1
  361. package/dist/pipeline/resourceLayout.js +95 -15
  362. package/dist/pipeline/resourceLayout.js.map +1 -1
  363. package/dist/pipeline/styles.d.ts +18 -2
  364. package/dist/pipeline/styles.d.ts.map +1 -1
  365. package/dist/pipeline/styles.js +23 -8
  366. package/dist/pipeline/styles.js.map +1 -1
  367. package/dist/pipeline/tabs.d.ts +35 -0
  368. package/dist/pipeline/tabs.d.ts.map +1 -0
  369. package/dist/pipeline/tabs.js +99 -0
  370. package/dist/pipeline/tabs.js.map +1 -0
  371. package/dist/pipeline/textWrap.d.ts +94 -0
  372. package/dist/pipeline/textWrap.d.ts.map +1 -0
  373. package/dist/pipeline/textWrap.js +188 -0
  374. package/dist/pipeline/textWrap.js.map +1 -0
  375. package/dist/pipeline/toc.d.ts.map +1 -1
  376. package/dist/pipeline/toc.js +3 -38
  377. package/dist/pipeline/toc.js.map +1 -1
  378. package/dist/pipeline/verseLines.d.ts +98 -0
  379. package/dist/pipeline/verseLines.d.ts.map +1 -0
  380. package/dist/pipeline/verseLines.js +293 -0
  381. package/dist/pipeline/verseLines.js.map +1 -0
  382. package/dist/preflight.d.ts +46 -1
  383. package/dist/preflight.d.ts.map +1 -1
  384. package/dist/preflight.js +43 -7
  385. package/dist/preflight.js.map +1 -1
  386. package/dist/table/booktabs.d.ts +37 -0
  387. package/dist/table/booktabs.d.ts.map +1 -0
  388. package/dist/table/booktabs.js +82 -0
  389. package/dist/table/booktabs.js.map +1 -0
  390. package/dist/types.d.ts +787 -12
  391. package/dist/types.d.ts.map +1 -1
  392. package/dist/vdt.d.ts +367 -9
  393. package/dist/vdt.d.ts.map +1 -1
  394. package/dist/vdt.js +9 -2
  395. package/dist/vdt.js.map +1 -1
  396. package/dist/word/docxRead.d.ts.map +1 -1
  397. package/dist/word/docxRead.js +15 -0
  398. package/dist/word/docxRead.js.map +1 -1
  399. package/dist/word/index.d.ts +1 -1
  400. package/dist/word/index.d.ts.map +1 -1
  401. package/dist/word/index.js +1 -1
  402. package/dist/word/index.js.map +1 -1
  403. package/dist/word/inline.d.ts +6 -1
  404. package/dist/word/inline.d.ts.map +1 -1
  405. package/dist/word/inline.js +35 -1
  406. package/dist/word/inline.js.map +1 -1
  407. package/dist/word/model.d.ts +14 -0
  408. package/dist/word/model.d.ts.map +1 -1
  409. package/dist/word/model.js.map +1 -1
  410. package/dist/word/template.d.ts +12 -3
  411. package/dist/word/template.d.ts.map +1 -1
  412. package/dist/word/template.js +13 -3
  413. package/dist/word/template.js.map +1 -1
  414. package/dist/word/toDocx.d.ts.map +1 -1
  415. package/dist/word/toDocx.js +52 -8
  416. package/dist/word/toDocx.js.map +1 -1
  417. package/dist/word/toMarkdown.d.ts +15 -1
  418. package/dist/word/toMarkdown.d.ts.map +1 -1
  419. package/dist/word/toMarkdown.js +133 -7
  420. package/dist/word/toMarkdown.js.map +1 -1
  421. package/dist/word/word.test.js +124 -2
  422. package/dist/word/word.test.js.map +1 -1
  423. package/dist/worker/fonts.d.ts +2 -1
  424. package/dist/worker/fonts.d.ts.map +1 -1
  425. package/dist/worker/fonts.js +6 -2
  426. package/dist/worker/fonts.js.map +1 -1
  427. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { withBitmapResolutions } from '../bitmapResolution';
1
2
  import { plainTitleBreaks } from '../parse/inlineFormatting';
2
3
  import { dimensionToPx } from '../units';
3
4
  import { createVDTDocument, createVDTBlock, createBoundingBox, createVDTColumn, pageIsMirrored, pageIsVertical, flowRectToPage, } from '../vdt';
@@ -13,6 +14,7 @@ import { getMeasureRegion, getMeasureUprightDigits, getMeasureWritingMode, setMe
13
14
  import { stampCentralBaselines } from './verticalMetrics';
14
15
  import { orientLinesToFrames } from './mirrorFrame';
15
16
  import { verseMarginTopPx } from './buildBlockKind';
17
+ import { verseLinesSettings, versesLineByLine } from './verseLines';
16
18
  import { resolveAllConfig, computeBaselineGrid, resolvedDirection, resolvedLocale } from './config';
17
19
  import { asciiDigits } from '../arabicNumerals';
18
20
  import { getMeasureDirection, setMeasureDirection, shiftLineX } from '../measure/bidiLines';
@@ -41,26 +43,34 @@ import { planParts, derivePartMeasureContext } from './parts';
41
43
  import { layoutCallout, offsetBlock, offsetCalloutToAbsolute, pickCalloutStyle, planCallouts, resolveCalloutAttrs, } from './calloutLayout';
42
44
  import { layoutCaptionText, layoutResourceBlock, planTableSlice } from './resourceLayout';
43
45
  import { computeFloatPlan, floatedResourceIds, floatColumnCount, } from './floatPlacement';
44
- import { enumerateCurrentPageSlots, measureFloatBand, floatGapAbove, measureSideStack, clearSideObstacles, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
46
+ import { enumerateCurrentPageSlots, measureFloatBand, floatGapAbove, floatGapBelow, measureSideStack, clearSideObstacles, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
45
47
  import { computeHeadingContext, computeResourceNumberingState, } from './resourceNumbering';
46
48
  import { documentLocale, effectiveResourceTypes } from '../defaults/resourceTypes';
47
49
  import { resolveCalloutStylesConfig } from '../defaults/calloutStyles';
50
+ import { codeBoxStyle, codeParseOptions, wrapCodeBlocks } from './codeBlocks';
51
+ import { buildCodeLineNumbers, codeOverflowWarnings } from './codeLines';
48
52
  import { pickTableStyle } from '../defaults/tableStyle';
53
+ import { DEFAULT_FLOAT_MIN_SCALE } from '../defaults/layout';
54
+ import { buildLineNumbers, lineNumberWarnings } from './lineNumbers';
49
55
  import { buildHeadersAndFooters, defaultOpenerTitle, headingDesignBoxes, measureDefaultOpenerHeight, measureHeadingDesign } from './headerFooter';
50
56
  import { flowColorValues } from './partPalette';
51
57
  import { chapterNumberCounter, leadingBoldText } from './placeholders';
52
58
  import { proposeBalanceLines, collectColumnGaps, firstDivergentColumn, gapLinesIn, boxRoomIn, boxLeverKeys, pageSegments, MAX_BALANCING_PASSES, MAX_BALANCING_PASSES_PER_DOCUMENT, balanceKey, candidateKey, flexFigureKey, flexFloatKey } from './columnBalancing';
53
59
  import { applyBandCap, uncapBand, columnBottom, bandCapLines, bandTop, bareBandTop, resolveBandCapsGen, drainPasses, resolveTrailingCapsGen, bandCapLinesAroundZone, } from './bandCaps';
54
60
  import { raggedLooseLines } from './raggedLines';
55
- import { cjkLooseLineWarnings, collectContentWarnings, joiningLetterSpacingWarnings, locateContentWarnings, wordOverflowWarnings } from './contentWarnings';
61
+ import { cjkLooseLineWarnings, collectContentWarnings, designTruncationWarnings, dropCapWarnings, floatShrinkWarnings, joiningLetterSpacingWarnings, locateContentWarnings, wordOverflowWarnings } from './contentWarnings';
62
+ import { dropCapSinkAfter } from './dropCap';
56
63
  import { mostlyJoiningScript } from '../measure/joining';
57
64
  import { annotateDocument } from '../cjkMarks';
58
65
  import { annotateArabicMarks } from '../arabicMarks';
66
+ import { literalBreaksFor } from './hardBreaks';
59
67
  import { tashkilFor } from './tashkil';
60
68
  import { overlineEmphasis } from '../emphasisOverline';
61
69
  import { withBookTitleBrackets } from './annotations';
62
70
  import { paperByBlock, stampPagePaper } from './paper';
63
71
  import { headingLineSpanBand } from './lineSpan';
72
+ import { columnCursorY, insetsBefore, minTextWidthPx, openColumnWrap, resolveCalloutWrap, resolveResourceWrap, runsBesideWrap, sameInsetsFrom, settleColumnWraps, transparentToWrap, wrapGapPx, wrapInsetSteps } from './textWrap';
73
+ import { lineInsetsAt } from '../measure/types';
64
74
  /** Tolerance for "does this block fit" checks against a column's free
65
75
  * height, absorbing floating-point drift between grid multiples. */
66
76
  const FIT_EPS = 0.01;
@@ -142,6 +152,12 @@ function measureLooseParagraph(rawBlock, blockIdx, columnWidth, ctx, styleOverri
142
152
  const base = measureContentBlock(rawBlock, blockIdx, columnWidth, ctx, { styleOverride });
143
153
  if (!base)
144
154
  return null;
155
+ // A paragraph with a tab at a stop (#622) is set line by line, as
156
+ // measured: neither looseness nor tracking moves its stops' text.
157
+ if (base.measured.lines.some((l) => l.tabbed)) {
158
+ looseOutcome.set(blockIdx, null);
159
+ return base;
160
+ }
145
161
  const target = base.measured.lines.length + extraLines;
146
162
  // The breaker's optimum, before any runt fix (a looseness of 0 skips it).
147
163
  const optimum = measureContentBlock(rawBlock, blockIdx, columnWidth, ctx, { styleOverride, looseness: 0 });
@@ -213,6 +229,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
213
229
  * shortened it, so what does not fit it may still fit a full column. */
214
230
  const shortSideColumn = (page, col) => col.bbox.height < page.contentArea.height - 0.5;
215
231
  const resolved = resolveAllConfig(config);
232
+ // Bitmaps sized by the document's `layout.bitmapResolution` carry it as
233
+ // their own `resolution` from here on (#631); under the default policy
234
+ // the content is untouched.
235
+ const stampedResources = content.resources && withBitmapResolutions(content.resources, resolved.layout.bitmapResolution);
236
+ if (stampedResources && stampedResources !== content.resources)
237
+ content = { ...content, resources: stampedResources };
216
238
  // Tracking rungs a loose paragraph may climb: none, then 5‰ steps up to
217
239
  // the cap (`headings.balancing.maxTracking`, thousandths of an em), the
218
240
  // cap itself always included. Only the smallest rung that gains the line
@@ -320,7 +342,11 @@ function placeDocumentPass(content, config, cache, options, hints) {
320
342
  // bibliography takes `:::bibliography`'s place or follows the text.
321
343
  // Arabic vowel marks out of the text when `bodyText.tashkil` says so
322
344
  // (#376), for the outline and the layout alike.
323
- const parsedBody = numberTitlesFor(headingMarksFor(tashkilFor(parseMarkdownMemo(markdownBody), resolved.bodyText.tashkil), resolved), resolved);
345
+ // A configuration stored before #620 prints the backslashes of forced
346
+ // line breaks (`bodyText.hardLineBreaks: false`).
347
+ // Code fences (#624) as `codeStyle` reads them: a configuration stored
348
+ // before #624 reads their lines as Markdown (`codeStyle.blocks: false`).
349
+ const parsedBody = numberTitlesFor(headingMarksFor(tashkilFor(literalBreaksFor(parseMarkdownMemo(markdownBody, codeParseOptions(resolved)), resolved), resolved.bodyText.tashkil), resolved), resolved);
324
350
  const citationContext = content.citations
325
351
  ?? (needsCitationContext(parsedBody, frontmatterMeta) ? bookCitationContexts([{ metadata: frontmatterMeta, blocks: parsedBody }], content.resources)[0] : undefined);
326
352
  const citationsApplied = citationContext
@@ -375,7 +401,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
375
401
  // page where it stands (#535): each goes in a frameless page-span box,
376
402
  // set by the span-block path like any other.
377
403
  const spanEmbeds = wrapPageSpanEmbeds(numberedBlocks, content.resources ?? [], effectiveResourceTypes(config, content.resources ?? []));
378
- const contentBlocks = spanEmbeds.blocks;
404
+ // Code listings (#624): each in a box of the code style, which the
405
+ // callout machinery places and splits.
406
+ const codeBoxes = wrapCodeBlocks(spanEmbeds.blocks, resolved);
407
+ const contentBlocks = codeBoxes.blocks;
379
408
  if (indexExpanded.warnings.length > 0)
380
409
  indexWarnings.set(doc, indexExpanded.warnings);
381
410
  const isNumbered = (b) => headingIsNumbered(b, resolved);
@@ -412,9 +441,17 @@ function placeDocumentPass(content, config, cache, options, hints) {
412
441
  // The callout styles, with the frameless box of the page-span embeds
413
442
  // when there are any: the float gap (a line) above and below it, as an
414
443
  // inline resource keeps in a column.
415
- const calloutStyles = spanEmbeds.wrapped
416
- ? [...resolved.calloutStyles, ...resolveCalloutStylesConfig([spanEmbedStyle(bodyStyle.lineHeightPx, resolved.layout.inlineResourceGap !== 'above')], resolved.bodyText, resolved.headings, resolved.unorderedLists, config?.locale)]
444
+ // The listings' box (#624) likewise.
445
+ const syntheticBoxes = [
446
+ ...(spanEmbeds.wrapped ? [spanEmbedStyle(bodyStyle.lineHeightPx, resolved.layout.inlineResourceGap !== 'above')] : []),
447
+ ...(codeBoxes.wrapped ? [codeBoxStyle(resolved)] : []),
448
+ ];
449
+ const calloutStyles = syntheticBoxes.length > 0
450
+ ? [...resolved.calloutStyles, ...resolveCalloutStylesConfig(syntheticBoxes, resolved.bodyText, resolved.headings, resolved.unorderedLists, config?.locale)]
417
451
  : resolved.calloutStyles;
452
+ // A box nested in another looks its style up in the configuration it is
453
+ // laid out with: the listings' box among them.
454
+ const calloutResolved = codeBoxes.wrapped ? { ...resolved, calloutStyles } : resolved;
418
455
  const blockquoteStyle = resolveBlockquoteStyle(resolved);
419
456
  const listLevelIndentsPx = computeLevelIndentsPx(resolved, bodyStyle.fontSizePx);
420
457
  const orderedMetrics = computeOrderedListRunMetrics(contentBlocks, resolved, bodyStyle.fontSizePx);
@@ -445,7 +482,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
445
482
  // it asks for is not applied there. Decided per block, by the writing
446
483
  // mode of the styled section the reference sits in (a horizontal
447
484
  // appendix of a vertical book turns its figures as asked).
448
- const floatPlan = computeFloatPlan(contentBlocks, resources, resourceTypes, incorporated, (blockIdx) => sectionWritingMode(sectionPlan, resolved, blockIdx) === 'vertical-rl');
485
+ const floatPlan = computeFloatPlan(contentBlocks, resources, resourceTypes, incorporated, (blockIdx) => sectionWritingMode(sectionPlan, resolved, blockIdx) === 'vertical-rl', resolved.layout.floatShrink, resolved.layout.wrap);
449
486
  const floatedIds = floatedResourceIds(floatPlan, incorporated, resources, resourceTypes);
450
487
  const floatsByFirstBlock = new Map();
451
488
  for (const f of floatPlan) {
@@ -808,7 +845,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
808
845
  /** Pages whose head or foot a floated box took (see the gallery rule in
809
846
  * `placeFloatInColumns`). */
810
847
  const calloutBandPages = new Set();
811
- const enqueueCalloutFloat = (startIdx, plan, style, L, placement, span) => {
848
+ const enqueueCalloutFloat = (startIdx, plan, style, L, placement, span,
849
+ /** Text wraps round the box (#627): a one-column float at its side. */
850
+ wrap) => {
812
851
  const key = `${CALLOUT_FLOAT_PREFIX}${startIdx}`;
813
852
  const columns = plan.attrs.columns !== undefined ? floatColumnCount(plan.attrs.columns) : style.columns;
814
853
  // A keep-with-next replay passes the fence again: enqueue once.
@@ -824,6 +863,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
824
863
  span: span === 'page' ? 'page' : 'column',
825
864
  ...(span === 'column' && columns > 1 ? { columns } : {}),
826
865
  callout: { startIdx },
866
+ ...(wrap && span === 'column' && columns === 1
867
+ ? { widthFraction: wrap.width, align: wrap.side, wrap: wrap.side, ...(wrap.gap ? { wrapGap: wrap.gap } : {}) }
868
+ : {}),
827
869
  ...(col ? { refPageIndex: page.index, refY: col.bbox.y + (col.bbox.height - col.availableHeight) } : {}),
828
870
  });
829
871
  }
@@ -891,7 +933,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
891
933
  };
892
934
  const rotationKey = (rotated) => rotated ? `:${rotated.direction}${rotated.length.toFixed(2)}${rotated.upright ? 'u' : ''}` : '';
893
935
  const asideKey = (a) => (a ? `:aside${a.dx.toFixed(1)}x${a.width.toFixed(1)}${a.alignBottom ? 'b' : 't'}${(a.offsetY ?? 0).toFixed(1)}` : '');
894
- const layoutFloat = (resourceId, width, slice, rotated, aside) => {
936
+ const capKey = (cap) => (cap ? `:cap${cap.maxBodyHeight.toFixed(2)}@${cap.minScale}` : '');
937
+ const layoutFloat = (resourceId, width, slice, rotated, aside, cap) => {
895
938
  const resource = resourceById.get(resourceId);
896
939
  if (!resource)
897
940
  return null;
@@ -912,6 +955,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
912
955
  ...(rotated ? (rotated.upright ? { upright: { maxLength: rotated.length } } : { rotate: rotated.direction, rotatedLength: rotated.length }) : {}),
913
956
  ...(aside ? { captionAside: aside } : {}),
914
957
  ...(flexPx > 0 ? { bodyHeightDelta: flexPx } : {}),
958
+ ...(cap && !rotated ? { maxBodyHeight: cap.maxBodyHeight, minBodyScale: cap.minScale } : {}),
915
959
  });
916
960
  };
917
961
  /** The rotation of a pending float on a band whose columns keep `avail`
@@ -962,8 +1006,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
962
1006
  /** Height (and caption baseline) of a float at a given width, memoised —
963
1007
  * fit checks run for every pending float on every loop iteration. */
964
1008
  const floatMeasureMemo = new Map();
965
- const measureFloat = (resourceId, width, slice, rotated, aside) => {
966
- const key = `${resourceId}:${width.toFixed(2)}${sliceKey(slice)}${rotationKey(rotated)}${asideKey(aside)}`;
1009
+ const measureFloat = (resourceId, width, slice, rotated, aside, cap) => {
1010
+ const key = `${resourceId}:${width.toFixed(2)}${sliceKey(slice)}${rotationKey(rotated)}${asideKey(aside)}${capKey(cap)}`;
967
1011
  const memo = floatMeasureMemo.get(key);
968
1012
  if (memo !== undefined)
969
1013
  return memo;
@@ -997,7 +1041,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
997
1041
  floatMeasureMemo.set(key, mc);
998
1042
  return mc;
999
1043
  }
1000
- const laid = layoutFloat(resourceId, width, slice, rotated, aside);
1044
+ const laid = layoutFloat(resourceId, width, slice, rotated, aside, cap);
1001
1045
  let m = null;
1002
1046
  if (laid && laid.block.rotation) {
1003
1047
  // A rotated block takes its band whole: no caption baseline to align,
@@ -1018,10 +1062,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
1018
1062
  if (lastBaseline === undefined || ln.baseline > lastBaseline)
1019
1063
  lastBaseline = ln.baseline;
1020
1064
  }
1065
+ const picture = rb.kind === 'bitmap' || rb.kind === 'svg' || rb.kind === 'video';
1021
1066
  m = {
1022
1067
  height: laid.totalHeight,
1023
1068
  ...(lastBaseline !== undefined ? { lastCaptionBaseline: lastBaseline } : {}),
1024
1069
  ...(laid.asideHeight !== undefined ? { asideHeight: laid.asideHeight } : {}),
1070
+ ...(picture ? { body: { height: rb.bodyRect.height, scale: rb.shrinkScale ?? 1 } } : {}),
1025
1071
  };
1026
1072
  }
1027
1073
  floatMeasureMemo.set(key, m);
@@ -1037,7 +1083,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
1037
1083
  flushEnd = false, aside,
1038
1084
  /** The page is a verso of mirrored margins (a floated box's `'outer'`
1039
1085
  * corner icon hangs on the left there). */
1040
- mirrored = false) => {
1086
+ mirrored = false,
1087
+ /** A picture scaled to its slot (`placement.shrink`). */
1088
+ cap) => {
1041
1089
  const strip = comicStripOf(resourceId);
1042
1090
  if (strip) {
1043
1091
  const block = buildComicStripBlock(strip.idx, strip.source, x, width, contentArea.height - 2 * floatGapPx, cursor.pageIndex);
@@ -1050,7 +1098,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
1050
1098
  calloutFloatResults.set(result.frame, { result, startIdx, plan: cf.plan });
1051
1099
  return { block: result.frame, height: result.totalHeight };
1052
1100
  }
1053
- const laid = layoutFloat(resourceId, width, slice, rotated, aside);
1101
+ const laid = layoutFloat(resourceId, width, slice, rotated, aside, cap);
1054
1102
  if (!laid)
1055
1103
  return null;
1056
1104
  const { block: rb, totalHeight } = laid;
@@ -1089,6 +1137,52 @@ function placeDocumentPass(content, config, cache, options, hints) {
1089
1137
  /** Pages a `:::pagebreak{center}` opened (#424). */
1090
1138
  const centredPages = new Set();
1091
1139
  const reservedOf = (col) => floatReserved.get(col) ?? { top: 0, bottom: 0 };
1140
+ /** Text wrap (#627): floats set in a band at the head or foot of one
1141
+ * column that text may run beside, per column. The band stays a band
1142
+ * until running text comes to take it back (`openFloatWraps`). */
1143
+ /** The room a wrapped item takes from the lines beside it (#627): its
1144
+ * width and gap, in whole characters of the body under a CJK character
1145
+ * grid (`cjk.grid`), so the lines beside it keep to the grid. */
1146
+ const wrapExclusionWidth = (columnWidth, itemWidth, gap) => {
1147
+ const w = itemWidth + gap;
1148
+ const em = bodyStyle.fontSizePx;
1149
+ return Math.min(columnWidth, resolved.cjk.grid.enabled && em > 0 ? Math.ceil((w - 0.01) / em) * em : w);
1150
+ };
1151
+ /** The `textWrap` warnings of this pass (#627). */
1152
+ const textWrapNotes = [];
1153
+ const floatWraps = new Map();
1154
+ /** Give the bands of the wrapped floats of `col` back to the flow, as
1155
+ * exclusions the text runs beside: a float at the head of a column the
1156
+ * text has not entered yet, one at its foot the text has not reached. */
1157
+ const openFloatWraps = (col) => {
1158
+ const list = floatWraps.get(col);
1159
+ if (!list)
1160
+ return;
1161
+ floatWraps.delete(col);
1162
+ for (const w of list) {
1163
+ const r = { ...reservedOf(col) };
1164
+ if (w.position === 'top') {
1165
+ // A block already stands under it, or another band between: it
1166
+ // keeps its band.
1167
+ if (col.blocks.length > 0 || Math.abs(w.ex.y + w.need - col.bbox.y) > 0.5)
1168
+ continue;
1169
+ col.bbox.y -= w.need;
1170
+ col.bbox.height += w.need;
1171
+ col.availableHeight += w.need;
1172
+ r.top = Math.max(0, r.top - w.need);
1173
+ openColumnWrap(col, w.ex, 'top');
1174
+ }
1175
+ else {
1176
+ if (uncappedBottoms.has(col) || Math.abs(col.bbox.y + col.bbox.height - w.ex.y) > 0.5 || columnCursorY(col) > w.ex.y + 0.01)
1177
+ continue;
1178
+ col.bbox.height += w.need;
1179
+ col.availableHeight += w.need;
1180
+ r.bottom = Math.max(0, r.bottom - w.need);
1181
+ openColumnWrap(col, w.ex, 'bottom');
1182
+ }
1183
+ floatReserved.set(col, r);
1184
+ }
1185
+ };
1092
1186
  const floatHeadOf = (col) => reservedOf(col).top;
1093
1187
  /** `BandCap.headPx` for a cap proposed on `cols`: how far the float bands
1094
1188
  * every column holds at its head put the band's top below its bare one. */
@@ -1342,6 +1436,50 @@ function placeDocumentPass(content, config, cache, options, hints) {
1342
1436
  built.block.contentIndex = anchorIdx;
1343
1437
  (page.floats ??= []).push(built.block);
1344
1438
  };
1439
+ /**
1440
+ * A picture float scaled to `room` px of a slot instead of moving on
1441
+ * (`placement.shrink`, #626): the tallest body that keeps its band
1442
+ * within the room. A top band rounds up to the grid, so the float may
1443
+ * stand as tall as the grid multiple within the room, less its gap; a
1444
+ * bottom band (its caption's last baseline on the grid) and a side
1445
+ * stack give the room back px for px. The caption's height may change
1446
+ * with the picture's width (`captionMeasure: 'body'`) and a safe area
1447
+ * crops before it scales, so the cap is refined over a few rounds. The
1448
+ * scale never goes below the float's `minScale`; `fits` is false when
1449
+ * even that does not hold the room. Null for a float that does not
1450
+ * scale (no `shrink`, not an upright picture, the rest of a table) or
1451
+ * already fits.
1452
+ */
1453
+ const shrinkToRoom = (f, width, aside, full, room, topBand, bandOf) => {
1454
+ if (!f.shrink || !full.body || f.rotate || f.startRow)
1455
+ return null;
1456
+ const minScale = f.minScale ?? DEFAULT_FLOAT_MIN_SCALE;
1457
+ const excessOf = (m, band) => topBand
1458
+ ? m.height + floatGapBelow(m, floatGapPx) - Math.floor((room + 0.01) / baselineGrid) * baselineGrid
1459
+ : band.need - room;
1460
+ let measure = full;
1461
+ let band = bandOf(full);
1462
+ let cap;
1463
+ for (let round = 0; round < 4; round++) {
1464
+ const excess = excessOf(measure, band);
1465
+ if (excess <= 0.01)
1466
+ break;
1467
+ const next = { maxBodyHeight: Math.max(0, measure.body.height - excess), minScale };
1468
+ if (cap && next.maxBodyHeight >= cap.maxBodyHeight - 0.01)
1469
+ break;
1470
+ cap = next;
1471
+ const m = measureFloat(f.resourceId, width, undefined, undefined, aside, cap);
1472
+ if (!m?.body)
1473
+ return null;
1474
+ measure = m;
1475
+ band = bandOf(m);
1476
+ if (m.body.scale <= minScale + 1e-6)
1477
+ break;
1478
+ }
1479
+ if (!cap)
1480
+ return null;
1481
+ return { measure, ...band, cap, fits: excessOf(measure, band) <= 0.01 };
1482
+ };
1345
1483
  const placeFloatInColumns = (page, f, targetCols, position, pageSpan, mode, anchorToCap = false, side = false, refY) => {
1346
1484
  const first = targetCols[0];
1347
1485
  const probe = probeFloatBand(page, f, targetCols, position, pageSpan, anchorToCap, side, refY);
@@ -1350,6 +1488,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
1350
1488
  const { width, xLeft, rotated, aside, sideCol } = probe;
1351
1489
  let { slice, measure, need, y } = probe;
1352
1490
  let rest;
1491
+ /** A picture scaled to this slot (`placement.shrink`, #626). */
1492
+ let cap;
1493
+ const bandOf = (m) => measureFloatBand(position, m, targetCols, page.contentArea, baselineGrid, floatGapPx, (c) => (anchorToCap ? c.bbox.y + c.bbox.height : trueBottom(c, uncappedBottoms)));
1353
1494
  /** The float was cut to this slot (a table slice). */
1354
1495
  let cut = false;
1355
1496
  /** A rotated block wider than the band (its upright height). */
@@ -1359,24 +1500,36 @@ function placeDocumentPass(content, config, cache, options, hints) {
1359
1500
  // when the rest of the column takes it; otherwise it waits for the
1360
1501
  // side column of the next page — where it is set anyway, overflowing,
1361
1502
  // when even an empty column cannot hold it (a dominating figure).
1503
+ let cap;
1362
1504
  if (need > first.availableHeight + 0.01) {
1363
1505
  // A column that already holds something — a float, a box, or a
1364
1506
  // heading design standing in it — is no empty column to overflow.
1365
1507
  const holds = sideUsedBottom(first) > first.bbox.y + 0.5 || (sideObstacles.get(first)?.length ?? 0) > 0;
1366
- if (mode === 'strict' || holds)
1367
- return 'defer';
1368
1508
  // An empty column shortened by a band above it (a page-span box,
1369
1509
  // a top float) is no measure of the float: when a full column
1370
1510
  // would hold it, it waits for one instead of overflowing this one.
1371
- if (shortSideColumn(page, first) && measure.height <= page.contentArea.height + 0.01)
1372
- return 'defer';
1373
- // Overflowing a column a side caption has shortened: the next pass
1374
- // sets that caption under its figure and gives the column back.
1375
- const cutBy = asideCutBy.get(first);
1376
- if (cutBy !== undefined)
1377
- captionUnderProposals.add(cutBy);
1511
+ const waitsForFull = shortSideColumn(page, first) && measure.height <= page.contentArea.height + 0.01;
1512
+ const lastResort = mode === 'fresh' && !holds && !waitsForFull;
1513
+ // A picture scaled to the room the column keeps (#626): any slot
1514
+ // with `'slot'`, when it holds it at its smallest scale or more;
1515
+ // the empty column it would overflow with `'page'` too, at its
1516
+ // smallest scale if need be.
1517
+ if (f.shrink === 'slot' || (f.shrink && lastResort)) {
1518
+ const s = shrinkToRoom(f, width, undefined, measure, first.availableHeight, false, (m) => measureSideStack(m, first, refY, page.contentArea, baselineGrid, floatGapPx, sideObstacles.get(first)));
1519
+ if (s && (s.fits || lastResort))
1520
+ ({ measure, need, y, cap } = s);
1521
+ }
1522
+ if (need > first.availableHeight + 0.01) {
1523
+ if (!lastResort)
1524
+ return 'defer';
1525
+ // Overflowing a column a side caption has shortened: the next pass
1526
+ // sets that caption under its figure and gives the column back.
1527
+ const cutBy = asideCutBy.get(first);
1528
+ if (cutBy !== undefined)
1529
+ captionUnderProposals.add(cutBy);
1530
+ }
1378
1531
  }
1379
- const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated);
1532
+ const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated, false, undefined, false, cap);
1380
1533
  if (!built)
1381
1534
  return 'skip';
1382
1535
  first.availableHeight = Math.max(0, first.availableHeight - need);
@@ -1404,9 +1557,26 @@ function placeDocumentPass(content, config, cache, options, hints) {
1404
1557
  const gallery = need <= minAvail + 0.01
1405
1558
  && f.refPageIndex !== undefined && f.refPageIndex < page.index
1406
1559
  && calloutBandPages.has(page) && !calloutFloatOf(f.resourceId);
1407
- if (!gallery)
1408
- return 'defer';
1409
- galleryFill = true;
1560
+ if (gallery) {
1561
+ galleryFill = true;
1562
+ }
1563
+ else {
1564
+ // A picture that scales to any slot (#626) takes the room left
1565
+ // beside the other band, the text room kept, when it holds it
1566
+ // at its smallest scale or more.
1567
+ const s = f.shrink === 'slot' ? shrinkToRoom(f, width, aside, measure, minAvail - minTextPx, position === 'top', bandOf) : null;
1568
+ if (!s?.fits)
1569
+ return 'defer';
1570
+ ({ measure, need, y, cap } = s);
1571
+ }
1572
+ }
1573
+ // Dominating the band: a picture that scales (`'page'` or `'slot'`,
1574
+ // #626) takes the band's room, at its smallest scale if need be —
1575
+ // then it runs past the band, as any dominating float does.
1576
+ if (need > minAvail + 0.01 && f.shrink && !rotated) {
1577
+ const s = shrinkToRoom(f, width, aside, measure, minAvail, position === 'top', bandOf);
1578
+ if (s)
1579
+ ({ measure, need, y, cap } = s);
1410
1580
  }
1411
1581
  if (need > minAvail + 0.01 || tooWide(measure)) {
1412
1582
  // Dominating the band: a table is cut to it (a rotated table, to
@@ -1432,6 +1602,19 @@ function placeDocumentPass(content, config, cache, options, hints) {
1432
1602
  // too wide.
1433
1603
  if (f.rotate || tooWide(measure))
1434
1604
  return 'defer';
1605
+ // A picture that scales to any slot (#626) takes this one when it
1606
+ // holds it at its smallest scale or more (the text room kept next
1607
+ // to another band, as `fitsStrict` keeps it); the loop below checks
1608
+ // the band as for any float.
1609
+ if (f.shrink === 'slot' && measure.body && !rotated) {
1610
+ const room = Math.min(...targetCols.map((c) => c.availableHeight - (columnHasFloatBand(page, c) ? minTextPx : 0)));
1611
+ if (need > room + 0.01) {
1612
+ const s = shrinkToRoom(f, width, aside, measure, room, position === 'top', bandOf);
1613
+ if (!s?.fits)
1614
+ return 'defer';
1615
+ ({ measure, need, y, cap } = s);
1616
+ }
1617
+ }
1435
1618
  for (const c of targetCols) {
1436
1619
  if (position === 'bottom' && uncappedBottoms.has(c) && !anchorToCap) {
1437
1620
  // Trailing cap: the band must lie entirely below the level cut.
@@ -1489,7 +1672,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
1489
1672
  // An upright figure of a vertical page stands at the head of its tier
1490
1673
  // (`placement.align`: `left` the top, `center`, `right` the foot).
1491
1674
  const flush = rotated?.upright ? (f.align === 'center' ? 0.5 : f.align === 'right' ? 1 : 0) : rotated ? rotatedFlushEnd(page) : false;
1492
- const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated, flush, aside, mirroredOf(page));
1675
+ const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated, flush, aside, mirroredOf(page), cap);
1493
1676
  if (!built)
1494
1677
  return 'skip';
1495
1678
  // The caption beside the figure takes its band of the side column: a
@@ -1544,6 +1727,40 @@ function placeDocumentPass(content, config, cache, options, hints) {
1544
1727
  if (galleryFill)
1545
1728
  for (const col of targetCols)
1546
1729
  col.availableHeight = 0;
1730
+ // Text wrap (#627): a one-column float at the head or foot of its
1731
+ // column, which running text may take back to run beside it — unless
1732
+ // the text left beside it would be too narrow, or the float too short
1733
+ // for lines beside it.
1734
+ if (f.wrap && !pageSpan && !side && targetCols.length === 1 && !rotated && !aside && !cut && !galleryFill
1735
+ && !(position === 'bottom' && uncappedBottoms.has(first))) {
1736
+ const gap = wrapGapPx(f.wrapGap, dpi, bodyStyle.fontSizePx, floatGapPx);
1737
+ const settings = resolved.layout.wrap;
1738
+ const reason = first.bbox.width - width - gap + 0.01 < minTextWidthPx(settings.minTextWidth, first.bbox.width, dpi, bodyStyle.fontSizePx)
1739
+ ? 'tooNarrow'
1740
+ : Math.round(need / bodyStyle.lineHeightPx) < settings.minLinesBeside ? 'fewLines' : undefined;
1741
+ if (reason) {
1742
+ textWrapNotes.push({ kind: 'textWrap', reason, resourceId: f.resourceId, pageIndex: page.index });
1743
+ }
1744
+ else {
1745
+ const exWidth = wrapExclusionWidth(first.bbox.width, width, gap);
1746
+ const exY = position === 'top' ? first.bbox.y - need : first.bbox.y + first.bbox.height;
1747
+ const list = floatWraps.get(first) ?? [];
1748
+ list.push({
1749
+ position,
1750
+ need,
1751
+ ex: {
1752
+ x: f.wrap === 'left' ? first.bbox.x : first.bbox.x + first.bbox.width - exWidth,
1753
+ y: exY,
1754
+ width: exWidth,
1755
+ height: need,
1756
+ side: f.wrap,
1757
+ gap: exWidth - width,
1758
+ ownerId: built.block.id,
1759
+ },
1760
+ });
1761
+ floatWraps.set(first, list);
1762
+ }
1763
+ }
1547
1764
  commitFloatBlock(page, first, built, xLeft, y, width, f.firstBlockIdx);
1548
1765
  floatsPlaced++;
1549
1766
  return rest ? { rest } : 'placed';
@@ -1898,6 +2115,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
1898
2115
  /** Blocks whose style tracks text of a joining script, which is set
1899
2116
  * untracked (see `BlockMeasureContext.joiningLetterSpacing`). */
1900
2117
  const joiningLetterSpacing = new Set();
2118
+ /** Paragraphs whose drop cap could not be set as configured (#623). */
2119
+ const dropCapNotes = new Map();
1901
2120
  // Everything per-block measurement needs that is constant for this pass.
1902
2121
  const measureCtx = {
1903
2122
  resolved,
@@ -1923,6 +2142,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
1923
2142
  floatedIds,
1924
2143
  leftFlow: calloutsOutOfFlow,
1925
2144
  joiningLetterSpacing,
2145
+ dropCapNotes,
1926
2146
  ...(footnoteNumbering.numbers.size > 0 ? { footnoteNumbers: footnoteNumbering.numbers } : {}),
1927
2147
  ...(anchorRefContext ? { anchorRefs: anchorRefContext } : {}),
1928
2148
  };
@@ -2283,7 +2503,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
2283
2503
  blk.lines = resetLinePositions(raggedLooseLines(m.measured.lines, noteStyle.textAlign), noteStyle.lineHeightPx).map((line) => ({
2284
2504
  ...line,
2285
2505
  bbox: createBoundingBox(line.bbox.x + x, line.bbox.y + y, line.bbox.width, line.bbox.height),
2286
- ...(line.measure ? { measure: { x: line.measure.x + x, width: line.measure.width } } : {}),
2506
+ ...(line.measure ? { measure: { ...line.measure, x: line.measure.x + x } } : {}),
2287
2507
  baseline: line.baseline + y,
2288
2508
  }));
2289
2509
  const h = blk.lines.length * noteStyle.lineHeightPx;
@@ -2745,7 +2965,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
2745
2965
  return;
2746
2966
  const band = currentBand(page, cursor);
2747
2967
  const cols = bandColumns(page, band).filter((c) => c.bbox.height > 0.5);
2748
- if (cols.length < 2 || cols.some((c) => c.forcedBreak))
2968
+ // A band where text wraps round something (#627) is not levelled.
2969
+ if (cols.length < 2 || cols.some((c) => c.forcedBreak || (c.exclusions?.length ?? 0) > 0))
2749
2970
  return;
2750
2971
  if (activeCap && activeCap.spanIndex === boundaryIndex
2751
2972
  && activeCap.pageIndex === page.index && activeCap.band === band) {
@@ -2815,7 +3036,15 @@ function placeDocumentPass(content, config, cache, options, hints) {
2815
3036
  * page the boundary opens: they sit the drain out and stay pending (with
2816
3037
  * no reference stamp, which the citing line sets), so they land after
2817
3038
  * the line that cites them, in the new segment (EF-69). */
3039
+ /** Text wrap (#627): an inline figure that wraps, placed as a band, whose
3040
+ * band the next block takes back to run beside it when it can (see
3041
+ * `pipeline/textWrap.ts`). */
3042
+ let pendingWrap = null;
2818
3043
  const closeFlowSegment = (boundaryIndex) => {
3044
+ // What text wrapped round in the column the segment ends in keeps its
3045
+ // room (#627).
3046
+ pendingWrap = null;
3047
+ settleColumnWraps(currentColumn(doc, cursor));
2819
3048
  settleAwaitingSideBoxes(true);
2820
3049
  releaseStaleCitationGates();
2821
3050
  const ownFloats = [];
@@ -3081,7 +3310,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
3081
3310
  childStartIdx: startIdx + 1 + from.child,
3082
3311
  width,
3083
3312
  ctx: measureCtx,
3084
- resolved,
3313
+ resolved: calloutResolved,
3085
3314
  containerId: plan.containerId,
3086
3315
  frameId,
3087
3316
  nextChildId: () => `${frameId}-c${n++}`,
@@ -3154,7 +3383,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
3154
3383
  return null;
3155
3384
  // A head that goes on also carries the style's continuation marker.
3156
3385
  const tail = tailOf(full) + full.continuesMarkerPx;
3157
- /** Candidate cuts with the head's content bottom. */
3386
+ /** Candidate cuts with the head's content bottom. A `soft` cut parts a
3387
+ * wrapped code line from its continuation (#624): taken only when no
3388
+ * other cut fits. */
3158
3389
  const candidates = [];
3159
3390
  /** Collect the cuts among `units` (one box's items; `below` = the tails
3160
3391
  * of the nested boxes around them), each side of a cut holding enough
@@ -3197,6 +3428,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
3197
3428
  candidates.push({
3198
3429
  cut: { child: k, line: lineBase + l, widths: k === from.child ? cutWidths(from, width) : [{ fromLine: 0, width }] },
3199
3430
  bottom: line.bbox.y + line.bbox.height + below,
3431
+ ...(c.lines[l]?.codeLine?.continued ? { soft: true } : {}),
3200
3432
  });
3201
3433
  }
3202
3434
  }
@@ -3223,6 +3455,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
3223
3455
  const viable = candidates
3224
3456
  .filter((c) => !insideGroup(c.cut))
3225
3457
  .sort((a, b) => b.bottom - a.bottom);
3458
+ // A wrapped code line stays with its continuations when a cut
3459
+ // elsewhere fits (#624).
3460
+ viable.sort((a, b) => (a.soft ? 1 : 0) - (b.soft ? 1 : 0));
3226
3461
  for (const c of viable) {
3227
3462
  if (c.bottom + tail > roomPx + 0.01)
3228
3463
  continue;
@@ -4066,6 +4301,83 @@ function placeDocumentPass(content, config, cache, options, hints) {
4066
4301
  * boxes) still fall back to this inline placement for v1 — the frame's
4067
4302
  * `callout.placement` records the request.
4068
4303
  */
4304
+ /**
4305
+ * A box set `here` that text wraps round (#627): laid out whole at its
4306
+ * share of the column, at its side, level with the next line's top, then
4307
+ * set as a band the next running text takes back to run beside it (see
4308
+ * `pendingWrap`). It never splits: too tall for the room left, it moves
4309
+ * on to the next column (a `textWrap` warning says so). Frame and
4310
+ * children go to the page's floats, where a floated box goes. Returns
4311
+ * false, the box set in the flow as any, when the text beside it would
4312
+ * be too narrow or the box too short for lines beside it.
4313
+ */
4314
+ const placeCalloutWrapped = (startIdx, plan, style, L, frameId, wrap) => {
4315
+ const settings = resolved.layout.wrap;
4316
+ const gap = wrapGapPx(wrap.gap, dpi, bodyStyle.fontSizePx, floatGapPx);
4317
+ const note = (reason) => {
4318
+ const start = contentBlocks[startIdx];
4319
+ textWrapNotes.push({ kind: 'textWrap', reason, box: style.id, sourceStart: start.sourceStart + bodyOffset, sourceEnd: start.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex });
4320
+ };
4321
+ let col = currentColumn(doc, cursor);
4322
+ if (col.bbox.width * (1 - wrap.width) - gap + 0.01 < minTextWidthPx(settings.minTextWidth, col.bbox.width, dpi, bodyStyle.fontSizePx)) {
4323
+ note('tooNarrow');
4324
+ return false;
4325
+ }
4326
+ for (let attempt = 0;; attempt++) {
4327
+ col = currentColumn(doc, cursor);
4328
+ const page = doc.pages[cursor.pageIndex];
4329
+ const width = col.bbox.width * wrap.width;
4330
+ const result = L.layoutRange(CUT_START, L.end, width, frameId, false, mirroredOf(page));
4331
+ if (attempt === 0 && Math.ceil((result.totalHeight + gap - 0.01) / bodyStyle.lineHeightPx) < settings.minLinesBeside) {
4332
+ note('fewLines');
4333
+ return false;
4334
+ }
4335
+ const spacing = col.blocks.length === 0 ? 0 : pendingSpacing;
4336
+ if (result.totalHeight > col.availableHeight - spacing + 0.01 && col.blocks.length > 0 && attempt < 4) {
4337
+ if (attempt === 0)
4338
+ note('moved');
4339
+ pendingSpacing = 0;
4340
+ advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
4341
+ continue;
4342
+ }
4343
+ const top = columnCursorY(col) + spacing;
4344
+ const x = wrap.side === 'left' ? col.bbox.x : col.bbox.x + col.bbox.width - width;
4345
+ const frame = result.frame;
4346
+ stampCalloutSource(frame, startIdx, plan);
4347
+ frame.pageIndex = page.index;
4348
+ frame.columnIndex = col.index;
4349
+ offsetCalloutToAbsolute(result, x, top);
4350
+ const floats = (page.floats ??= []);
4351
+ doc.blocks.push(frame);
4352
+ floats.push(frame);
4353
+ for (const child of result.children) {
4354
+ child.pageIndex = page.index;
4355
+ child.columnIndex = col.index;
4356
+ doc.blocks.push(child);
4357
+ floats.push(child);
4358
+ }
4359
+ const bottomRel = Math.ceil((top - col.bbox.y + result.totalHeight + gap - 0.01) / baselineGrid) * baselineGrid;
4360
+ col.availableHeight = Math.max(0, Math.min(col.availableHeight, col.bbox.height - bottomRel));
4361
+ pendingSpacing = 0;
4362
+ inlineGapOwed = null;
4363
+ const exWidth = wrapExclusionWidth(col.bbox.width, width, gap);
4364
+ pendingWrap = {
4365
+ col,
4366
+ blocks: col.blocks.length,
4367
+ cursorY: columnCursorY(col),
4368
+ ex: {
4369
+ x: wrap.side === 'left' ? col.bbox.x : col.bbox.x + col.bbox.width - exWidth,
4370
+ y: top,
4371
+ width: exWidth,
4372
+ height: col.bbox.y + bottomRel - top,
4373
+ side: wrap.side,
4374
+ gap: exWidth - width,
4375
+ ownerId: frame.id,
4376
+ },
4377
+ };
4378
+ return true;
4379
+ }
4380
+ };
4069
4381
  const placeCalloutInline = (startIdx, plan) => {
4070
4382
  const style = pickCalloutStyle(calloutStyles, plan.attrs.type);
4071
4383
  const firstFrameId = `block-${blockIdCounter++}`;
@@ -4094,7 +4406,17 @@ function placeDocumentPass(content, config, cache, options, hints) {
4094
4406
  const inFlow = tallerThanColumn(L.layoutRange(CUT_START, L.end, width, 'float-probe', false))
4095
4407
  && splitCalloutFragment(L, CUT_START, width, contentArea.height, 'float-probe', false, style.splitMinLines) !== null;
4096
4408
  if (!inFlow) {
4097
- enqueueCalloutFloat(startIdx, plan, style, L, placement, span);
4409
+ const wrap = pageIsVertical(page) ? undefined : resolveCalloutWrap(plan.attrs, resolved.layout.wrap);
4410
+ enqueueCalloutFloat(startIdx, plan, style, L, placement, span, wrap);
4411
+ calloutsOutOfFlow.add(startIdx);
4412
+ return undefined;
4413
+ }
4414
+ }
4415
+ // A box text wraps round (#627): at its side of the column, the text
4416
+ // after it beside it.
4417
+ if (placement === 'here' && span === 'column' && !pageIsVertical(doc.pages[cursor.pageIndex])) {
4418
+ const wrap = resolveCalloutWrap(plan.attrs, resolved.layout.wrap);
4419
+ if (wrap && placeCalloutWrapped(startIdx, plan, style, L, firstFrameId, wrap)) {
4098
4420
  calloutsOutOfFlow.add(startIdx);
4099
4421
  return undefined;
4100
4422
  }
@@ -4433,6 +4755,29 @@ function placeDocumentPass(content, config, cache, options, hints) {
4433
4755
  releaseStaleCitationGates(blockIdx);
4434
4756
  tryPlacePendingFloatsOnCurrentPage(spanBoxAt(blockIdx), blockIdx);
4435
4757
  enqueueFloatsFor(blockIdx);
4758
+ // Text wrap (#627): running text after a wrapped figure takes the band
4759
+ // the figure was set in back, and runs beside it; anything else that
4760
+ // takes room from the flow goes under what text wraps round first.
4761
+ // Markers and the `::resource` line of a floated resource leave it.
4762
+ if (!transparentToWrap(rawBlock) && !(rawBlock.type === 'resourceBlock' && rawBlock.resourceId !== undefined && floatedIds.has(rawBlock.resourceId))) {
4763
+ const wrapCol = currentColumn(doc, cursor);
4764
+ const beside = runsBesideWrap(rawBlock);
4765
+ if (pendingWrap) {
4766
+ const { col, blocks, cursorY, ex } = pendingWrap;
4767
+ pendingWrap = null;
4768
+ if (beside && col === wrapCol && col.blocks.length === blocks && Math.abs(columnCursorY(col) - cursorY) < 0.01) {
4769
+ // The cursor goes back to the figure's top; the text starts there.
4770
+ col.availableHeight += columnCursorY(col) - ex.y;
4771
+ pendingSpacing = 0;
4772
+ inlineGapOwed = null;
4773
+ openColumnWrap(col, ex, 'top');
4774
+ }
4775
+ }
4776
+ if (!beside && settleColumnWraps(wrapCol)) {
4777
+ pendingSpacing = 0;
4778
+ inlineGapOwed = null;
4779
+ }
4780
+ }
4436
4781
  // --- Directives ----------------------------------------------------
4437
4782
  // (A comic page, `:::page`, and a strip, `:::strip`, are placed below,
4438
4783
  // once a closed part or paper run has taken its page break.)
@@ -4881,6 +5226,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
4881
5226
  if (!measuredBlock)
4882
5227
  continue;
4883
5228
  const { kind, contentBlock, measured, prefixLen, absoluteSourceMap, mathDisplayRender } = measuredBlock;
5229
+ /** The drop cap opening the paragraph (#623): stamped on the fragment
5230
+ * that holds its first line. A paragraph broken again for a column of
5231
+ * another width takes the one of that setting. */
5232
+ let capInfo = measuredBlock.dropCap;
4884
5233
  /** Tracking the block's lines are set with; a paragraph moved whole into
4885
5234
  * a column of another width is measured again, and may change it. */
4886
5235
  let { letterSpacingPx } = measuredBlock;
@@ -4922,12 +5271,55 @@ function placeDocumentPass(content, config, cache, options, hints) {
4922
5271
  segments: [],
4923
5272
  isLastLine: true,
4924
5273
  }];
5274
+ // Text wrap (#627): a picture the text after it may run beside, at
5275
+ // its side of the column — unless the text left beside it would be
5276
+ // too narrow, or the picture too short to set lines beside.
5277
+ const wrapSettings = blockMeasureCtx.resolved.layout.wrap;
5278
+ const wrapSpec = kind.resource && !pageIsVertical(blockPage)
5279
+ ? resolveResourceWrap(kind.resource, kind.resourceType, wrapSettings)
5280
+ : undefined;
5281
+ // Vertical text keeps its figures' bands whole (horizontal wrap only).
5282
+ if (kind.resource && pageIsVertical(blockPage) && resolveResourceWrap(kind.resource, kind.resourceType, wrapSettings)) {
5283
+ textWrapNotes.push({
5284
+ kind: 'textWrap', reason: 'verticalText', resourceId: kind.resource.id,
5285
+ sourceStart: rawBlock.sourceStart + bodyOffset, sourceEnd: rawBlock.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex,
5286
+ });
5287
+ }
5288
+ let wrapGap = 0;
5289
+ let wraps = false;
5290
+ if (wrapSpec) {
5291
+ wrapGap = wrapGapPx(wrapSpec.gap, dpi, bodyStyle.fontSizePx, floatGapPx);
5292
+ const textWidth = col.bbox.width * (1 - wrapSpec.width) - wrapGap;
5293
+ const linesBeside = Math.ceil((groupHeight + wrapGap - 0.01) / bodyStyle.lineHeightPx);
5294
+ const reason = textWidth + 0.01 < minTextWidthPx(wrapSettings.minTextWidth, col.bbox.width, dpi, bodyStyle.fontSizePx)
5295
+ ? 'tooNarrow'
5296
+ : linesBeside < wrapSettings.minLinesBeside ? 'fewLines' : undefined;
5297
+ if (reason) {
5298
+ textWrapNotes.push({
5299
+ kind: 'textWrap', reason, resourceId: kind.resource.id,
5300
+ sourceStart: rawBlock.sourceStart + bodyOffset, sourceEnd: rawBlock.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex,
5301
+ });
5302
+ }
5303
+ else {
5304
+ wraps = true;
5305
+ }
5306
+ }
4925
5307
  // An inline resource keeps the float gap (a line) above it, as a
4926
- // float would, unless the block before asked for more.
4927
- const spacingBefore = Math.max(pendingSpacing, floatGapPx);
5308
+ // float would, unless the block before asked for more. A wrapped one
5309
+ // stands level with the next line's top instead.
5310
+ const spacingBefore = wraps ? pendingSpacing : Math.max(pendingSpacing, floatGapPx);
5311
+ const columnBefore = col;
4928
5312
  enterBand(blockIdx, 0);
4929
5313
  placeAtomicBlock(blk, groupHeight, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx, onAtomicNewPage(groupHeight));
4930
5314
  enterBand(blockIdx, 0);
5315
+ // Too tall for the room left in its column, a wrapped picture moved
5316
+ // on with its anchor: say so.
5317
+ if (wraps && currentColumn(doc, cursor) !== columnBefore && columnBefore.blocks.length > 0) {
5318
+ textWrapNotes.push({
5319
+ kind: 'textWrap', reason: 'moved', resourceId: kind.resource.id,
5320
+ sourceStart: rawBlock.sourceStart + bodyOffset, sourceEnd: rawBlock.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex,
5321
+ });
5322
+ }
4931
5323
  // `placeBlockInColumn` (inside placeAtomicBlock) shifts `blk.lines`; the
4932
5324
  // resource's own caption/table lines live on `resourceBlock` and must be
4933
5325
  // offset to absolute page coordinates here using the placed bbox origin.
@@ -4938,6 +5330,34 @@ function placeDocumentPass(content, config, cache, options, hints) {
4938
5330
  addBalanceExtra(currentColumn(doc, cursor), grown);
4939
5331
  }
4940
5332
  doc.blocks.push(blk);
5333
+ // A wrapped picture (#627): the flow goes on under it and its gap, on
5334
+ // a grid line, unless the next block takes the band back to run
5335
+ // beside it (see `pendingWrap`).
5336
+ if (wraps && wrapSpec) {
5337
+ const rCol = currentColumn(doc, cursor);
5338
+ const top = blk.bbox.y;
5339
+ const bottomRel = Math.ceil((top - rCol.bbox.y + groupHeight + wrapGap - 0.01) / baselineGrid) * baselineGrid;
5340
+ rCol.availableHeight = Math.max(0, Math.min(rCol.availableHeight, rCol.bbox.height - bottomRel));
5341
+ pendingSpacing = 0;
5342
+ inlineGapOwed = null;
5343
+ const exWidth = wrapExclusionWidth(rCol.bbox.width, rCol.bbox.width * wrapSpec.width, wrapGap);
5344
+ pendingWrap = {
5345
+ col: rCol,
5346
+ blocks: rCol.blocks.length,
5347
+ cursorY: columnCursorY(rCol),
5348
+ ex: {
5349
+ x: wrapSpec.side === 'left' ? rCol.bbox.x : rCol.bbox.x + rCol.bbox.width - exWidth,
5350
+ y: top,
5351
+ width: exWidth,
5352
+ height: rCol.bbox.y + bottomRel - top,
5353
+ side: wrapSpec.side,
5354
+ gap: exWidth - rCol.bbox.width * wrapSpec.width,
5355
+ ownerId: blk.id,
5356
+ },
5357
+ };
5358
+ flushPendingNumberingAtBoundary();
5359
+ continue;
5360
+ }
4941
5361
  // Snap the flow position after the resource to the baseline grid (the
4942
5362
  // group height is arbitrary), baking in at least marginBottom — same
4943
5363
  // convention as snapped headings — so the following text lands back on
@@ -4965,6 +5385,32 @@ function placeDocumentPass(content, config, cache, options, hints) {
4965
5385
  continue;
4966
5386
  }
4967
5387
  const finalizeListItem = (blk, isFirstPart) => {
5388
+ // The drop cap (#623) stands on the baseline of the line it sinks to,
5389
+ // beside the first fragment only.
5390
+ const capLine = blk.lines[0];
5391
+ if (isFirstPart && capInfo && capLine) {
5392
+ const cap = capInfo;
5393
+ blk.dropCap = {
5394
+ text: cap.text,
5395
+ fontString: cap.fontString,
5396
+ color: cap.color,
5397
+ x: blk.bbox.x + cap.x,
5398
+ baselineY: capLine.baseline + (cap.sink - 1) * style.lineHeightPx,
5399
+ width: cap.width,
5400
+ fontSizePx: cap.fontSizePx,
5401
+ lines: cap.lines,
5402
+ sink: cap.sink,
5403
+ ...(cap.sourceStart !== undefined ? { sourceStart: cap.sourceStart, sourceEnd: cap.sourceEnd } : {}),
5404
+ plainStart: cap.plainStart,
5405
+ plainEnd: cap.plainEnd,
5406
+ word: cap.word,
5407
+ wordRest: cap.wordRest,
5408
+ ...(cap.hang ? { hang: { text: cap.hang.text, fontString: cap.hang.fontString, x: blk.bbox.x + cap.hang.x, baselineY: capLine.baseline, width: cap.hang.width } } : {}),
5409
+ };
5410
+ // The block's source range starts at the initial.
5411
+ if (cap.sourceStart !== undefined && (blk.sourceStart === undefined || cap.sourceStart < blk.sourceStart))
5412
+ blk.sourceStart = cap.sourceStart;
5413
+ }
4968
5414
  if (!listBullet)
4969
5415
  return;
4970
5416
  blk.listDepth = listDepth;
@@ -4979,7 +5425,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
4979
5425
  // `bulletXOffsetInColumn` is `indentPx` for unordered/task, and
4980
5426
  // `indentPx + (maxNumberWidth - thisNumberWidth)` for ordered — giving
4981
5427
  // the right-aligned separator.
4982
- blk.bulletOffsetX = blk.bbox.x + bulletXOffsetInColumn;
5428
+ // Beside a wrapped picture on the left (#627) the marker stands past
5429
+ // it, with its text.
5430
+ blk.bulletOffsetX = blk.bbox.x + bulletXOffsetInColumn + lineInsetsAt(wrapInsets, 0).start;
4983
5431
  if (listBullet.separatorText !== undefined) {
4984
5432
  blk.separatorText = listBullet.separatorText;
4985
5433
  blk.separatorFontString = listBullet.separatorFontString;
@@ -5035,7 +5483,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
5035
5483
  (vdtType === 'paragraph' && isContainerTail && paragraphContainer?.snapToGrid !== false) ||
5036
5484
  // A poem, like a container of one block: its own leading and the
5037
5485
  // margins of its style are off the grid, the text after it is not.
5038
- (vdtType === 'paragraph' && rawBlock.verse !== undefined && verseSnaps(rawBlock.verse.attrs.style, resolved)) ||
5486
+ // A stanza between two of a poem in the line layout snaps too, so the
5487
+ // space between stanzas is whole grid lines (#620); one of a poem set
5488
+ // as single hemistichs (a configuration stored before #620) does not,
5489
+ // as the one block postext 1.22 set it in did not.
5490
+ (vdtType === 'paragraph' && rawBlock.verse !== undefined && verseSnaps(rawBlock.verse.attrs.style, resolved)
5491
+ && (rawBlock.verse.stanza === undefined || rawBlock.verse.stanza.last || versesLineByLine(rawBlock, resolved))) ||
5039
5492
  vdtType === 'mathDisplay');
5040
5493
  // Place block, splitting across columns/pages if needed.
5041
5494
  // List items may split too — orphan/widow protection per-list is gated by
@@ -5072,6 +5525,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
5072
5525
  * default opener paints across the page. */
5073
5526
  let brokenForWidth = measureWidth;
5074
5527
  let laterWidths = [];
5528
+ /** Text wrap (#627): the lines of the block set short beside what text
5529
+ * wraps round, as its lines are now set (paragraph line numbers). */
5530
+ let wrapInsets = [];
5531
+ const wrapsBeside = canSplit && runsBesideWrap(rawBlock);
5075
5532
  const rebreaksForColumn = !tryLoose && rawBlock.toc === undefined && rawBlock.index === undefined
5076
5533
  && (canSplit || (vdtType === 'heading' && !opensDefaultOpener(rawBlock)));
5077
5534
  /** Measure the block again for the columns it is set in (see
@@ -5079,6 +5536,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
5079
5536
  const measureForColumns = (extra) => measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
5080
5537
  styleOverride,
5081
5538
  ...(laterWidths.length > 0 ? { restColumnWidths: laterWidths } : {}),
5539
+ ...(wrapInsets.length > 0 ? { lineInsets: wrapInsets } : {}),
5082
5540
  ...extra,
5083
5541
  });
5084
5542
  // "Keep with next" for colon-introduced lists: a paragraph ending in `:`
@@ -5098,6 +5556,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
5098
5556
  while (remainingLines.length > 0) {
5099
5557
  enterBand(blockIdx, partIndex);
5100
5558
  const curCol = currentColumn(doc, cursor);
5559
+ // A wrapped float at the head or foot of this column (#627): running
5560
+ // text takes its band back to run beside it.
5561
+ if (wrapsBeside)
5562
+ openFloatWraps(curCol);
5101
5563
  // The rest of the block goes into a column of another width than the
5102
5564
  // one it was broken for (EF-157): break it again for this one. Up to
5103
5565
  // postext 1.4 it kept the lines of the column it started in, and a
@@ -5109,8 +5571,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
5109
5571
  // Nothing placed yet: the whole block, for this column.
5110
5572
  const again = measureContentBlock(rawBlock, blockIdx, curCol.bbox.width, blockMeasureCtx, { styleOverride });
5111
5573
  if (again && again.measured.lines.length > 0) {
5574
+ capInfo = again.dropCap;
5112
5575
  brokenForWidth = curCol.bbox.width;
5113
5576
  laterWidths = [];
5577
+ wrapInsets = [];
5114
5578
  letterSpacingPx = again.letterSpacingPx;
5115
5579
  remainingLines = [...raggedLooseLines(again.measured.lines, style.textAlign)];
5116
5580
  paragraphLines = remainingLines;
@@ -5129,8 +5593,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
5129
5593
  const keepBreaks = paragraphBreaks && paragraphBreaks.at.length >= placed
5130
5594
  ? { path: paragraphBreaks.path, at: paragraphBreaks.at.slice(0, placed) }
5131
5595
  : undefined;
5596
+ const keptInsets = insetsBefore(wrapInsets, placed);
5132
5597
  const again = measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
5133
5598
  styleOverride, restColumnWidths: steps, ...(keepBreaks ? { keepBreaks } : {}),
5599
+ ...(keptInsets.length > 0 ? { lineInsets: keptInsets } : {}),
5134
5600
  });
5135
5601
  const lines = again && (again.letterSpacingPx ?? 0) === (letterSpacingPx ?? 0)
5136
5602
  ? [...raggedLooseLines(again.measured.lines, style.textAlign)]
@@ -5138,6 +5604,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
5138
5604
  if (lines && lines.length > placed
5139
5605
  && paragraphLines.slice(0, placed).every((l, i) => lines[i].text === l.text && lines[i].hyphenated === l.hyphenated)) {
5140
5606
  laterWidths = steps;
5607
+ wrapInsets = keptInsets;
5141
5608
  paragraphLines = lines;
5142
5609
  paragraphBreaks = again.measured.breaks;
5143
5610
  remainingLines = lines.slice(placed);
@@ -5244,6 +5711,58 @@ function placeDocumentPass(content, config, cache, options, hints) {
5244
5711
  balancing = { levers: [vdtType === 'heading' ? 'heading' : 'afterFloat'], spaceAbove: extraPx };
5245
5712
  }
5246
5713
  }
5714
+ // A raised initial (#623) keeps its rise clear above the paragraph,
5715
+ // in whole grid lines.
5716
+ if (partIndex === 0 && capInfo && capInfo.rise > 0.5) {
5717
+ spacingBefore += Math.ceil((capInfo.rise - 0.5) / baselineGrid) * baselineGrid;
5718
+ }
5719
+ // Text wrap (#627): the lines this block would set beside what text
5720
+ // wraps round in this column are broken short of it; the lines it
5721
+ // already placed elsewhere keep their breaks. A block that cannot
5722
+ // keep them (a break trace of the other measuring path) goes under
5723
+ // what it would have run beside instead.
5724
+ if (wrapsBeside) {
5725
+ const placed = paragraphLines.length - remainingLines.length;
5726
+ const y0 = columnCursorY(curCol) + spacingBefore;
5727
+ const wanted = wrapInsetSteps(curCol, y0, style.lineHeightPx, placed);
5728
+ if (!sameInsetsFrom(wrapInsets, wanted, placed)) {
5729
+ const steps = [...insetsBefore(wrapInsets, placed), ...wanted];
5730
+ const keepBreaks = placed > 0 && paragraphBreaks && paragraphBreaks.at.length >= placed
5731
+ ? { path: paragraphBreaks.path, at: paragraphBreaks.at.slice(0, placed) }
5732
+ : undefined;
5733
+ const again = placed > 0 && !keepBreaks ? null : measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
5734
+ styleOverride,
5735
+ ...(laterWidths.length > 0 ? { restColumnWidths: laterWidths } : {}),
5736
+ ...(steps.length > 0 ? { lineInsets: steps } : {}),
5737
+ ...(keepBreaks ? { keepBreaks } : {}),
5738
+ });
5739
+ const lines = again && (placed === 0 || (again.letterSpacingPx ?? 0) === (letterSpacingPx ?? 0))
5740
+ ? [...raggedLooseLines(again.measured.lines, style.textAlign)]
5741
+ : undefined;
5742
+ if (lines && lines.length > placed
5743
+ && paragraphLines.slice(0, placed).every((l, i) => lines[i].text === l.text && lines[i].hyphenated === l.hyphenated)) {
5744
+ wrapInsets = steps;
5745
+ paragraphLines = lines;
5746
+ paragraphBreaks = again.measured.breaks;
5747
+ remainingLines = lines.slice(placed);
5748
+ if (placed === 0) {
5749
+ capInfo = again.dropCap;
5750
+ letterSpacingPx = again.letterSpacingPx;
5751
+ guardedEnds = [];
5752
+ columnEndHyphenRetried = false;
5753
+ }
5754
+ regateCitedFloats(blockIdx, lines);
5755
+ }
5756
+ else if (wanted.length > 0) {
5757
+ if (balancing && balancing.spaceAbove > 0) {
5758
+ balanceExtraInColumn.set(curCol, (balanceExtraInColumn.get(curCol) ?? 0) - balancing.spaceAbove);
5759
+ }
5760
+ settleColumnWraps(curCol);
5761
+ pendingSpacing = 0;
5762
+ continue;
5763
+ }
5764
+ }
5765
+ }
5247
5766
  // A loose paragraph's extra line is balancing height too (it lands
5248
5767
  // whole in this column — loose candidates are never split parts).
5249
5768
  const looseTracking = looseOutcome.get(blockIdx);
@@ -5281,6 +5800,11 @@ function placeDocumentPass(content, config, cache, options, hints) {
5281
5800
  // actual content bottom so the reserved block height (and the
5282
5801
  // subsequent marginBottom + grid snap) starts from there.
5283
5802
  let effectiveRemainHeight = totalRemainHeight;
5803
+ // A paragraph shorter than its drop cap sinks (#623, `'reserve'`):
5804
+ // the block keeps the room under the initial.
5805
+ if (partIndex === 0 && capInfo && capInfo.minLines > remainingLines.length) {
5806
+ effectiveRemainHeight = capInfo.minLines * style.lineHeightPx;
5807
+ }
5284
5808
  /** How far the lines of a 行取り heading go down its band (#424). */
5285
5809
  let lineSpanShift = 0;
5286
5810
  if (lineSpan !== undefined && partIndex === 0) {
@@ -5557,9 +6081,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
5557
6081
  ? Math.ceil((naturalBottom - 0.01) / baselineGrid) * baselineGrid
5558
6082
  : naturalBottom;
5559
6083
  const remainAfterHeading = curCol.bbox.height - snappedBottom;
5560
- const minLinesNeeded = resolved.bodyText.avoidWidows
5561
- ? Math.max(1, resolved.bodyText.widowMinLines)
5562
- : 1;
6084
+ // A drop cap opening the text (#623) needs its lines under it.
6085
+ const minLinesNeeded = Math.max(resolved.bodyText.avoidWidows ? Math.max(1, resolved.bodyText.widowMinLines) : 1, dropCapSinkAfter(blockIdx, blockMeasureCtx));
5563
6086
  // A `:::space` between the heading and its text needs room too.
5564
6087
  const minSpaceAfter = minLinesNeeded * bodyStyle.lineHeightPx
5565
6088
  + spaceLinesAfter(contentBlocks, blockIdx) * baselineGrid;
@@ -5725,7 +6248,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
5725
6248
  // (the no-fit branch below rolls it back).
5726
6249
  const headingRun = partIndex === 0 ? trailingHeadingRun(curCol) : 0;
5727
6250
  if (choice.splitAt === 0 && headingRun > 0 && headingRun < curCol.blocks.length) {
5728
- const minKeep = effectiveAvoidWidows ? Math.max(1, resolved.bodyText.widowMinLines) : 1;
6251
+ // A drop cap's lines stay with it (#623).
6252
+ const minKeep = Math.max(effectiveAvoidWidows ? Math.max(1, resolved.bodyText.widowMinLines) : 1, partIndex === 0 && capInfo ? capInfo.sink : 1);
5729
6253
  const minTail = effectiveAvoidOrphans && resolved.headings.keepWithNextSplit === 'rules'
5730
6254
  ? Math.max(1, resolved.bodyText.orphanMinLines)
5731
6255
  : 1;
@@ -5848,11 +6372,27 @@ function placeDocumentPass(content, config, cache, options, hints) {
5848
6372
  }
5849
6373
  }
5850
6374
  // A poem's bayt set staggered over two lines (or more) stays in one
5851
- // column (#378): the cut goes before it — in an empty column, which
5852
- // the poem cannot leave, after its first line at least.
5853
- while (choice.splitAt > (curCol.blocks.length === 0 ? 1 : 0) && sameBayt(remainingLines[choice.splitAt - 1], remainingLines[choice.splitAt])) {
6375
+ // column (#378), and so does a line of verse with its turnovers
6376
+ // (#620): the cut goes before it — in an empty column, which the
6377
+ // poem cannot leave, after its first line at least.
6378
+ while (choice.splitAt > (curCol.blocks.length === 0 ? 1 : 0) && sameVerseLine(remainingLines[choice.splitAt - 1], remainingLines[choice.splitAt])) {
5854
6379
  choice = { splitAt: choice.splitAt - 1, demerit: choice.demerit };
5855
6380
  }
6381
+ // A paragraph a drop cap opens never breaks before the initial's
6382
+ // last line (#623): it moves on whole, unless it is alone in an
6383
+ // empty column too short for those lines, where it breaks all the
6384
+ // same (and says so).
6385
+ if (partIndex === 0 && capInfo && choice.splitAt > 0 && choice.splitAt < Math.min(capInfo.sink, remainingLines.length)) {
6386
+ if (curCol.blocks.length > 0)
6387
+ choice = { splitAt: 0, demerit: choice.demerit };
6388
+ else
6389
+ blockMeasureCtx.dropCapNotes?.set(blockIdx, { reason: 'split' });
6390
+ }
6391
+ // A stanza `keepStanzas` covers (a haiku, a tanka) moves on whole,
6392
+ // unless the column is empty: it cannot do better anywhere.
6393
+ if (choice.splitAt > 0 && partIndex === 0 && curCol.blocks.length > 0 && keepsStanzaWhole(rawBlock, resolved)) {
6394
+ choice = { splitAt: 0, demerit: choice.demerit };
6395
+ }
5856
6396
  if (choice.splitAt > 0) {
5857
6397
  // Consume spacing (negative: a container margin pulling the block up)
5858
6398
  if (spacingBefore !== 0) {
@@ -6121,6 +6661,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
6121
6661
  .map((s) => s.startPageIndex);
6122
6662
  if (restarts.length > 0)
6123
6663
  doc.pageNumberRestarts = restarts;
6664
+ // Line numbers (#621): the lines of this pass counted where they landed,
6665
+ // before the running heads (whose palettes recolour them).
6666
+ buildLineNumbers(doc, contentBlocks, continuation);
6124
6667
  buildHeadersAndFooters(doc, resourceById, {
6125
6668
  // Where two palette entries share a base value, each kind of flow
6126
6669
  // colour follows its own settings under a part or section palette.
@@ -6137,6 +6680,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
6137
6680
  return text.length > 0 ? { styleId, text } : undefined;
6138
6681
  },
6139
6682
  });
6683
+ // The line numbers of code listings (#624), in the same slot, after the
6684
+ // margin numbers' palette pass: they keep `codeStyle.lineNumberColor`.
6685
+ if (codeBoxes.wrapped)
6686
+ buildCodeLineNumbers(doc);
6140
6687
  // Vertical pages: the axis each font's upright characters turn about.
6141
6688
  if (doc.pages.some((p) => pageIsVertical(p)))
6142
6689
  stampCentralBaselines(doc);
@@ -6148,6 +6695,17 @@ function placeDocumentPass(content, config, cache, options, hints) {
6148
6695
  doc.iterationCount = 1;
6149
6696
  if (joiningLetterSpacing.size > 0)
6150
6697
  joiningSpacingWarnings.set(doc, joiningLetterSpacingWarnings(doc, joiningLetterSpacing));
6698
+ if (dropCapNotes.size > 0)
6699
+ dropCapWarningsByDoc.set(doc, dropCapWarnings(doc, dropCapNotes));
6700
+ // Pictures scaled to the room of their slot (#626), at the block citing them.
6701
+ const shrunk = floatShrinkWarnings(doc, (idx) => {
6702
+ const b = contentBlocks[idx];
6703
+ return b ? { start: b.sourceStart + bodyOffset, end: b.sourceEnd + bodyOffset } : undefined;
6704
+ });
6705
+ if (shrunk.length > 0)
6706
+ floatShrinkWarningsByDoc.set(doc, shrunk);
6707
+ if (textWrapNotes.length > 0)
6708
+ textWrapWarningsByDoc.set(doc, textWrapNotes);
6151
6709
  return { doc, forcedBreakPages, bandCapProposals, spanPlacedInBand, bandCapsApplied, looseOutcome, captionUnderProposals };
6152
6710
  }
6153
6711
  /** Passes a document printing its own contents gets at most, beyond the
@@ -6207,7 +6765,10 @@ export function* buildDocumentGen(content, config, cache, options) {
6207
6765
  // Justified CJK lines the composer could not fill within its tracking
6208
6766
  // cap; words of a joining script that run past their line, and styles
6209
6767
  // whose letter-spacing such words do not take.
6210
- const loose = [...cjkLooseLineWarnings(doc), ...wordOverflowWarnings(doc), ...(joiningSpacingWarnings.get(doc) ?? [])];
6768
+ // Line numbers set over a float of the side column (#621).
6769
+ const loose = [...cjkLooseLineWarnings(doc), ...wordOverflowWarnings(doc), ...(joiningSpacingWarnings.get(doc) ?? []), ...(dropCapWarningsByDoc.get(doc) ?? []), ...lineNumberWarnings(doc), ...codeOverflowWarnings(doc), ...(floatShrinkWarningsByDoc.get(doc) ?? []), ...(textWrapWarningsByDoc.get(doc) ?? [])];
6770
+ // Design texts cut to fit their width (#628).
6771
+ loose.push(...designTruncationWarnings(doc));
6211
6772
  // Comic panels whose cell cannot hold their picture's safe area (#556).
6212
6773
  for (const page of doc.pages) {
6213
6774
  if (page.comic)
@@ -6250,10 +6811,16 @@ const indexWarnings = new WeakMap();
6250
6811
  /** The `joiningScriptLetterSpacing` warnings of the last pass, per
6251
6812
  * document. */
6252
6813
  const joiningSpacingWarnings = new WeakMap();
6814
+ /** The `dropCap` warnings of the last pass, per document (#623). */
6815
+ const dropCapWarningsByDoc = new WeakMap();
6816
+ /** The `floatShrunk` warnings of a build (#626), found by its last pass. */
6817
+ const floatShrinkWarningsByDoc = new WeakMap();
6818
+ /** The `textWrap` warnings of a build (#627), found by its last pass. */
6819
+ const textWrapWarningsByDoc = new WeakMap();
6253
6820
  /** `doc` with the page of each of its index marks (`doc.indexMarks`). */
6254
6821
  function withIndexMarks(doc, content) {
6255
6822
  const { content: body, contentOffset } = extractFrontmatter(content.markdown);
6256
- const blocks = parseMarkdownMemo(body);
6823
+ const blocks = parseMarkdownMemo(body, codeParseOptions(doc.config));
6257
6824
  if (content.markdown.includes(':index')) {
6258
6825
  const marks = locateIndexMarks(doc, blocks, contentOffset);
6259
6826
  if (marks)
@@ -6274,7 +6841,7 @@ function* buildDocumentRounds(content, config, cache, options) {
6274
6841
  // follows, and a numbering restart after the front matter usually settles
6275
6842
  // it in one extra round.
6276
6843
  if (content.outline === undefined) {
6277
- const parsed = parseMarkdownMemo(extractFrontmatter(content.markdown).content);
6844
+ const parsed = parseMarkdownMemo(extractFrontmatter(content.markdown).content, codeParseOptions(resolveAllConfig(config)));
6278
6845
  // A reference printing an anchor's page (#263) settles the same way.
6279
6846
  const pageRefs = () => {
6280
6847
  if (!hasAnchorRefs(parsed, new Set((content.resources ?? []).map((r) => r.id))))
@@ -6804,6 +7371,21 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0, f
6804
7371
  function sameBayt(a, b) {
6805
7372
  return a?.verse !== undefined && b?.verse !== undefined && a.verse.bayt === b.verse.bayt;
6806
7373
  }
7374
+ /** Whether a column may not break between two lines of verse: they set one
7375
+ * bayt (#378), or `b` is a turnover of the line of verse `a` sets or
7376
+ * turns over (#620). */
7377
+ function sameVerseLine(a, b) {
7378
+ return sameBayt(a, b) || (a?.verseLine !== undefined && b?.verseLine?.turnover === true);
7379
+ }
7380
+ /** Whether a stanza of the line layout is kept whole (#620): it has no
7381
+ * more lines of verse than `keepStanzas` asks for. */
7382
+ function keepsStanzaWhole(raw, resolved) {
7383
+ const stanza = raw.verse?.stanza;
7384
+ if (!stanza || !versesLineByLine(raw, resolved))
7385
+ return false;
7386
+ const keep = verseLinesSettings(raw.verse.attrs, { fontSizePx: 0, lineHeightPx: 0 }, resolved, false).keepStanzas;
7387
+ return keep > 0 && stanza.lines.length <= keep;
7388
+ }
6807
7389
  /** Whether the text after a poem goes back to the baseline grid: unless
6808
7390
  * the paragraph style its fence names says `snapToGrid: false`. */
6809
7391
  function verseSnaps(styleId, resolved) {