@react-x11/components 0.9.0 → 0.10.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 (550) hide show
  1. package/README.md +10 -4
  2. package/dist/charts/node.d.ts +11 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +44 -14
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/charts/render.d.ts +12 -3
  7. package/dist/charts/render.d.ts.map +1 -1
  8. package/dist/charts/render.js +94 -25
  9. package/dist/charts/render.js.map +1 -1
  10. package/dist/code/index.d.ts.map +1 -1
  11. package/dist/code/index.js +3 -1
  12. package/dist/code/index.js.map +1 -1
  13. package/dist/code-editor/completion.d.ts.map +1 -1
  14. package/dist/code-editor/completion.js +3 -2
  15. package/dist/code-editor/completion.js.map +1 -1
  16. package/dist/code-editor/index.d.ts.map +1 -1
  17. package/dist/code-editor/index.js +19 -0
  18. package/dist/code-editor/index.js.map +1 -1
  19. package/dist/code-editor/node.d.ts +177 -7
  20. package/dist/code-editor/node.d.ts.map +1 -1
  21. package/dist/code-editor/node.js +1329 -164
  22. package/dist/code-editor/node.js.map +1 -1
  23. package/dist/code-language/languages/glsl.d.ts.map +1 -1
  24. package/dist/code-language/languages/glsl.js +10 -2
  25. package/dist/code-language/languages/glsl.js.map +1 -1
  26. package/dist/code-language/languages/javascript.d.ts.map +1 -1
  27. package/dist/code-language/languages/javascript.js +18 -3
  28. package/dist/code-language/languages/javascript.js.map +1 -1
  29. package/dist/code-language/languages/json.d.ts.map +1 -1
  30. package/dist/code-language/languages/json.js +10 -2
  31. package/dist/code-language/languages/json.js.map +1 -1
  32. package/dist/code-language/languages/shell.d.ts.map +1 -1
  33. package/dist/code-language/languages/shell.js +10 -2
  34. package/dist/code-language/languages/shell.js.map +1 -1
  35. package/dist/code-language/languages/sql.d.ts.map +1 -1
  36. package/dist/code-language/languages/sql.js +20 -2
  37. package/dist/code-language/languages/sql.js.map +1 -1
  38. package/dist/code-language/stream.d.ts +22 -0
  39. package/dist/code-language/stream.d.ts.map +1 -1
  40. package/dist/code-language/stream.js +259 -9
  41. package/dist/code-language/stream.js.map +1 -1
  42. package/dist/code-language/types.d.ts +5 -4
  43. package/dist/code-language/types.d.ts.map +1 -1
  44. package/dist/codeblock/index.d.ts +13 -2
  45. package/dist/codeblock/index.d.ts.map +1 -1
  46. package/dist/codeblock/index.js +20 -3
  47. package/dist/codeblock/index.js.map +1 -1
  48. package/dist/flow/draw.d.ts +50 -0
  49. package/dist/flow/draw.d.ts.map +1 -1
  50. package/dist/flow/draw.js +132 -33
  51. package/dist/flow/draw.js.map +1 -1
  52. package/dist/flow/gl/color.d.ts +13 -0
  53. package/dist/flow/gl/color.d.ts.map +1 -0
  54. package/dist/flow/gl/color.js +82 -0
  55. package/dist/flow/gl/color.js.map +1 -0
  56. package/dist/flow/gl/index.d.ts +60 -0
  57. package/dist/flow/gl/index.d.ts.map +1 -0
  58. package/dist/flow/gl/index.js +283 -0
  59. package/dist/flow/gl/index.js.map +1 -0
  60. package/dist/flow/gl/pack.d.ts +133 -0
  61. package/dist/flow/gl/pack.d.ts.map +1 -0
  62. package/dist/flow/gl/pack.js +408 -0
  63. package/dist/flow/gl/pack.js.map +1 -0
  64. package/dist/flow/gl/renderer.d.ts +95 -0
  65. package/dist/flow/gl/renderer.d.ts.map +1 -0
  66. package/dist/flow/gl/renderer.js +459 -0
  67. package/dist/flow/gl/renderer.js.map +1 -0
  68. package/dist/flow/gl/shaders.d.ts +79 -0
  69. package/dist/flow/gl/shaders.d.ts.map +1 -0
  70. package/dist/flow/gl/shaders.js +346 -0
  71. package/dist/flow/gl/shaders.js.map +1 -0
  72. package/dist/flow/gl/text.d.ts +318 -0
  73. package/dist/flow/gl/text.d.ts.map +1 -0
  74. package/dist/flow/gl/text.js +1103 -0
  75. package/dist/flow/gl/text.js.map +1 -0
  76. package/dist/flow/index.d.ts +1 -1
  77. package/dist/flow/index.d.ts.map +1 -1
  78. package/dist/flow/index.js +619 -64
  79. package/dist/flow/index.js.map +1 -1
  80. package/dist/flow/node.d.ts +621 -72
  81. package/dist/flow/node.d.ts.map +1 -1
  82. package/dist/flow/node.js +2253 -1018
  83. package/dist/flow/node.js.map +1 -1
  84. package/dist/flow/paint.d.ts +35 -0
  85. package/dist/flow/paint.d.ts.map +1 -0
  86. package/dist/flow/paint.js +393 -0
  87. package/dist/flow/paint.js.map +1 -0
  88. package/dist/flow/paths.d.ts +14 -0
  89. package/dist/flow/paths.d.ts.map +1 -1
  90. package/dist/flow/paths.js +45 -19
  91. package/dist/flow/paths.js.map +1 -1
  92. package/dist/flow/scene.d.ts +584 -0
  93. package/dist/flow/scene.d.ts.map +1 -0
  94. package/dist/flow/scene.js +1453 -0
  95. package/dist/flow/scene.js.map +1 -0
  96. package/dist/flow/types.d.ts +111 -1
  97. package/dist/flow/types.d.ts.map +1 -1
  98. package/dist/html/controls.d.ts +28 -0
  99. package/dist/html/controls.d.ts.map +1 -1
  100. package/dist/html/controls.js +52 -2
  101. package/dist/html/controls.js.map +1 -1
  102. package/dist/html/css/calc.d.ts +14 -0
  103. package/dist/html/css/calc.d.ts.map +1 -0
  104. package/dist/html/css/calc.js +344 -0
  105. package/dist/html/css/calc.js.map +1 -0
  106. package/dist/html/css/cascade.d.ts +240 -6
  107. package/dist/html/css/cascade.d.ts.map +1 -1
  108. package/dist/html/css/cascade.js +1327 -66
  109. package/dist/html/css/cascade.js.map +1 -1
  110. package/dist/html/css/color.d.ts +23 -0
  111. package/dist/html/css/color.d.ts.map +1 -0
  112. package/dist/html/css/color.js +740 -0
  113. package/dist/html/css/color.js.map +1 -0
  114. package/dist/html/css/content.d.ts +92 -0
  115. package/dist/html/css/content.d.ts.map +1 -0
  116. package/dist/html/css/content.js +388 -0
  117. package/dist/html/css/content.js.map +1 -0
  118. package/dist/html/css/counter-styles.d.ts +76 -0
  119. package/dist/html/css/counter-styles.d.ts.map +1 -0
  120. package/dist/html/css/counter-styles.js +893 -0
  121. package/dist/html/css/counter-styles.js.map +1 -0
  122. package/dist/html/css/decode.d.ts +12 -0
  123. package/dist/html/css/decode.d.ts.map +1 -0
  124. package/dist/html/css/decode.js +116 -0
  125. package/dist/html/css/decode.js.map +1 -0
  126. package/dist/html/css/parse.d.ts +110 -10
  127. package/dist/html/css/parse.d.ts.map +1 -1
  128. package/dist/html/css/parse.js +1473 -180
  129. package/dist/html/css/parse.js.map +1 -1
  130. package/dist/html/css/style.d.ts +498 -15
  131. package/dist/html/css/style.d.ts.map +1 -1
  132. package/dist/html/css/style.js +3698 -206
  133. package/dist/html/css/style.js.map +1 -1
  134. package/dist/html/css/ua.d.ts.map +1 -1
  135. package/dist/html/css/ua.js +57 -11
  136. package/dist/html/css/ua.js.map +1 -1
  137. package/dist/html/css/values.d.ts +37 -3
  138. package/dist/html/css/values.d.ts.map +1 -1
  139. package/dist/html/css/values.js +122 -48
  140. package/dist/html/css/values.js.map +1 -1
  141. package/dist/html/css/vars.d.ts +52 -0
  142. package/dist/html/css/vars.d.ts.map +1 -0
  143. package/dist/html/css/vars.js +358 -0
  144. package/dist/html/css/vars.js.map +1 -0
  145. package/dist/html/dom.d.ts +30 -4
  146. package/dist/html/dom.d.ts.map +1 -1
  147. package/dist/html/dom.js +278 -17
  148. package/dist/html/dom.js.map +1 -1
  149. package/dist/html/fonts.d.ts +106 -0
  150. package/dist/html/fonts.d.ts.map +1 -0
  151. package/dist/html/fonts.js +565 -0
  152. package/dist/html/fonts.js.map +1 -0
  153. package/dist/html/gif.d.ts +15 -0
  154. package/dist/html/gif.d.ts.map +1 -0
  155. package/dist/html/gif.js +229 -0
  156. package/dist/html/gif.js.map +1 -0
  157. package/dist/html/index.d.ts +50 -11
  158. package/dist/html/index.d.ts.map +1 -1
  159. package/dist/html/index.js +115 -9
  160. package/dist/html/index.js.map +1 -1
  161. package/dist/html/layout/block.d.ts +226 -3
  162. package/dist/html/layout/block.d.ts.map +1 -1
  163. package/dist/html/layout/block.js +2937 -197
  164. package/dist/html/layout/block.js.map +1 -1
  165. package/dist/html/layout/boxes.d.ts +340 -17
  166. package/dist/html/layout/boxes.d.ts.map +1 -1
  167. package/dist/html/layout/boxes.js +1714 -182
  168. package/dist/html/layout/boxes.js.map +1 -1
  169. package/dist/html/layout/cache.d.ts +58 -0
  170. package/dist/html/layout/cache.d.ts.map +1 -0
  171. package/dist/html/layout/cache.js +224 -0
  172. package/dist/html/layout/cache.js.map +1 -0
  173. package/dist/html/layout/collapse.d.ts +45 -0
  174. package/dist/html/layout/collapse.d.ts.map +1 -0
  175. package/dist/html/layout/collapse.js +246 -0
  176. package/dist/html/layout/collapse.js.map +1 -0
  177. package/dist/html/layout/css-grid.d.ts +7 -0
  178. package/dist/html/layout/css-grid.d.ts.map +1 -0
  179. package/dist/html/layout/css-grid.js +900 -0
  180. package/dist/html/layout/css-grid.js.map +1 -0
  181. package/dist/html/layout/flex.d.ts.map +1 -1
  182. package/dist/html/layout/flex.js +1025 -89
  183. package/dist/html/layout/flex.js.map +1 -1
  184. package/dist/html/layout/floats.d.ts +45 -6
  185. package/dist/html/layout/floats.d.ts.map +1 -1
  186. package/dist/html/layout/floats.js +113 -14
  187. package/dist/html/layout/floats.js.map +1 -1
  188. package/dist/html/layout/grid-lines.d.ts +39 -0
  189. package/dist/html/layout/grid-lines.d.ts.map +1 -0
  190. package/dist/html/layout/grid-lines.js +119 -0
  191. package/dist/html/layout/grid-lines.js.map +1 -0
  192. package/dist/html/layout/grid.d.ts +53 -0
  193. package/dist/html/layout/grid.d.ts.map +1 -0
  194. package/dist/html/layout/grid.js +153 -0
  195. package/dist/html/layout/grid.js.map +1 -0
  196. package/dist/html/layout/inline.d.ts +151 -1
  197. package/dist/html/layout/inline.d.ts.map +1 -1
  198. package/dist/html/layout/inline.js +3375 -174
  199. package/dist/html/layout/inline.js.map +1 -1
  200. package/dist/html/layout/shaping.d.ts +6 -0
  201. package/dist/html/layout/shaping.d.ts.map +1 -0
  202. package/dist/html/layout/shaping.js +100 -0
  203. package/dist/html/layout/shaping.js.map +1 -0
  204. package/dist/html/layout/table.d.ts +3 -0
  205. package/dist/html/layout/table.d.ts.map +1 -1
  206. package/dist/html/layout/table.js +681 -175
  207. package/dist/html/layout/table.js.map +1 -1
  208. package/dist/html/layout/tracks.d.ts +44 -0
  209. package/dist/html/layout/tracks.d.ts.map +1 -0
  210. package/dist/html/layout/tracks.js +349 -0
  211. package/dist/html/layout/tracks.js.map +1 -0
  212. package/dist/html/node.d.ts +169 -4
  213. package/dist/html/node.d.ts.map +1 -1
  214. package/dist/html/node.js +1331 -100
  215. package/dist/html/node.js.map +1 -1
  216. package/dist/html/paint.d.ts +92 -3
  217. package/dist/html/paint.d.ts.map +1 -1
  218. package/dist/html/paint.js +3503 -141
  219. package/dist/html/paint.js.map +1 -1
  220. package/dist/html/resources.d.ts +60 -14
  221. package/dist/html/resources.d.ts.map +1 -1
  222. package/dist/html/resources.js +122 -36
  223. package/dist/html/resources.js.map +1 -1
  224. package/dist/html/surfaces.d.ts +23 -0
  225. package/dist/html/surfaces.d.ts.map +1 -0
  226. package/dist/html/surfaces.js +68 -0
  227. package/dist/html/surfaces.js.map +1 -0
  228. package/dist/html/svg.d.ts +80 -0
  229. package/dist/html/svg.d.ts.map +1 -0
  230. package/dist/html/svg.js +435 -0
  231. package/dist/html/svg.js.map +1 -0
  232. package/dist/html/url.d.ts +23 -0
  233. package/dist/html/url.d.ts.map +1 -0
  234. package/dist/html/url.js +80 -0
  235. package/dist/html/url.js.map +1 -0
  236. package/dist/index.d.ts +1 -1
  237. package/dist/index.d.ts.map +1 -1
  238. package/dist/index.js.map +1 -1
  239. package/dist/internal/collate.d.ts +3 -0
  240. package/dist/internal/collate.d.ts.map +1 -0
  241. package/dist/internal/collate.js +11 -0
  242. package/dist/internal/collate.js.map +1 -0
  243. package/dist/internal/markdown/ast.d.ts +11 -0
  244. package/dist/internal/markdown/ast.d.ts.map +1 -1
  245. package/dist/internal/markdown/parse.d.ts.map +1 -1
  246. package/dist/internal/markdown/parse.js +109 -8
  247. package/dist/internal/markdown/parse.js.map +1 -1
  248. package/dist/internal/prewarm.d.ts +18 -0
  249. package/dist/internal/prewarm.d.ts.map +1 -0
  250. package/dist/internal/prewarm.js +42 -0
  251. package/dist/internal/prewarm.js.map +1 -0
  252. package/dist/internal/sdf.d.ts +20 -0
  253. package/dist/internal/sdf.d.ts.map +1 -0
  254. package/dist/internal/sdf.js +216 -0
  255. package/dist/internal/sdf.js.map +1 -0
  256. package/dist/internal/splice.d.ts +12 -0
  257. package/dist/internal/splice.d.ts.map +1 -0
  258. package/dist/internal/splice.js +23 -0
  259. package/dist/internal/splice.js.map +1 -0
  260. package/dist/internal/window.d.ts +12 -0
  261. package/dist/internal/window.d.ts.map +1 -1
  262. package/dist/internal/window.js +23 -6
  263. package/dist/internal/window.js.map +1 -1
  264. package/dist/maps/anchors.d.ts +6 -1
  265. package/dist/maps/anchors.d.ts.map +1 -1
  266. package/dist/maps/anchors.js +260 -1
  267. package/dist/maps/anchors.js.map +1 -1
  268. package/dist/maps/controller.d.ts +57 -0
  269. package/dist/maps/controller.d.ts.map +1 -1
  270. package/dist/maps/controller.js +97 -18
  271. package/dist/maps/controller.js.map +1 -1
  272. package/dist/maps/gl/buckets.d.ts +5 -1
  273. package/dist/maps/gl/buckets.d.ts.map +1 -1
  274. package/dist/maps/gl/buckets.js +5 -2
  275. package/dist/maps/gl/buckets.js.map +1 -1
  276. package/dist/maps/gl/build-worker.d.ts +2 -0
  277. package/dist/maps/gl/build-worker.d.ts.map +1 -1
  278. package/dist/maps/gl/build-worker.js +1 -1
  279. package/dist/maps/gl/build-worker.js.map +1 -1
  280. package/dist/maps/gl/cover.d.ts +23 -4
  281. package/dist/maps/gl/cover.d.ts.map +1 -1
  282. package/dist/maps/gl/cover.js +61 -48
  283. package/dist/maps/gl/cover.js.map +1 -1
  284. package/dist/maps/gl/placement.d.ts +21 -8
  285. package/dist/maps/gl/placement.d.ts.map +1 -1
  286. package/dist/maps/gl/placement.js +133 -58
  287. package/dist/maps/gl/placement.js.map +1 -1
  288. package/dist/maps/gl/renderer.d.ts +36 -4
  289. package/dist/maps/gl/renderer.d.ts.map +1 -1
  290. package/dist/maps/gl/renderer.js +81 -24
  291. package/dist/maps/gl/renderer.js.map +1 -1
  292. package/dist/maps/gl/shaders.d.ts +18 -17
  293. package/dist/maps/gl/shaders.d.ts.map +1 -1
  294. package/dist/maps/gl/shaders.js +34 -32
  295. package/dist/maps/gl/shaders.js.map +1 -1
  296. package/dist/maps/gl/store.d.ts +10 -0
  297. package/dist/maps/gl/store.d.ts.map +1 -1
  298. package/dist/maps/gl/store.js +47 -4
  299. package/dist/maps/gl/store.js.map +1 -1
  300. package/dist/maps/gl/text.d.ts +153 -24
  301. package/dist/maps/gl/text.d.ts.map +1 -1
  302. package/dist/maps/gl/text.js +379 -63
  303. package/dist/maps/gl/text.js.map +1 -1
  304. package/dist/maps/gl/view.d.ts +293 -2
  305. package/dist/maps/gl/view.d.ts.map +1 -1
  306. package/dist/maps/gl/view.js +317 -82
  307. package/dist/maps/gl/view.js.map +1 -1
  308. package/dist/maps/icons.d.ts +32 -0
  309. package/dist/maps/icons.d.ts.map +1 -0
  310. package/dist/maps/icons.js +151 -0
  311. package/dist/maps/icons.js.map +1 -0
  312. package/dist/maps/index.d.ts +2 -0
  313. package/dist/maps/index.d.ts.map +1 -1
  314. package/dist/maps/index.js +2 -0
  315. package/dist/maps/index.js.map +1 -1
  316. package/dist/maps/labels.d.ts +56 -4
  317. package/dist/maps/labels.d.ts.map +1 -1
  318. package/dist/maps/labels.js +213 -46
  319. package/dist/maps/labels.js.map +1 -1
  320. package/dist/maps/node.d.ts +17 -0
  321. package/dist/maps/node.d.ts.map +1 -1
  322. package/dist/maps/node.js +64 -30
  323. package/dist/maps/node.js.map +1 -1
  324. package/dist/maps/proj.d.ts +48 -0
  325. package/dist/maps/proj.d.ts.map +1 -1
  326. package/dist/maps/proj.js +73 -0
  327. package/dist/maps/proj.js.map +1 -1
  328. package/dist/maps/sources.d.ts +5 -3
  329. package/dist/maps/sources.d.ts.map +1 -1
  330. package/dist/maps/sources.js +5 -3
  331. package/dist/maps/sources.js.map +1 -1
  332. package/dist/maps/style.d.ts +35 -0
  333. package/dist/maps/style.d.ts.map +1 -1
  334. package/dist/maps/style.js.map +1 -1
  335. package/dist/maps/styles.d.ts +12 -0
  336. package/dist/maps/styles.d.ts.map +1 -1
  337. package/dist/maps/styles.js +120 -6
  338. package/dist/maps/styles.js.map +1 -1
  339. package/dist/maps/tiles.d.ts +31 -11
  340. package/dist/maps/tiles.d.ts.map +1 -1
  341. package/dist/maps/tiles.js +61 -27
  342. package/dist/maps/tiles.js.map +1 -1
  343. package/dist/maps/types.d.ts +28 -4
  344. package/dist/maps/types.d.ts.map +1 -1
  345. package/dist/markdown/index.d.ts.map +1 -1
  346. package/dist/markdown/index.js +112 -43
  347. package/dist/markdown/index.js.map +1 -1
  348. package/dist/markdown/spans.d.ts.map +1 -1
  349. package/dist/markdown/spans.js +6 -0
  350. package/dist/markdown/spans.js.map +1 -1
  351. package/dist/qml/containers.d.ts.map +1 -1
  352. package/dist/qml/containers.js +6 -1
  353. package/dist/qml/containers.js.map +1 -1
  354. package/dist/qml/interaction.d.ts.map +1 -1
  355. package/dist/qml/interaction.js +38 -5
  356. package/dist/qml/interaction.js.map +1 -1
  357. package/dist/qml/layouts.d.ts.map +1 -1
  358. package/dist/qml/layouts.js +21 -6
  359. package/dist/qml/layouts.js.map +1 -1
  360. package/dist/qml/qtquick.d.ts.map +1 -1
  361. package/dist/qml/qtquick.js +9 -2
  362. package/dist/qml/qtquick.js.map +1 -1
  363. package/dist/qml/react.d.ts.map +1 -1
  364. package/dist/qml/react.js +9 -7
  365. package/dist/qml/react.js.map +1 -1
  366. package/dist/reorder/clock.d.ts +7 -0
  367. package/dist/reorder/clock.d.ts.map +1 -0
  368. package/dist/reorder/clock.js +26 -0
  369. package/dist/reorder/clock.js.map +1 -0
  370. package/dist/reorder/index.d.ts +25 -3
  371. package/dist/reorder/index.d.ts.map +1 -1
  372. package/dist/reorder/index.js +64 -19
  373. package/dist/reorder/index.js.map +1 -1
  374. package/dist/rich-text-editor/index.d.ts +0 -11
  375. package/dist/rich-text-editor/index.d.ts.map +1 -1
  376. package/dist/rich-text-editor/index.js +35 -1
  377. package/dist/rich-text-editor/index.js.map +1 -1
  378. package/dist/rich-text-editor/keymap.d.ts.map +1 -1
  379. package/dist/rich-text-editor/keymap.js +2 -1
  380. package/dist/rich-text-editor/keymap.js.map +1 -1
  381. package/dist/rich-text-editor/keys.d.ts +17 -6
  382. package/dist/rich-text-editor/keys.d.ts.map +1 -1
  383. package/dist/rich-text-editor/keys.js +135 -26
  384. package/dist/rich-text-editor/keys.js.map +1 -1
  385. package/dist/rich-text-editor/marks.d.ts +9 -0
  386. package/dist/rich-text-editor/marks.d.ts.map +1 -0
  387. package/dist/rich-text-editor/marks.js +83 -0
  388. package/dist/rich-text-editor/marks.js.map +1 -0
  389. package/dist/rich-text-editor/render.d.ts.map +1 -1
  390. package/dist/rich-text-editor/render.js +28 -10
  391. package/dist/rich-text-editor/render.js.map +1 -1
  392. package/dist/rich-text-editor/toolbar.d.ts.map +1 -1
  393. package/dist/rich-text-editor/toolbar.js +1 -1
  394. package/dist/rich-text-editor/toolbar.js.map +1 -1
  395. package/dist/rich-text-editor/virtual.d.ts.map +1 -1
  396. package/dist/rich-text-editor/virtual.js +17 -0
  397. package/dist/rich-text-editor/virtual.js.map +1 -1
  398. package/dist/richtext/node.d.ts +46 -2
  399. package/dist/richtext/node.d.ts.map +1 -1
  400. package/dist/richtext/node.js +187 -4
  401. package/dist/richtext/node.js.map +1 -1
  402. package/dist/richtext/runs.d.ts +13 -8
  403. package/dist/richtext/runs.d.ts.map +1 -1
  404. package/dist/richtext/runs.js +18 -8
  405. package/dist/richtext/runs.js.map +1 -1
  406. package/dist/table/index.d.ts.map +1 -1
  407. package/dist/table/index.js +23 -0
  408. package/dist/table/index.js.map +1 -1
  409. package/dist/table/rows.d.ts +3 -2
  410. package/dist/table/rows.d.ts.map +1 -1
  411. package/dist/table/rows.js +20 -6
  412. package/dist/table/rows.js.map +1 -1
  413. package/dist/terminal/index.d.ts.map +1 -1
  414. package/dist/terminal/index.js +4 -0
  415. package/dist/terminal/index.js.map +1 -1
  416. package/dist/terminal/vt/pty.d.ts +13 -2
  417. package/dist/terminal/vt/pty.d.ts.map +1 -1
  418. package/dist/terminal/vt/pty.js +18 -3
  419. package/dist/terminal/vt/pty.js.map +1 -1
  420. package/dist/terminal-output/index.d.ts.map +1 -1
  421. package/dist/terminal-output/index.js +6 -1
  422. package/dist/terminal-output/index.js.map +1 -1
  423. package/dist/three/canvas.d.ts.map +1 -1
  424. package/dist/three/canvas.js +85 -7
  425. package/dist/three/canvas.js.map +1 -1
  426. package/dist/three/events.d.ts +22 -4
  427. package/dist/three/events.d.ts.map +1 -1
  428. package/dist/three/events.js +34 -4
  429. package/dist/three/events.js.map +1 -1
  430. package/dist/tree/index.d.ts.map +1 -1
  431. package/dist/tree/index.js +17 -0
  432. package/dist/tree/index.js.map +1 -1
  433. package/package.json +42 -33
  434. package/src/charts/node.ts +62 -26
  435. package/src/charts/render.ts +120 -30
  436. package/src/code/index.ts +3 -1
  437. package/src/code-editor/completion.ts +3 -2
  438. package/src/code-editor/index.ts +19 -0
  439. package/src/code-editor/node.ts +1574 -184
  440. package/src/code-language/languages/glsl.ts +12 -2
  441. package/src/code-language/languages/javascript.ts +21 -4
  442. package/src/code-language/languages/json.ts +12 -2
  443. package/src/code-language/languages/shell.ts +12 -2
  444. package/src/code-language/languages/sql.ts +22 -2
  445. package/src/code-language/stream.ts +257 -17
  446. package/src/code-language/types.ts +5 -4
  447. package/src/codeblock/index.ts +20 -3
  448. package/src/flow/draw.ts +167 -41
  449. package/src/flow/gl/color.ts +85 -0
  450. package/src/flow/gl/index.ts +342 -0
  451. package/src/flow/gl/pack.ts +585 -0
  452. package/src/flow/gl/renderer.ts +679 -0
  453. package/src/flow/gl/shaders.ts +371 -0
  454. package/src/flow/gl/text.ts +1295 -0
  455. package/src/flow/index.ts +794 -67
  456. package/src/flow/node.ts +2496 -1256
  457. package/src/flow/paint.ts +511 -0
  458. package/src/flow/paths.ts +80 -15
  459. package/src/flow/scene.ts +2199 -0
  460. package/src/flow/types.ts +113 -1
  461. package/src/html/controls.ts +73 -2
  462. package/src/html/css/calc.ts +385 -0
  463. package/src/html/css/cascade.ts +1403 -62
  464. package/src/html/css/color.ts +755 -0
  465. package/src/html/css/content.ts +417 -0
  466. package/src/html/css/counter-styles.ts +973 -0
  467. package/src/html/css/decode.ts +130 -0
  468. package/src/html/css/parse.ts +1514 -160
  469. package/src/html/css/style.ts +4102 -222
  470. package/src/html/css/ua.ts +58 -12
  471. package/src/html/css/values.ts +150 -50
  472. package/src/html/css/vars.ts +348 -0
  473. package/src/html/dom.ts +284 -17
  474. package/src/html/fonts.ts +641 -0
  475. package/src/html/gif.ts +233 -0
  476. package/src/html/index.ts +180 -20
  477. package/src/html/layout/block.ts +3456 -230
  478. package/src/html/layout/boxes.ts +2100 -190
  479. package/src/html/layout/cache.ts +289 -0
  480. package/src/html/layout/collapse.ts +317 -0
  481. package/src/html/layout/css-grid.ts +1063 -0
  482. package/src/html/layout/flex.ts +1115 -106
  483. package/src/html/layout/floats.ts +128 -14
  484. package/src/html/layout/grid-lines.ts +141 -0
  485. package/src/html/layout/grid.ts +181 -0
  486. package/src/html/layout/inline.ts +3943 -192
  487. package/src/html/layout/shaping.ts +140 -0
  488. package/src/html/layout/table.ts +696 -186
  489. package/src/html/layout/tracks.ts +437 -0
  490. package/src/html/node.ts +1481 -118
  491. package/src/html/paint.ts +4293 -169
  492. package/src/html/resources.ts +161 -50
  493. package/src/html/surfaces.ts +81 -0
  494. package/src/html/svg.ts +493 -0
  495. package/src/html/url.ts +86 -0
  496. package/src/index.ts +2 -0
  497. package/src/internal/collate.ts +12 -0
  498. package/src/internal/markdown/ast.ts +11 -0
  499. package/src/internal/markdown/parse.ts +154 -11
  500. package/src/internal/prewarm.ts +45 -0
  501. package/src/internal/sdf.ts +227 -0
  502. package/src/internal/splice.ts +25 -0
  503. package/src/internal/window.ts +36 -5
  504. package/src/maps/anchors.ts +307 -1
  505. package/src/maps/controller.ts +116 -31
  506. package/src/maps/gl/buckets.ts +5 -1
  507. package/src/maps/gl/build-worker.ts +13 -2
  508. package/src/maps/gl/cover.ts +79 -44
  509. package/src/maps/gl/placement.ts +221 -69
  510. package/src/maps/gl/renderer.ts +104 -20
  511. package/src/maps/gl/shaders.ts +35 -32
  512. package/src/maps/gl/store.ts +66 -17
  513. package/src/maps/gl/text.ts +444 -71
  514. package/src/maps/gl/view.ts +361 -96
  515. package/src/maps/icons.ts +183 -0
  516. package/src/maps/index.ts +4 -0
  517. package/src/maps/labels.ts +269 -49
  518. package/src/maps/node.ts +82 -49
  519. package/src/maps/proj.ts +87 -0
  520. package/src/maps/sources.ts +5 -3
  521. package/src/maps/style.ts +32 -0
  522. package/src/maps/styles.ts +215 -4
  523. package/src/maps/tiles.ts +64 -31
  524. package/src/maps/types.ts +28 -4
  525. package/src/markdown/index.ts +149 -67
  526. package/src/markdown/spans.ts +5 -0
  527. package/src/qml/containers.tsx +6 -1
  528. package/src/qml/interaction.tsx +42 -5
  529. package/src/qml/layouts.tsx +21 -6
  530. package/src/qml/qtquick.tsx +9 -2
  531. package/src/qml/react.tsx +7 -5
  532. package/src/reorder/clock.ts +27 -0
  533. package/src/reorder/index.ts +100 -27
  534. package/src/rich-text-editor/index.ts +34 -1
  535. package/src/rich-text-editor/keymap.ts +1 -1
  536. package/src/rich-text-editor/keys.ts +135 -26
  537. package/src/rich-text-editor/marks.ts +101 -0
  538. package/src/rich-text-editor/render.ts +49 -12
  539. package/src/rich-text-editor/toolbar.ts +1 -1
  540. package/src/rich-text-editor/virtual.ts +25 -0
  541. package/src/richtext/node.ts +235 -14
  542. package/src/richtext/runs.ts +23 -12
  543. package/src/table/index.ts +29 -0
  544. package/src/table/rows.ts +18 -8
  545. package/src/terminal/index.ts +4 -0
  546. package/src/terminal/vt/pty.ts +16 -2
  547. package/src/terminal-output/index.ts +6 -1
  548. package/src/three/canvas.ts +102 -8
  549. package/src/three/events.ts +49 -9
  550. package/src/tree/index.ts +19 -0
@@ -1,4 +1,8 @@
1
- import { attr, childrenOf, isElement, isText, NON_RENDERED, tagOf, } from '../dom.js';
1
+ import { attr, childrenOf, isElement, isText, inImpliedHead, NON_RENDERED, tagOf, } from '../dom.js';
2
+ import { quoteAt } from '../css/content.js';
3
+ import { CONTAIN_STYLE, FIRST_LINE_INHERITED, copyStyle, firstLineParent, inherit, } from '../css/style.js';
4
+ import { AUTO } from '../css/values.js';
5
+ import { svgIntrinsics } from '../svg.js';
2
6
  /**
3
7
  * One box. Fields are assigned rather than passed so the shape stays
4
8
  * monomorphic — every box has every field, which is what keeps the property
@@ -16,6 +20,9 @@ export class Box {
16
20
  y = 0;
17
21
  width = 0;
18
22
  height = 0;
23
+ /** For an inline box broken around a block in it (CSS 2.1 9.2.1.1), the
24
+ * sides it goes on from, which have no edge: 1 its start, 2 its end. */
25
+ cut = 0;
19
26
  /** Resolved edges — border + padding, per side. */
20
27
  borderTop = 0;
21
28
  borderRight = 0;
@@ -29,6 +36,34 @@ export class Box {
29
36
  marginRight = 0;
30
37
  marginBottom = 0;
31
38
  marginLeft = 0;
39
+ /** The margin that came out through the box's bottom edge from its last
40
+ * children, collapsed with its own bottom margin, which is their sum:
41
+ * kept as a strut for the next sibling's to collapse with. Null where
42
+ * none came out, which is most boxes. Set by the block layout. */
43
+ bottomStrut = null;
44
+ /** An inline box with a background or a border to paint behind its
45
+ * fragments: the ascent and descent of its own face, the height CSS
46
+ * paints them over (10.6.1). Set by the inline layout. */
47
+ decoration = null;
48
+ /** What a percentage `height` resolves against: an absolutely positioned
49
+ * box's containing block's height, which is known before the box is laid
50
+ * out (CSS 2.1 10.5). NaN everywhere else, where that height depends on
51
+ * the content and a percentage is `auto`. */
52
+ percentHeightBase = NaN;
53
+ /** Whether this box last handed its children a percentage base that
54
+ * was a number: until it does, they hold NaN already, and the next
55
+ * pass need not visit them to say so again. */
56
+ gavePercentBase = false;
57
+ /** Whether this box's top margin collapsed through its parent's top edge
58
+ * and was spent placing the parent (CSS 2.1 8.3.1): its own layout puts
59
+ * it at the parent's content top, and applies no margin again. `2` when
60
+ * the box is empty and its bottom margin went the same way, so the next
61
+ * sibling's may have too; `3` for a new formatting context the margin
62
+ * stopped above, separated from the floats it carries, which sits where
63
+ * the margins before it end and applies none of its own. Set by the walk
64
+ * that places the parent, each pass, and read only while every sibling
65
+ * before it was a `2`. */
66
+ topAbsorbed = 0;
32
67
  /** Text, for a `text` box: already whitespace-processed and transformed. */
33
68
  text = '';
34
69
  /** This box's slice of the document text index. */
@@ -36,11 +71,16 @@ export class Box {
36
71
  textEnd = 0;
37
72
  /** Lines, for a box that established an inline formatting context. */
38
73
  lines = null;
39
- /** Intrinsic size, for a replaced box that knows one. */
40
- intrinsicWidth = 0;
41
- intrinsicHeight = 0;
74
+ /** The widest row of floats its lines placed beside none of them, side
75
+ * by side: what the box is measured with, as a line's floats are
76
+ * (`LineBox.floats`). */
77
+ floatRow = 0;
78
+ /** A replaced box's intrinsic size and ratio; null on every other box,
79
+ * which is nearly all of them, so it is one field rather than four. */
80
+ intrinsic = null;
42
81
  /**
43
- * Cached min-/max-content widths, for a table cell. -1 until measured.
82
+ * Cached min-/max-content widths, for a table cell and for a box an
83
+ * intrinsic size (`width: fit-content`) sizes. -1 until measured.
44
84
  *
45
85
  * Intrinsic widths are width-independent by definition, so measuring them
46
86
  * per layout pass was this engine breaking its own phase rule — a resize
@@ -51,17 +91,23 @@ export class Box {
51
91
  */
52
92
  intrinsicMinContent = -1;
53
93
  intrinsicMaxContent = -1;
94
+ /** Which layout of the pass this box last had, counted across all boxes:
95
+ * what tells a layout kept for reuse (`layoutOwn`) from one a probe has
96
+ * laid over since. */
97
+ layoutSerial = 0;
54
98
  /** What a replaced box is: the resource seam and the control host both
55
99
  * key on this rather than re-reading the tag. */
56
100
  replaced = 'none';
57
- /** The marker text of a `list-item`, if it generated one, and where it
58
- * was laid out. The marker is not a box: it is not in the flow, nothing
59
- * can select it (CSS spells that `::marker`, and no author styles it
60
- * here), and giving it one would put a bullet in every copied list. */
61
- markerText = '';
62
- markerLayout = null;
63
- markerX = 0;
64
- markerY = 0;
101
+ /** The marker of a `list-item`, if it generated one. The marker is not a
102
+ * box: it is not in the flow, nothing can select it (CSS spells that
103
+ * `::marker`, and no author styles it here), and giving it one would put
104
+ * a bullet in every copied list. */
105
+ marker = null;
106
+ /** Which of an element's pseudo-elements this box is. Its `el` is null —
107
+ * it is no element — and its text box's is the element it hangs off, so
108
+ * a click on a link's generated text or first letter is a click on the
109
+ * link. */
110
+ pseudo = null;
65
111
  /**
66
112
  * The bounds of everything this box and its descendants draw, in document
67
113
  * coordinates — **ink** bounds, not the border box, because `overflow:
@@ -86,6 +132,10 @@ export class Box {
86
132
  /** Out-of-flow children in paint order (z-index, then document order),
87
133
  * precomputed so a paint does not filter and sort per box per frame. */
88
134
  positionedPaint = null;
135
+ /** Whether a positioned box is somewhere under this one: what the pass
136
+ * that gives each stacking context its positioned boxes walks, so that
137
+ * a subtree with none is not walked at all (`stackLayers`). */
138
+ holdsLayers = false;
89
139
  /** The tallest line box under this box — the slack a binary search over
90
140
  * the y-sorted lines needs, since a line's bottom is not monotone. */
91
141
  maxLineHeight = 0;
@@ -102,6 +152,20 @@ export class Box {
102
152
  outOfFlow = false;
103
153
  /** Set on a float, for the same reason. */
104
154
  isFloat = false;
155
+ /** Where an out-of-flow box would have been in the flow it was taken
156
+ * from — its static position (CSS 2.1 10.3.7, 10.6.4). */
157
+ staticPosition = null;
158
+ /** A table's captions, above and below it: they are in the box's height,
159
+ * and outside the table's own border and background (CSS 2.1 17.4). */
160
+ captionTop = 0;
161
+ captionBottom = 0;
162
+ /** A table whose borders collapse: the border each segment of its grid
163
+ * carries, resolved once per build (`collapseTable`). */
164
+ collapsed = null;
165
+ /** Set on a table whose borders collapse, and on its cells: their border
166
+ * widths are the halves the collapsing model leaves them, and the table
167
+ * paints the borders rather than the boxes. */
168
+ bordersCollapsed = false;
105
169
  constructor(kind, el, style) {
106
170
  this.kind = kind;
107
171
  this.el = el;
@@ -141,6 +205,65 @@ export class Box {
141
205
  return this.borderTop + this.borderBottom + this.padTop + this.padBottom;
142
206
  }
143
207
  }
208
+ /** The `::first-line` style of each block container a rule gives one to,
209
+ * kept beside the boxes: few documents have any (`BoxTree.firstLine`). */
210
+ export const FIRST_LINE = new WeakMap();
211
+ /** The url of an image generated content put in a pseudo-element: it has
212
+ * no element of its own to name it (`imageUrlOf`). */
213
+ export const CONTENT_IMAGES = new WeakMap();
214
+ /** How far `position: relative` moved an inline box (`offsetInline`), how
215
+ * far each text on a line moved with the boxes it is in, and the lines
216
+ * holding one: beside the boxes and the lines, since few documents move an
217
+ * inline box. A moved text's `drawX` and `drawY` are where it is drawn;
218
+ * its box's decorations are worked out from where it was, and moved by
219
+ * their own box's offset. */
220
+ export const INLINE_OFFSETS = new WeakMap();
221
+ export const TEXT_SHIFTS = new WeakMap();
222
+ export const SHIFTED_LINES = new WeakSet();
223
+ /** How far `vertical-align` raised a text above its line's baseline, and an
224
+ * inline box's own baseline, where it did: beside the lines and the boxes
225
+ * for the reason the offsets above are. A line holding a raised text is
226
+ * among `SHIFTED_LINES` too, as its text is off the line's baseline. */
227
+ export const TEXT_RAISES = new WeakMap();
228
+ /** The blocks some of whose text casts a shadow (`text-shadow`), which the
229
+ * paint pass looks for in them and in no other. */
230
+ export const SHADOWED_TEXT = new WeakSet();
231
+ export const BOX_RAISES = new WeakMap();
232
+ /** And the raise, on one line, of each inline box a `top` or `bottom` box
233
+ * holds, the box itself among them: that box's baseline is where the
234
+ * line's edge puts it, which is a different height on every line, so its
235
+ * background, drawn from its baseline, is put there by line. */
236
+ export const LINE_BOX_RAISES = new WeakMap();
237
+ /** The table cells wholly in columns `visibility: collapse` took out of
238
+ * their table (CSS 2.1 17.5.5), which the paint pass leaves out: a cell is
239
+ * no descendant of its column and inherits nothing from it, so its own
240
+ * style still says `visible`. */
241
+ export const COLLAPSED_CELLS = new WeakSet();
242
+ /** And the cells spanning into a column or a row taken out: laid out as
243
+ * they would have been, placed over what is left of their span, and
244
+ * clipped to it. */
245
+ export const CLIPPED_CELLS = new WeakSet();
246
+ /** The boxes after a line-clamp container's clamp point (CSS Overflow 4,
247
+ * 5.3.1): invisible, with all they hold, and no taller than nothing to
248
+ * the blocks around them. Marked by the layout of the flow they are in,
249
+ * which leaves them where the last layout put them, and passed over by
250
+ * paint and the paint bounds. */
251
+ export const CLAMPED = new WeakSet();
252
+ export const GRID_TRACKS = new WeakMap();
253
+ /** A flex box's children in the order it lays them out and paints them,
254
+ * `order` first and the document's after it (CSS Flexbox 5.4), where
255
+ * `order` moves any: an absolutely positioned child's is 0. */
256
+ export const PAINT_ORDER = new WeakMap();
257
+ /** The blocks that broke a relatively positioned inline box in pieces,
258
+ * under its first piece: its offset moves them too (CSS 2.1 9.2.1.1),
259
+ * though they stand outside it (`breakAround`). */
260
+ export const CUT_BLOCKS = new WeakMap();
261
+ /** The blocks that broke an inline box under full opacity in pieces, and
262
+ * the opacity they take from it — from each such inline box around them,
263
+ * multiplied. They stand outside it (CSS 2.1 9.2.1.1) and are still its
264
+ * content, which its opacity fades as a group: a green square in a
265
+ * `<span style="opacity: .5">` was drawn at full strength. */
266
+ export const FADED_BLOCKS = new WeakMap();
144
267
  /** Build the box tree for a document. */
145
268
  export function buildBoxes(root, options) {
146
269
  const builder = new Builder(options);
@@ -153,66 +276,247 @@ export function buildBoxes(root, options) {
153
276
  * output, a runaway template) from a stack overflow five phases later.
154
277
  * Blink's parser flattens at 512 for the same reason; content past the cap
155
278
  * is dropped, which beats the alternative of crashing the application.
279
+ * The parser keeps a document to 256 elements (`dom.ts`), so what reaches
280
+ * this now is the anonymous boxes a table builds round each of them.
156
281
  */
157
282
  const MAX_DEPTH = 512;
283
+ /** The sharing key the root box's children share under: one root style per
284
+ * build, and the shared styles last a build (`Cascade.beginSharing`). */
285
+ const ROOT_SHARE_KEY = 0;
158
286
  class Builder {
159
287
  _options;
160
- _text = '';
288
+ /** The document text, in the pieces it was pushed in — joined once at the
289
+ * end, so that taking back a line's last space is not a copy of it. */
290
+ _chunks = [];
291
+ _length = 0;
161
292
  _textBoxes = [];
293
+ _textStyles = new Set();
294
+ _styles = new Map();
295
+ /** The last text box's style: runs of text share their parent's, so
296
+ * most text boxes repeat it and add nothing to `_textStyles`. */
297
+ _lastTextStyle = null;
162
298
  _controls = [];
163
299
  _links = [];
164
- /** Counter stack for `<ol>` numbering, one entry per open list. */
165
- _counters = [];
300
+ _backgrounds = [];
301
+ _contentImages = [];
302
+ _relative = false;
303
+ _negative = false;
304
+ _firstLine = false;
305
+ _nestedOutOfLine = false;
306
+ _movedInline = false;
307
+ _clipText = false;
308
+ /** The CSS counters in scope, for `counter()` in generated content. */
309
+ _scopes = new CounterScopes();
310
+ /** How many quotes generated content has opened and not closed. */
311
+ _quoteDepth = 0;
312
+ /** Where the inline content being built stands, for collapsing white
313
+ * space across element boundaries. */
314
+ _ws = 'start';
315
+ /** Whether the word the inline content being built is in has had its
316
+ * first letter, for `text-transform: capitalize`: a word runs on across
317
+ * element boundaries, as white space collapses across them. */
318
+ _lettered = false;
166
319
  _depth = 0;
320
+ /** The `::first-letter` whose letter is still to come, for the block
321
+ * container whose first line has not begun. Null when there is none,
322
+ * and once anything but a letter begins that line. */
323
+ _firstLetter = null;
167
324
  constructor(options) {
168
325
  this._options = options;
169
326
  }
170
327
  run(root) {
171
328
  const cascade = this._options.cascade;
172
- const rootStyle = cascade.rootStyle(hasBody(root));
329
+ cascade.beginSharing();
330
+ const rootStyle = cascade.rootStyle(hasBody(root), hasHtml(root));
173
331
  const rootBox = new Box('block', null, rootStyle);
332
+ // a fragment's root stands in for a `<body>`, counters and all
333
+ this._scopes.open();
334
+ this._counterChanges(rootStyle, {
335
+ node: root,
336
+ style: rootStyle,
337
+ key: ROOT_SHARE_KEY,
338
+ parent: null,
339
+ });
174
340
  // The DOM's `<html>`/`<body>` are ordinary elements with ordinary styles;
175
341
  // the box above them exists only to be the initial containing block, so
176
342
  // it carries no margins of its own and cannot collapse with anything.
177
- this._children(root, rootBox, rootStyle, false);
178
- fixUp(rootBox);
343
+ this._children(root, rootBox, rootStyle, false, null, ROOT_SHARE_KEY);
344
+ this._endLine();
345
+ fixUp(rootBox, anonymousStyles(cascade.initial));
179
346
  assignSubtreeRanges(rootBox);
180
347
  return {
181
348
  root: rootBox,
182
- text: this._text,
349
+ text: this._chunks.join(''),
183
350
  textBoxes: this._textBoxes,
351
+ textStyles: this._textStyles,
352
+ styles: this._styles,
184
353
  controls: this._controls,
185
354
  links: this._links,
355
+ backgrounds: this._backgrounds,
356
+ contentImages: this._contentImages,
357
+ relative: this._relative || isRelative(rootStyle),
358
+ negative: this._negative,
359
+ firstLine: this._firstLine,
360
+ firstLineStyler: this._firstLine ? this._firstLineStyler() : null,
361
+ nestedOutOfLine: this._nestedOutOfLine,
362
+ movedInline: this._movedInline,
363
+ clipText: this._clipText,
364
+ };
365
+ }
366
+ /** `BoxTree.firstLineStyler`, over this build's cascade. */
367
+ _firstLineStyler() {
368
+ const cascade = this._options.cascade;
369
+ const styles = this._styles;
370
+ return (box, parent) => {
371
+ const up = box.parent?.style ?? box.style;
372
+ const inherits = firstLineParent(up, parent);
373
+ const el = box.kind === 'text' ? null : box.el;
374
+ const own = el ? styles.get(el) : undefined;
375
+ if (el && own && own.style === box.style) {
376
+ return cascade.styleFor(el, inherits, own.inFlex);
377
+ }
378
+ // text, an anonymous box, a pseudo-element's: no rules of its own to
379
+ // be asked again, so what it took from its parent it takes from the
380
+ // parent's style on the line, and what it set it keeps
381
+ const out = copyStyle(box.style);
382
+ const to = out;
383
+ const mine = box.style;
384
+ const theirs = up;
385
+ const line = inherits;
386
+ for (const name of FIRST_LINE_INHERITED) {
387
+ if (mine[name] === theirs[name])
388
+ to[name] = line[name];
389
+ }
390
+ return out;
186
391
  };
187
392
  }
188
393
  /** Build boxes for a parent's children into `into`. */
189
- _children(node, into, parentStyle, inFlex, owner = null) {
394
+ _children(node, into, parentStyle, inFlex, owner, parentKey) {
395
+ // CSS 2.1 17.2.1: a column group holds columns, and anything else in it
396
+ // is not rendered
397
+ const onlyColumns = parentStyle.display === 'table-column-group';
398
+ // A closed `<details>` shows its first `<summary>` and nothing else:
399
+ // HTML renders the rest into a slot that is out of the box tree until
400
+ // the element is `open`.
401
+ const closed = owner !== null &&
402
+ tagOf(owner) === 'details' &&
403
+ owner.attribs.open === undefined;
404
+ let summarised = false;
190
405
  for (const child of childrenOf(node)) {
406
+ if (closed) {
407
+ if (!isElement(child) || summarised || tagOf(child) !== 'summary') {
408
+ continue;
409
+ }
410
+ summarised = true;
411
+ }
191
412
  if (isText(child)) {
192
- this._textNode(child.data, into, parentStyle, owner);
413
+ if (!onlyColumns)
414
+ this._textNode(child.data, into, parentStyle, owner);
193
415
  continue;
194
416
  }
195
417
  if (!isElement(child))
196
418
  continue;
197
- this._element(child, into, parentStyle, inFlex);
419
+ this._element(child, into, parentStyle, inFlex, parentKey, onlyColumns);
198
420
  }
199
421
  }
200
- _element(el, into, parentStyle, inFlex) {
422
+ _element(el, into, parentStyle, inFlex, parentKey, onlyColumns = false) {
201
423
  const tag = tagOf(el);
202
- if (NON_RENDERED.has(tag))
424
+ if (NON_RENDERED.has(tag) || inImpliedHead(el, tag))
203
425
  return;
204
- const style = this._options.cascade.styleFor(el, parentStyle, inFlex);
426
+ // shared with every element that must compute the same style, which in
427
+ // a long document is most of them (`Cascade.sharedStyleFor`)
428
+ const { style, key } = this._options.cascade.sharedStyleFor(el, parentStyle, parentKey, inFlex);
429
+ this._styles.set(el, { style, inFlex });
205
430
  if (style.display === 'none')
206
431
  return;
432
+ if (onlyColumns && style.display !== 'table-column')
433
+ return;
434
+ // what an element counts is in scope for it and what it holds, and for
435
+ // what follows it where its parent has no such counter
436
+ // (`CounterScopes`)
437
+ this._scopes.open();
438
+ // style containment keeps what the element's subtree does to counters
439
+ // and quotes in it (CSS Containment 2, 3.4)
440
+ const contains = (style.contain & CONTAIN_STYLE) !== 0 && style.display !== 'contents';
441
+ const quoteDepth = this._quoteDepth;
442
+ if (contains)
443
+ this._scopes.contain();
444
+ this._elementIn(el, tag, style, key, into, inFlex, {
445
+ node: el,
446
+ style,
447
+ key,
448
+ parent: { style: parentStyle, key: parentKey, inFlex },
449
+ });
450
+ if (contains)
451
+ this._quoteDepth = quoteDepth;
452
+ this._scopes.close();
453
+ }
454
+ _elementIn(el, tag, style, key, into, inFlex, place) {
455
+ if (style.display === 'contents') {
456
+ // no box of its own (CSS Display 3, 2.5): its `::before`, its
457
+ // children and its `::after` are its parent's, in its style — the
458
+ // items of a flex row still, through a wrapper Tailwind's `contents`
459
+ // takes out — and a replaced element, which has nothing to hand on,
460
+ // is not rendered
461
+ if (tag === 'br' || replacedKind(el, tag) !== 'none')
462
+ return;
463
+ this._counterChanges(style, place);
464
+ this._pseudo(el, 'before', style, into);
465
+ this._children(el, into, style, inFlex, el, key);
466
+ this._pseudo(el, 'after', style, into);
467
+ return;
468
+ }
469
+ if (isRelative(style)) {
470
+ this._relative = true;
471
+ if (style.display === 'inline' && isOffset(style)) {
472
+ this._movedInline = true;
473
+ }
474
+ }
475
+ // a translation is moved by the same pass
476
+ if (style.translate || style.transformTranslate)
477
+ this._relative = true;
478
+ if (style.backgroundClipText)
479
+ this._clipText = true;
480
+ if (style.verticalAlign !== 'baseline' && style.display === 'inline') {
481
+ this._movedInline = true;
482
+ }
483
+ if (isNegative(style))
484
+ this._negative = true;
485
+ // before anything else of the element's, including its `::before`,
486
+ // and for the element whatever box it makes (CSS 2.1 12.4)
487
+ this._counterChanges(style, place);
207
488
  // `<br>` is a line break rather than a box, and it is the one element
208
489
  // whose *absence* of a box still has to reach the inline layout.
209
490
  if (tag === 'br') {
491
+ this._endLine();
492
+ // the first line ends with no letter on it (CSS 2.1 5.12.2)
493
+ this._abandonLetter();
494
+ if (style.clear !== 'none') {
495
+ // a break that clears the floats — `<br clear="all">` after a
496
+ // floated image — puts what follows it below them: an empty block
497
+ // that clears them, which the inline content around it is broken
498
+ // for (`breakInlines`), where a line break went on beside them
499
+ const clearing = copyStyle(style);
500
+ clearing.display = 'block';
501
+ into.append(new Box('block', el, clearing));
502
+ this._ws = 'start';
503
+ return;
504
+ }
210
505
  const box = new Box('break', el, style);
211
506
  into.append(box);
212
507
  this._push('\n', box);
508
+ this._ws = 'start';
213
509
  return;
214
510
  }
215
- const replaced = replacedKind(el, tag);
511
+ // an `<object>` is its image once it has one, and its content until;
512
+ // an `<embed>` its image, and a `<video>` its poster, or a frame
513
+ const replaced = tag === 'object'
514
+ ? this._options.imageSize(el)
515
+ ? 'image'
516
+ : 'none'
517
+ : (tag === 'embed' || tag === 'video') && this._options.imageSize(el)
518
+ ? 'image'
519
+ : replacedKind(el, tag);
216
520
  if (replaced !== 'none') {
217
521
  this._replaced(el, tag, replaced, style, into);
218
522
  return;
@@ -222,51 +526,185 @@ class Builder {
222
526
  const kind = boxKindFor(style.display);
223
527
  const box = new Box(kind, el, style);
224
528
  into.append(box);
529
+ if ((kind === 'block' || kind === 'table-cell' || kind === 'table-caption') &&
530
+ this._options.cascade.hasFirstLine) {
531
+ const firstLine = this._options.cascade.firstLineStyle(el, style);
532
+ if (firstLine) {
533
+ FIRST_LINE.set(box, firstLine);
534
+ this._firstLine = true;
535
+ }
536
+ }
537
+ if (style.backgroundImage ||
538
+ style.backgroundImages ||
539
+ typeof style.borderImage.source === 'string') {
540
+ this._backgrounds.push(box);
541
+ }
225
542
  if (style.position === 'absolute' || style.position === 'fixed')
226
543
  box.outOfFlow = true;
227
544
  else if (style.float !== 'none')
228
545
  box.isFloat = true;
546
+ if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
547
+ this._nestedOutOfLine = true;
229
548
  if (attr(el, 'href') && (tag === 'a' || tag === 'area'))
230
549
  this._links.push(box);
231
- if (style.display === 'list-item') {
232
- box.markerText = markerFor(el, style, this._counters);
550
+ // a column's content is not rendered at all (CSS 2.1 17.2.1)
551
+ if (style.display === 'table-column')
552
+ return;
553
+ let insideMarker = null;
554
+ let markerStyle = null;
555
+ let ownMarker = false;
556
+ let markerImage = null;
557
+ let insideImage = null;
558
+ if (style.display === 'list-item' && style.listStyleImage) {
559
+ // asked for as generated content's images are, and the tree built
560
+ // again when it arrives; until it has, and where it never does, the
561
+ // item's marker is its `list-style-type`'s
562
+ const url = style.listStyleImage;
563
+ const size = this._options.urlSize?.(url) ?? null;
564
+ this._contentImages.push({ url, element: el, sized: !!size });
565
+ if (size)
566
+ markerImage = { url, size };
233
567
  }
234
- const opensCounter = tag === 'ol' || tag === 'ul';
235
- if (opensCounter) {
236
- const start = Number(attr(el, 'start') ?? '1');
237
- this._counters.push(Number.isFinite(start) ? start : 1);
568
+ if (style.display === 'list-item') {
569
+ // the item's number, the `list-item` counter it has just counted
570
+ const written = markerFor(style, this._scopes.value('list-item'), this._options.cascade.counterStyles);
571
+ let text = written.text;
572
+ let flush = written.flush;
573
+ markerStyle = this._options.cascade.markerStyle(el, style);
574
+ // a `::marker` with a `content` is set as that, as it is written —
575
+ // its strings and its counters, where one of `none` is no marker
576
+ const content = markerStyle?.content;
577
+ if (content === 'none')
578
+ text = '';
579
+ else if (Array.isArray(content) && markerStyle) {
580
+ text = this._generated(content, markerStyle, el)
581
+ .filter((piece) => typeof piece === 'string')
582
+ .join('');
583
+ ownMarker = true;
584
+ flush = true;
585
+ }
586
+ else if (style.listStyleType.startsWith('"')) {
587
+ ownMarker = true;
588
+ flush = true;
589
+ }
590
+ if (markerImage && !ownMarker) {
591
+ if (style.listStylePosition === 'inside')
592
+ insideImage = markerImage.url;
593
+ else {
594
+ const scale = this._options.scale ?? 1;
595
+ box.marker = {
596
+ text: '',
597
+ layout: null,
598
+ x: 0,
599
+ y: 0,
600
+ style: markerStyle,
601
+ image: {
602
+ url: markerImage.url,
603
+ width: (markerImage.size.width ?? 0) * scale,
604
+ height: (markerImage.size.height ?? 0) * scale,
605
+ },
606
+ };
607
+ }
608
+ }
609
+ else if (text && style.listStylePosition === 'inside') {
610
+ insideMarker = flush ? text : `${text} `;
611
+ }
612
+ else if (text) {
613
+ box.marker = { text, layout: null, x: 0, y: 0, style: markerStyle };
614
+ if (flush)
615
+ box.marker.flush = true;
616
+ }
238
617
  }
239
618
  const childInFlex = style.display === 'flex' || style.display === 'inline-flex';
619
+ const flow = flowOf(style, box);
620
+ const around = this._ws;
621
+ const aroundWord = this._lettered;
622
+ if (flow === 'block')
623
+ this._endLine();
624
+ if (flow !== 'inline')
625
+ this._ws = 'start';
626
+ // A first letter is looked for in the first line of a block container,
627
+ // down through its inline content and its first blocks. A float, a
628
+ // positioned box and a flex container are no part of that line, and
629
+ // an atomic inline is something other than a letter at its start
630
+ // (CSS 2.1 5.12.2). A block container with rules of its own takes the
631
+ // search over.
632
+ const outerLetter = this._firstLetter;
633
+ const skipped = flow === 'out' || flow === 'atomic' || kind === 'flex';
634
+ if (skipped)
635
+ this._firstLetter = null;
636
+ // a block starts a line, and punctuation before it was on another
637
+ else if (flow === 'block' && outerLetter)
638
+ giveBack(outerLetter);
639
+ const ownRules = hasFirstLetter(style.display)
640
+ ? this._options.cascade.firstLetterRules(el)
641
+ : null;
642
+ const ownLetter = ownRules ? { rules: ownRules, punctuation: [] } : null;
643
+ if (ownLetter)
644
+ this._firstLetter = ownLetter;
240
645
  this._depth += 1;
241
- this._children(el, box, style, childInFlex, el);
646
+ if (insideImage) {
647
+ // an inline image at the start of the first line, as a generated
648
+ // image is, and the space a marker's text ends in
649
+ this._contentImage(insideImage, box, style, el);
650
+ this._textNode(' ', box, style, el);
651
+ }
652
+ else if (insideMarker) {
653
+ this._insideMarker(insideMarker, style, markerStyle, box, el);
654
+ }
655
+ this._pseudo(el, 'before', style, box);
656
+ this._children(el, box, style, childInFlex, el, key);
657
+ this._pseudo(el, 'after', style, box);
242
658
  this._depth -= 1;
243
- if (opensCounter)
244
- this._counters.pop();
659
+ this._letterAfter(flow, skipped, outerLetter, ownLetter);
660
+ if (flow !== 'inline')
661
+ this._endLine();
662
+ this._ws = after(flow, this._ws, around);
663
+ this._lettered = letteredAfterFlow(flow, this._lettered, aroundWord);
245
664
  }
246
665
  _replaced(el, tag, replaced, style, into) {
247
666
  const box = new Box('replaced', el, style);
248
667
  box.replaced = replaced;
249
668
  into.append(box);
669
+ if (style.backgroundImage ||
670
+ style.backgroundImages ||
671
+ typeof style.borderImage.source === 'string') {
672
+ this._backgrounds.push(box);
673
+ }
250
674
  if (style.position === 'absolute' || style.position === 'fixed')
251
675
  box.outOfFlow = true;
252
676
  else if (style.float !== 'none')
253
677
  box.isFloat = true;
678
+ if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
679
+ this._nestedOutOfLine = true;
680
+ const flow = flowOf(style, box);
681
+ if (flow === 'block')
682
+ this._endLine();
683
+ this._ws = after(flow, this._ws, this._ws);
684
+ this._lettered = letteredAfterFlow(flow, this._lettered, this._lettered);
685
+ if (flow === 'atomic')
686
+ this._abandonLetter();
254
687
  if (replaced === 'image') {
255
688
  // Both sources are CSS pixels — an image pixel is one, and so is an
256
689
  // attribute — and the box is device.
257
690
  const scale = this._options.scale ?? 1;
258
691
  const loaded = this._options.imageSize(el);
259
692
  if (loaded) {
260
- box.intrinsicWidth = loaded.width * scale;
261
- box.intrinsicHeight = loaded.height * scale;
693
+ setIntrinsics(box, loaded, scale);
262
694
  }
263
695
  else {
264
696
  // An image that has not arrived still needs a box, or the document
265
697
  // reflows under the reader when it does. The attributes are the
266
698
  // author telling us the size in advance; without them the box is a
267
699
  // small placeholder rather than nothing.
268
- box.intrinsicWidth = (numberAttr(el, 'width') ?? 0) * scale;
269
- box.intrinsicHeight = (numberAttr(el, 'height') ?? 0) * scale;
700
+ const width = numberAttr(el, 'width') ?? 0;
701
+ const height = numberAttr(el, 'height') ?? 0;
702
+ box.intrinsic = {
703
+ width: width * scale,
704
+ height: height * scale,
705
+ missing: 0,
706
+ ratio: width > 0 && height > 0 ? width / height : 0,
707
+ };
270
708
  }
271
709
  // The alt text joins the document text, so a document read with the
272
710
  // images blocked still copies as prose.
@@ -277,35 +715,494 @@ class Builder {
277
715
  }
278
716
  if (replaced === 'hr')
279
717
  return;
718
+ if (replaced === 'svg') {
719
+ // its `em` is its own font size, in CSS pixels like the rest of it
720
+ const scale = this._options.scale ?? 1;
721
+ setIntrinsics(box, svgIntrinsics(el, style.fontSize / scale), scale);
722
+ return;
723
+ }
724
+ if (replaced === 'frame') {
725
+ const scale = this._options.scale ?? 1;
726
+ if (tag === 'canvas') {
727
+ // A canvas is the size of its bitmap, which its `width` and
728
+ // `height` attributes give, 300 by 150 where they do not, and
729
+ // keeps its proportions (HTML 4.12.5): no script draws in it, so it
730
+ // is that box with nothing in it. Taken for an element with no box
731
+ // of its own, it took no room, and one set a height kept no width.
732
+ const width = canvasSize(el, 'width', 300);
733
+ const height = canvasSize(el, 'height', 150);
734
+ box.intrinsic = {
735
+ width: width * scale,
736
+ height: height * scale,
737
+ missing: 0,
738
+ ratio: width > 0 && height > 0 ? width / height : 0,
739
+ };
740
+ return;
741
+ }
742
+ // HTML's default object size, in CSS pixels; `width` and `height`
743
+ // attributes reach the style as presentational hints and win
744
+ box.intrinsic = {
745
+ width: 300 * scale,
746
+ height: 150 * scale,
747
+ missing: 0,
748
+ ratio: 0,
749
+ };
750
+ return;
751
+ }
280
752
  const size = this._options.controlSize(el, replaced, style);
281
- box.intrinsicWidth = size.width;
282
- box.intrinsicHeight = size.height;
753
+ box.intrinsic = {
754
+ width: size.width,
755
+ height: size.height,
756
+ missing: 0,
757
+ ratio: 0,
758
+ };
283
759
  this._controls.push(box);
284
760
  // A control's value is the widget's, not the document's: putting it in
285
761
  // the selection index would make Ctrl+A copy the contents of every text
286
762
  // field, which no document viewer does.
287
763
  }
764
+ /**
765
+ * An element's `::before` or `::after`, when a rule gives it content: a
766
+ * box of its own `display`, holding the text its `content` comes to, which
767
+ * goes through the same white-space processing as the document's text.
768
+ */
769
+ _pseudo(el, which, elementStyle, into) {
770
+ const style = this._options.cascade.pseudoStyleFor(el, which, elementStyle);
771
+ if (!style || style.display === 'none')
772
+ return;
773
+ if (isRelative(style)) {
774
+ this._relative = true;
775
+ if (style.display === 'inline' && isOffset(style)) {
776
+ this._movedInline = true;
777
+ }
778
+ }
779
+ // a translation is moved by the same pass
780
+ if (style.translate || style.transformTranslate)
781
+ this._relative = true;
782
+ if (style.backgroundClipText)
783
+ this._clipText = true;
784
+ if (style.verticalAlign !== 'baseline' && style.display === 'inline') {
785
+ this._movedInline = true;
786
+ }
787
+ if (isNegative(style))
788
+ this._negative = true;
789
+ // a column renders no content, and generated content is all it would
790
+ // hold; in a column group it is not a column either (CSS 2.1 17.2.1)
791
+ if (style.display === 'table-column' ||
792
+ style.display === 'table-column-group' ||
793
+ elementStyle.display === 'table-column-group') {
794
+ return;
795
+ }
796
+ // a scope of its own, which what it counts is in where its element has
797
+ // the counter already (`CounterScopes`)
798
+ this._scopes.open();
799
+ this._counterChanges(style, null);
800
+ const box = new Box(boxKindFor(style.display), null, style);
801
+ box.pseudo = which;
802
+ into.append(box);
803
+ // a list item it generates has a marker as an element's has, of the
804
+ // `list-item` counter it has just counted (CSS 2.1 12.5): outside it,
805
+ // or at the start of its content
806
+ const written = style.display === 'list-item'
807
+ ? markerFor(style, this._scopes.value('list-item'), this._options.cascade.counterStyles)
808
+ : null;
809
+ let marker = written?.text ?? '';
810
+ if (marker && style.listStylePosition !== 'inside') {
811
+ box.marker = { text: marker, layout: null, x: 0, y: 0, style: null };
812
+ if (written?.flush)
813
+ box.marker.flush = true;
814
+ marker = '';
815
+ }
816
+ if (style.position === 'absolute' || style.position === 'fixed')
817
+ box.outOfFlow = true;
818
+ else if (style.float !== 'none')
819
+ box.isFloat = true;
820
+ if (into.kind === 'inline' && (box.outOfFlow || box.isFloat))
821
+ this._nestedOutOfLine = true;
822
+ const pieces = this._generated(style.content, style, el);
823
+ if (marker)
824
+ pieces.unshift(written?.flush ? marker : `${marker} `);
825
+ this._scopes.close();
826
+ const flow = flowOf(style, box);
827
+ const around = this._ws;
828
+ const aroundWord = this._lettered;
829
+ if (flow === 'block')
830
+ this._endLine();
831
+ if (flow !== 'inline')
832
+ this._ws = 'start';
833
+ // the first letter can be generated, and is looked for here as in an
834
+ // element of the same display
835
+ const outerLetter = this._firstLetter;
836
+ const skipped = flow === 'out' || flow === 'atomic';
837
+ if (skipped)
838
+ this._firstLetter = null;
839
+ else if (flow === 'block' && outerLetter)
840
+ giveBack(outerLetter);
841
+ for (const piece of pieces) {
842
+ if (typeof piece === 'string')
843
+ this._textNode(piece, box, style, el);
844
+ else
845
+ this._contentImage(piece.url, box, style, el);
846
+ }
847
+ this._letterAfter(flow, skipped, outerLetter, null);
848
+ if (flow !== 'inline')
849
+ this._endLine();
850
+ this._ws = after(flow, this._ws, around);
851
+ this._lettered = letteredAfterFlow(flow, this._lettered, aroundWord);
852
+ }
853
+ /**
854
+ * An `inside` list marker: an inline box at the start of the item, the
855
+ * marker and a space, which takes its room on the first line as a
856
+ * `::marker` does (CSS Lists 3, 3.1), where a marker set beside the line
857
+ * was drawn over its first letters — every `list-style-position: inside`
858
+ * list, and every `<summary>`. It inherits the item's text style and has
859
+ * none of its box.
860
+ */
861
+ _insideMarker(text, itemStyle, markerStyle, into, el) {
862
+ const style = inherit(markerStyle ?? itemStyle, this._options.cascade.initial);
863
+ style.display = 'inline';
864
+ // a marker's direction is its own, whatever the text after it, unless
865
+ // its rules say otherwise (the HTML style sheet's `::marker`)
866
+ style.unicodeBidi = markerStyle?.unicodeBidi ?? 'isolate';
867
+ const box = new Box('inline', null, style);
868
+ box.pseudo = 'before';
869
+ into.append(box);
870
+ this._textNode(text, box, style, el);
871
+ }
872
+ /**
873
+ * `counter-reset`, then `counter-increment`, then `counter-set`, as CSS
874
+ * Lists 3 orders them (4.2). A list item counts `list-item` besides,
875
+ * unless its `counter-increment` names it: one up, or one down in a list
876
+ * that counts down. `place` is where an element is, for a reversed
877
+ * counter to be counted from; a pseudo-element has none.
878
+ */
879
+ _counterChanges(style, place) {
880
+ const resets = style.counterReset;
881
+ const increments = style.counterIncrement;
882
+ const sets = style.counterSet;
883
+ const listItem = style.display === 'list-item';
884
+ if (!resets && !increments && !sets && !listItem)
885
+ return;
886
+ if (resets) {
887
+ for (let i = 0; i < resets.length; i += 1) {
888
+ const change = resets[i];
889
+ // a name reset twice is reset by the last
890
+ if (resets.some((c, j) => j > i && c.name === change.name))
891
+ continue;
892
+ const value = change.counted
893
+ ? this._countedStart(change.name, style, place)
894
+ : change.value;
895
+ this._scopes.reset(change.name, value, !!change.reversed);
896
+ }
897
+ }
898
+ let counted = false;
899
+ for (const { name, value } of increments ?? []) {
900
+ this._scopes.increment(name, value);
901
+ if (name === 'list-item')
902
+ counted = true;
903
+ }
904
+ if (listItem && !counted) {
905
+ this._scopes.increment('list-item', this._scopes.reversed('list-item') ? -1 : 1);
906
+ }
907
+ for (const { name, value } of sets ?? [])
908
+ this._scopes.set(name, value);
909
+ }
910
+ /**
911
+ * Where a reversed counter written with no number starts (CSS Lists 3,
912
+ * 4.4.2): minus what the elements and pseudo-elements in its scope add to
913
+ * it, as far as the first that sets it, and what that sets it to, and as
914
+ * much again as the last of them that counted took away — 1 for a list
915
+ * item. `<ol reversed>`'s items count down to 1, and to an item's
916
+ * `value` before it. The scope is walked ahead of the builder, from the styles it will
917
+ * find shared when it gets there (`Cascade.sharedStyleFor`), and only for
918
+ * such a counter.
919
+ */
920
+ _countedStart(name, style, place) {
921
+ const cascade = this._options.cascade;
922
+ /** What each element and pseudo-element in the scope does to the
923
+ * counter, in document order: what it adds, and what it sets it to. */
924
+ const steps = [];
925
+ const note = (st) => {
926
+ let by = 0;
927
+ let increments = false;
928
+ for (const change of st.counterIncrement ?? []) {
929
+ if (change.name !== name)
930
+ continue;
931
+ by += change.value;
932
+ increments = true;
933
+ }
934
+ if (!increments && name === 'list-item' && st.display === 'list-item') {
935
+ by = -1;
936
+ increments = true;
937
+ }
938
+ let set = null;
939
+ for (const change of st.counterSet ?? []) {
940
+ if (change.name === name)
941
+ set = change.value;
942
+ }
943
+ if (increments || set !== null)
944
+ steps.push({ by, set });
945
+ };
946
+ const resets = (st) => st.counterReset?.some((change) => change.name === name) ?? false;
947
+ const pseudo = (el, which, st) => {
948
+ const own = cascade.pseudoStyleFor(el, which, st);
949
+ if (own && own.display !== 'none' && !resets(own))
950
+ note(own);
951
+ };
952
+ /** The elements from `from` on of a parent, and what is in them: a
953
+ * reset of the counter in one is a counter of its own, and a later
954
+ * sibling's reset at the scope's own level ends it (`stopAtReset`). */
955
+ const walk = (children, from, parentStyle, parentKey, inFlex, stopAtReset) => {
956
+ for (let i = from; i < children.length; i += 1) {
957
+ const child = children[i];
958
+ if (!isElement(child))
959
+ continue;
960
+ const tag = tagOf(child);
961
+ if (NON_RENDERED.has(tag) || inImpliedHead(child, tag))
962
+ continue;
963
+ const shared = cascade.sharedStyleFor(child, parentStyle, parentKey, inFlex);
964
+ const st = shared.style;
965
+ if (st.display === 'none')
966
+ continue;
967
+ if (resets(st)) {
968
+ if (stopAtReset)
969
+ return;
970
+ continue;
971
+ }
972
+ note(st);
973
+ inside(child, st, shared.key);
974
+ }
975
+ };
976
+ const inside = (el, st, key) => {
977
+ pseudo(el, 'before', st);
978
+ walk(childrenOf(el), 0, st, key, st.display === 'flex' || st.display === 'inline-flex', false);
979
+ pseudo(el, 'after', st);
980
+ };
981
+ // the element that resets it counts in it too, after the reset
982
+ note(style);
983
+ if (place) {
984
+ const siblings = this._scopes.reachesSiblings(name);
985
+ inside(place.node, place.style, place.key);
986
+ const parent = place.parent;
987
+ const node = place.node;
988
+ if (siblings && parent && node.parent) {
989
+ const children = childrenOf(node.parent);
990
+ walk(children, children.indexOf(place.node) + 1, parent.style, parent.key, parent.inFlex, true);
991
+ }
992
+ }
993
+ let num = 0;
994
+ let last = 0;
995
+ for (const { by, set } of steps) {
996
+ if (by !== 0)
997
+ last = -by;
998
+ if (set !== null) {
999
+ num += set;
1000
+ break;
1001
+ }
1002
+ num -= by;
1003
+ }
1004
+ return num + last;
1005
+ }
1006
+ /** A counter's value in a style the document can name, as `counter()`
1007
+ * writes it; `none` writes nothing. */
1008
+ _counterText(n, name, style) {
1009
+ if (name === 'none')
1010
+ return '';
1011
+ return this._options.cascade.counterStyles.text(n, name, style.direction === 'rtl');
1012
+ }
1013
+ /** What `content` comes to here, in document order: its text, broken
1014
+ * where it names an image. The quotes it opens and closes count for
1015
+ * everything after it. */
1016
+ _generated(items, style, el) {
1017
+ const pieces = [];
1018
+ let text = '';
1019
+ for (const item of items) {
1020
+ switch (item.kind) {
1021
+ case 'string':
1022
+ text += item.text;
1023
+ break;
1024
+ case 'url':
1025
+ if (text)
1026
+ pieces.push(text);
1027
+ text = '';
1028
+ pieces.push({ url: item.url });
1029
+ break;
1030
+ case 'attr':
1031
+ text += attr(el, item.name) ?? '';
1032
+ break;
1033
+ case 'counter':
1034
+ text += this._counterText(this._scopes.value(item.name), item.style, style);
1035
+ break;
1036
+ case 'counters':
1037
+ text += this._scopes
1038
+ .values(item.name)
1039
+ .map((v) => this._counterText(v, item.style, style))
1040
+ .join(item.separator);
1041
+ break;
1042
+ case 'open-quote':
1043
+ text += quoteAt(style.quotes, this._quoteDepth, 0);
1044
+ this._quoteDepth += 1;
1045
+ break;
1046
+ case 'close-quote':
1047
+ // a close with nothing open writes nothing and closes nothing
1048
+ if (this._quoteDepth > 0) {
1049
+ this._quoteDepth -= 1;
1050
+ text += quoteAt(style.quotes, this._quoteDepth, 1);
1051
+ }
1052
+ break;
1053
+ case 'no-open-quote':
1054
+ this._quoteDepth += 1;
1055
+ break;
1056
+ case 'no-close-quote':
1057
+ if (this._quoteDepth > 0)
1058
+ this._quoteDepth -= 1;
1059
+ break;
1060
+ }
1061
+ }
1062
+ if (text)
1063
+ pieces.push(text);
1064
+ return pieces;
1065
+ }
1066
+ /**
1067
+ * An image generated content names: an inline replaced box in the
1068
+ * pseudo-element, of the style it inherits and no other (CSS 2.1 12.2),
1069
+ * the image's size once the host has it and none before. An image that
1070
+ * never arrives takes no room, as no image does.
1071
+ */
1072
+ _contentImage(url, into, style, owner) {
1073
+ const box = new Box('replaced', null, {
1074
+ ...inherit(style, this._options.cascade.initial),
1075
+ display: 'inline',
1076
+ });
1077
+ box.replaced = 'image';
1078
+ into.append(box);
1079
+ CONTENT_IMAGES.set(box, url);
1080
+ const size = this._options.urlSize?.(url) ?? null;
1081
+ this._contentImages.push({ url, element: owner, sized: !!size });
1082
+ if (size)
1083
+ setIntrinsics(box, size, this._options.scale ?? 1);
1084
+ else
1085
+ box.intrinsic = { width: 0, height: 0, missing: 0, ratio: 0 };
1086
+ this._ws = after('atomic', this._ws, this._ws);
1087
+ this._lettered = false;
1088
+ this._abandonLetter();
1089
+ }
288
1090
  /** A text node, whitespace-processed per the inherited `white-space`. */
289
1091
  _textNode(data, into, style, owner) {
1092
+ // text set at no size draws nothing and takes no room, so it needs no
1093
+ // box — and it is no part of the white space around it either
1094
+ if (!(style.fontSize > 0))
1095
+ return;
1096
+ // a word does not run on over the start of a line
1097
+ if (this._ws === 'start')
1098
+ this._lettered = false;
290
1099
  const ws = style.whiteSpace;
291
1100
  let text;
292
1101
  if (ws === 'pre' || ws === 'pre-wrap') {
293
1102
  text = data;
294
- }
295
- else if (ws === 'pre-line') {
296
- text = data.replace(/[ \t]+/g, ' ').replace(/ *\n */g, '\n');
1103
+ if (!text)
1104
+ return;
1105
+ // preserved spaces do not collapse with the ones after them
1106
+ this._ws = text.endsWith('\n') ? 'start' : 'content';
297
1107
  }
298
1108
  else {
299
- text = data.replace(/[\t\n\r\f ]+/g, ' ');
300
- // A run of whitespace against the start of a block collapses away
301
- // entirely; between two inline boxes it collapses to one space, which
302
- // the replace above already did.
303
- if (text === ' ' && !hasInlineContent(into))
1109
+ text =
1110
+ ws === 'pre-line'
1111
+ ? data.replace(/[ \t]+/g, ' ').replace(/ *\n */g, '\n')
1112
+ : COLLAPSIBLE.test(data)
1113
+ ? data.replace(/[\t\n\r\f ]+/g, ' ')
1114
+ : data;
1115
+ // A space at the start of a line goes, and so does one after another
1116
+ // space, across element boundaries (CSS 2.1 16.6.1): `<p>\n Hi` has
1117
+ // no space before the H, and `Hi <b> there</b>` has one between.
1118
+ if (text.charCodeAt(0) === 32 && this._ws !== 'content') {
1119
+ text = text.slice(1);
1120
+ }
1121
+ if (!text)
304
1122
  return;
1123
+ const last = text.charCodeAt(text.length - 1);
1124
+ this._ws = last === 32 ? 'space' : last === 10 ? 'start' : 'content';
305
1125
  }
306
- if (!text)
1126
+ text = transformText(text, style.textTransform, this._lettered);
1127
+ this._lettered = letteredAfter(text, this._lettered);
1128
+ const search = this._firstLetter;
1129
+ const letter = search ? FIRST_LETTER.exec(text) : null;
1130
+ if (!search || !letter) {
1131
+ const punctuation = search ? PUNCTUATION_ONLY.exec(text) : null;
1132
+ if (!search || !punctuation) {
1133
+ this._textBox(text, into, style, owner);
1134
+ return;
1135
+ }
1136
+ // punctuation the letter comes after, in a text of its own —
1137
+ // `<q>`'s open quote — takes the letter's style, and gives it back
1138
+ // if no letter follows on the line
1139
+ const start = punctuation[1].length;
1140
+ if (start > 0)
1141
+ this._textBox(text.slice(0, start), into, style, owner);
1142
+ search.punctuation.push(this._letterBox(search, text.slice(start), into, style, owner));
307
1143
  return;
308
- text = transformText(text, style.textTransform);
1144
+ }
1145
+ // The first letter, with the punctuation around it, in a box of its
1146
+ // own inside the box it was found in, and so inheriting from that
1147
+ // (CSS 2.1 5.12.2): `<p><b>T</b>his` has a bold first letter.
1148
+ this._firstLetter = null;
1149
+ const start = letter[1].length;
1150
+ const end = letter[0].length;
1151
+ if (start > 0)
1152
+ this._textBox(text.slice(0, start), into, style, owner);
1153
+ this._letterBox(search, text.slice(start, end), into, style, owner);
1154
+ if (end < text.length) {
1155
+ this._textBox(text.slice(end), into, style, owner);
1156
+ }
1157
+ }
1158
+ /** A box of the first letter's style around `text`, in `into`. */
1159
+ _letterBox(search, text, into, style, owner) {
1160
+ const cascade = this._options.cascade;
1161
+ const letterStyle = cascade.firstLetterStyle(search.rules, style);
1162
+ const box = new Box(boxKindFor(letterStyle.display), null, letterStyle);
1163
+ box.pseudo = 'first-letter';
1164
+ if (letterStyle.float !== 'none') {
1165
+ box.isFloat = true;
1166
+ if (into.kind === 'inline')
1167
+ this._nestedOutOfLine = true;
1168
+ }
1169
+ into.append(box);
1170
+ if (letterStyle.textTransform !== style.textTransform) {
1171
+ text = transformText(text, letterStyle.textTransform);
1172
+ }
1173
+ return { box, text: this._textBox(text, box, letterStyle, owner), style };
1174
+ }
1175
+ /**
1176
+ * Where the search for a first letter stands after a box. A box that was
1177
+ * no part of the line — a float, a positioned box — leaves it where it
1178
+ * was, and one that was something other than a letter on it ends it. A
1179
+ * block that looked for a letter of its own and found its first line
1180
+ * ended its parent's too, and one that found no line leaves the parent
1181
+ * looking. A block ends the line it is on.
1182
+ */
1183
+ _letterAfter(flow, skipped, outer, own) {
1184
+ const lined = own !== null && this._firstLetter !== own;
1185
+ if (own && !lined)
1186
+ this._abandonLetter();
1187
+ if (skipped || own)
1188
+ this._firstLetter = skipped || !lined ? outer : null;
1189
+ if (lined && !skipped && outer)
1190
+ giveBack(outer);
1191
+ if (flow === 'atomic')
1192
+ this._abandonLetter();
1193
+ else if (flow === 'block' && this._firstLetter)
1194
+ giveBack(this._firstLetter);
1195
+ }
1196
+ /** The first line ended, or began with something other than a letter:
1197
+ * there is no first letter, and punctuation that took its style gives
1198
+ * it back. */
1199
+ _abandonLetter() {
1200
+ const search = this._firstLetter;
1201
+ this._firstLetter = null;
1202
+ if (search)
1203
+ giveBack(search);
1204
+ }
1205
+ _textBox(text, into, style, owner) {
309
1206
  // The owning element rides on the text box, and from there onto the
310
1207
  // `TextRun`: hit testing inside a paragraph has no rectangle to test —
311
1208
  // an inline box is the runs on its lines — so the run is what has to
@@ -314,19 +1211,198 @@ class Builder {
314
1211
  box.text = text;
315
1212
  into.append(box);
316
1213
  this._push(text, box);
1214
+ return box;
1215
+ }
1216
+ /**
1217
+ * A line ends here — a block's inline content is over, or a `<br>` or a
1218
+ * block interrupts it — and a collapsible space it ends on goes (CSS 2.1
1219
+ * 16.6.1). Content standing on a space means that space was the last
1220
+ * thing pushed, so taking it back is a character off the last chunk; a
1221
+ * text box it empties goes with it.
1222
+ */
1223
+ _endLine() {
1224
+ if (this._ws !== 'space')
1225
+ return;
1226
+ this._ws = 'start';
1227
+ const box = this._textBoxes[this._textBoxes.length - 1];
1228
+ if (!box || box.textEnd !== this._length || !box.text.endsWith(' ')) {
1229
+ return;
1230
+ }
1231
+ box.text = box.text.slice(0, -1);
1232
+ box.textEnd -= 1;
1233
+ this._length -= 1;
1234
+ const last = this._chunks.length - 1;
1235
+ this._chunks[last] = this._chunks[last].slice(0, -1);
1236
+ if (!box.text) {
1237
+ this._textBoxes.pop();
1238
+ const siblings = box.parent?.children;
1239
+ const at = siblings ? siblings.lastIndexOf(box) : -1;
1240
+ if (at >= 0)
1241
+ siblings.splice(at, 1);
1242
+ }
317
1243
  }
318
1244
  /** Give a box its slice of the document text index. */
319
1245
  _push(text, box) {
320
- box.textStart = this._text.length;
321
- this._text += text;
322
- box.textEnd = this._text.length;
323
- if (box.kind === 'text')
1246
+ box.textStart = this._length;
1247
+ this._chunks.push(text);
1248
+ this._length += text.length;
1249
+ box.textEnd = this._length;
1250
+ if (box.kind === 'text') {
324
1251
  this._textBoxes.push(box);
1252
+ if (box.style !== this._lastTextStyle) {
1253
+ this._lastTextStyle = box.style;
1254
+ this._textStyles.add(box.style);
1255
+ }
1256
+ }
325
1257
  }
326
1258
  }
1259
+ /**
1260
+ * The CSS counters in scope as the builder walks the document (CSS Lists 3,
1261
+ * 4.5). Every element and pseudo-element opens a level, which closes after
1262
+ * what it holds. A `counter-reset` makes an instance that reaches the
1263
+ * element's descendants and — where its parent has no counter of the name
1264
+ * — its later siblings too, so that instance belongs to the level the
1265
+ * element is on, its parent's, and goes when that closes; a later sibling's
1266
+ * reset takes its place. Where its parent has one, the new instance nests
1267
+ * in it and reaches the element's descendants alone, on the element's own
1268
+ * level: a list in a list item, a counter reset in a `::before`. `counter()`
1269
+ * reads the innermost instance and `counters()` all of them, outermost
1270
+ * first. A counter used where none is in scope is reset to 0 there, as
1271
+ * though the element had asked.
1272
+ */
1273
+ class CounterScopes {
1274
+ /** Per name, its instances, outermost first: the level each goes with,
1275
+ * whether it is the own one of the element that level is, and whether it
1276
+ * counts down. */
1277
+ _instances = new Map();
1278
+ /** The names each open level made an instance of, so closing it drops
1279
+ * exactly those: null for a level that made none, which is nearly every
1280
+ * one, so that opening an element allocates nothing. */
1281
+ _made = [null];
1282
+ /** The levels of the open elements with style containment, innermost
1283
+ * last: below one, a counter made further out is not counted on. */
1284
+ _contained = [];
1285
+ open() {
1286
+ this._made.push(null);
1287
+ }
1288
+ close() {
1289
+ const level = this._made.length - 1;
1290
+ const made = this._made.pop();
1291
+ if (made) {
1292
+ for (const name of made) {
1293
+ const stack = this._instances.get(name);
1294
+ if (stack && stack[stack.length - 1]?.level === level)
1295
+ stack.pop();
1296
+ }
1297
+ }
1298
+ const contained = this._contained;
1299
+ if (contained[contained.length - 1] === level)
1300
+ contained.pop();
1301
+ }
1302
+ /** The element whose level is open has style containment. */
1303
+ contain() {
1304
+ this._contained.push(this._made.length - 1);
1305
+ }
1306
+ /** Whether the parent of the element whose level is open has a counter
1307
+ * of this name: its own, or one from further out — not one a sibling
1308
+ * before the element made. */
1309
+ _parentHas(name) {
1310
+ const stack = this._instances.get(name);
1311
+ if (!stack?.length)
1312
+ return false;
1313
+ const parent = this._made.length - 2;
1314
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
1315
+ const instance = stack[i];
1316
+ if (instance.level < parent)
1317
+ return true;
1318
+ if (instance.level === parent && instance.own)
1319
+ return true;
1320
+ }
1321
+ return false;
1322
+ }
1323
+ /** Whether a reset of `name` here reaches the element's later siblings,
1324
+ * or its descendants alone. */
1325
+ reachesSiblings(name) {
1326
+ return this._made.length < 2 || !this._parentHas(name);
1327
+ }
1328
+ reset(name, value, reversed) {
1329
+ const own = !this.reachesSiblings(name);
1330
+ const level = own ? this._made.length - 1 : this._made.length - 2;
1331
+ let stack = this._instances.get(name);
1332
+ if (!stack) {
1333
+ stack = [];
1334
+ this._instances.set(name, stack);
1335
+ }
1336
+ const top = stack[stack.length - 1];
1337
+ if (top?.level === level && top.own === own) {
1338
+ top.value = value;
1339
+ top.reversed = reversed;
1340
+ return;
1341
+ }
1342
+ stack.push({ level, own, value, reversed });
1343
+ (this._made[level] ??= []).push(name);
1344
+ }
1345
+ /** The innermost instance, made here at 0 where there is none. */
1346
+ _innermost(name) {
1347
+ let stack = this._instances.get(name);
1348
+ if (!stack?.length) {
1349
+ this.reset(name, 0, false);
1350
+ stack = this._instances.get(name);
1351
+ }
1352
+ return stack[stack.length - 1];
1353
+ }
1354
+ increment(name, by) {
1355
+ const instance = this._counted(name);
1356
+ instance.value = clampCounter(instance.value + by);
1357
+ }
1358
+ set(name, value) {
1359
+ this._counted(name).value = value;
1360
+ }
1361
+ /** The instance an increment or a set counts on: the innermost, unless
1362
+ * it was made outside an element with style containment that this is
1363
+ * inside, where a new one is made instead, as though this reset it for
1364
+ * itself and its later siblings — which one outside the element's parent
1365
+ * then does not see (CSS Containment 2, 3.4). */
1366
+ _counted(name) {
1367
+ const instance = this._innermost(name);
1368
+ const bound = this._contained[this._contained.length - 1];
1369
+ const level = this._made.length - 1;
1370
+ if (bound === undefined || level <= bound || instance.level >= bound) {
1371
+ return instance;
1372
+ }
1373
+ const parent = level - 1;
1374
+ const fresh = { level: parent, own: false, value: 0, reversed: false };
1375
+ this._instances.get(name).push(fresh);
1376
+ (this._made[parent] ??= []).push(name);
1377
+ return fresh;
1378
+ }
1379
+ /** Whether the innermost instance counts down: a list item takes one
1380
+ * from it rather than adding one. */
1381
+ reversed(name) {
1382
+ return this._innermost(name).reversed;
1383
+ }
1384
+ value(name) {
1385
+ return this._innermost(name).value;
1386
+ }
1387
+ values(name) {
1388
+ this._innermost(name);
1389
+ return this._instances.get(name).map((instance) => instance.value);
1390
+ }
1391
+ }
1392
+ /** A counter is a 32-bit integer in a browser, and saturates there. */
1393
+ function clampCounter(value) {
1394
+ return Math.max(-2147483648, Math.min(2147483647, value));
1395
+ }
327
1396
  /** Whether the parsed document has a `<body>`. htmlparser2 does not
328
1397
  * synthesise one — it parses what it was given — so a fragment has none,
329
1398
  * and the root box stands in for it. */
1399
+ function hasHtml(root) {
1400
+ for (const child of childrenOf(root)) {
1401
+ if (isElement(child) && tagOf(child) === 'html')
1402
+ return true;
1403
+ }
1404
+ return false;
1405
+ }
330
1406
  function hasBody(root) {
331
1407
  for (const child of childrenOf(root)) {
332
1408
  if (!isElement(child))
@@ -339,6 +1415,13 @@ function hasBody(root) {
339
1415
  }
340
1416
  return false;
341
1417
  }
1418
+ /** A canvas's bitmap dimension: a non-negative integer, as HTML parses
1419
+ * one, or its default where the attribute is missing or no number. */
1420
+ function canvasSize(el, name, fallback) {
1421
+ const raw = attr(el, name);
1422
+ const n = raw == null ? NaN : parseInt(raw.trim(), 10);
1423
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
1424
+ }
342
1425
  function numberAttr(el, name) {
343
1426
  const raw = attr(el, name);
344
1427
  if (!raw)
@@ -346,28 +1429,222 @@ function numberAttr(el, name) {
346
1429
  const n = parseFloat(raw);
347
1430
  return Number.isFinite(n) ? n : null;
348
1431
  }
349
- function hasInlineContent(box) {
350
- for (let i = box.children.length - 1; i >= 0; i -= 1) {
351
- const kind = box.children[i].kind;
352
- if (kind === 'text' || kind === 'inline' || kind === 'replaced')
353
- return true;
354
- if (kind === 'block' || kind === 'flex' || kind === 'table')
355
- return false;
356
- }
357
- return false;
358
- }
359
- function transformText(text, transform) {
1432
+ function transformText(text, transform, lettered = false) {
360
1433
  switch (transform) {
361
1434
  case 'uppercase':
362
1435
  return text.toUpperCase();
363
1436
  case 'lowercase':
364
1437
  return text.toLowerCase();
365
1438
  case 'capitalize':
366
- return text.replace(/(^|\s)(\S)/g, (_, sp, c) => sp + c.toUpperCase());
1439
+ return capitalize(text, lettered);
367
1440
  default:
368
1441
  return text;
369
1442
  }
370
1443
  }
1444
+ /** A letter or a number: what a word's first typographic letter unit is
1445
+ * (CSS Text 3, 2.1). */
1446
+ const LETTER_UNIT = /[\p{L}\p{N}]/u;
1447
+ /**
1448
+ * What does not end a word (UAX #29): a combining mark, and the
1449
+ * punctuation that joins letters into one — `x.y`, `don't`, `a:b`. Any
1450
+ * other character that is not a letter or a number is between words: a
1451
+ * space, a no-break space, a hyphen, a bracket.
1452
+ */
1453
+ const IN_WORD = /[\p{M}'.:\u00b7\u0387\u05f4\u2018\u2019\u2024\u2027\ufe13\ufe52\ufe55\uff07\uff0e\uff1a]/u;
1454
+ /**
1455
+ * `text-transform: capitalize` (CSS Text 3, 2.1): the first letter or
1456
+ * number of each word in upper case, and nothing else. Punctuation a word
1457
+ * starts with is not its first letter — `(p.p.)` is `(P.p.)` — and a word
1458
+ * the text continues, `lettered`, has had its own: `<b>fo</b>o` is `Foo`.
1459
+ */
1460
+ function capitalize(text, lettered) {
1461
+ let out = '';
1462
+ let from = 0;
1463
+ let at = 0;
1464
+ for (const ch of text) {
1465
+ if (LETTER_UNIT.test(ch)) {
1466
+ if (!lettered) {
1467
+ const title = titleCase(ch);
1468
+ if (title !== ch) {
1469
+ out += text.slice(from, at) + title;
1470
+ from = at + ch.length;
1471
+ }
1472
+ lettered = true;
1473
+ }
1474
+ }
1475
+ else if (!IN_WORD.test(ch)) {
1476
+ lettered = false;
1477
+ }
1478
+ at += ch.length;
1479
+ }
1480
+ return from ? out + text.slice(from) : text;
1481
+ }
1482
+ /**
1483
+ * A letter in title case (Unicode's SpecialCasing), which is its upper case
1484
+ * but for a letter that is two: a digraph is capitalized as its first
1485
+ * letter alone — `dž` as `Dž`, where upper case is `DŽ` — and a Greek vowel
1486
+ * with a subscript iota keeps the iota subscript, where upper case writes
1487
+ * it out. A letter whose upper case is two letters, as `ß` is `SS`, is the
1488
+ * first of them: `Ss`.
1489
+ */
1490
+ function titleCase(ch) {
1491
+ const cp = ch.codePointAt(0);
1492
+ if (cp >= 0x1c4 && cp <= 0x1cc) {
1493
+ return String.fromCharCode(0x1c5 + 3 * Math.floor((cp - 0x1c4) / 3));
1494
+ }
1495
+ if (cp >= 0x1f1 && cp <= 0x1f3)
1496
+ return '\u01f2';
1497
+ if (cp >= 0x1f80 && cp <= 0x1faf)
1498
+ return String.fromCharCode(cp | 0x08);
1499
+ if (cp === 0x1fb3 || cp === 0x1fc3 || cp === 0x1ff3) {
1500
+ return String.fromCharCode(cp + 9);
1501
+ }
1502
+ if (cp === 0x1fbc || cp === 0x1fcc || cp === 0x1ffc)
1503
+ return ch;
1504
+ const upper = ch.toUpperCase();
1505
+ if (upper.length <= ch.length)
1506
+ return upper;
1507
+ const first = String.fromCodePoint(upper.codePointAt(0));
1508
+ return first + upper.slice(first.length).toLowerCase();
1509
+ }
1510
+ /**
1511
+ * Whether a word is under way after `text`, as `capitalize` reads words: its
1512
+ * last letter, number or character between words decides, and a text of
1513
+ * nothing but joining punctuation leaves it as it was. Asked of every text,
1514
+ * from the end, where it is almost always decided by the last character.
1515
+ */
1516
+ function letteredAfter(text, lettered) {
1517
+ for (let i = text.length - 1; i >= 0; i -= 1) {
1518
+ const c = text.charCodeAt(i);
1519
+ if (c < 0x80) {
1520
+ if ((c >= 0x61 && c <= 0x7a) ||
1521
+ (c >= 0x41 && c <= 0x5a) ||
1522
+ (c >= 0x30 && c <= 0x39)) {
1523
+ return true;
1524
+ }
1525
+ if (c === 0x27 || c === 0x2e || c === 0x3a)
1526
+ continue;
1527
+ return false;
1528
+ }
1529
+ // a low surrogate is read with the high one before it
1530
+ const start = c >= 0xdc00 && c <= 0xdfff && i > 0 && isHighSurrogate(text, i - 1)
1531
+ ? i - 1
1532
+ : i;
1533
+ const ch = text.slice(start, i + 1);
1534
+ if (LETTER_UNIT.test(ch))
1535
+ return true;
1536
+ if (!IN_WORD.test(ch))
1537
+ return false;
1538
+ i = start;
1539
+ }
1540
+ return lettered;
1541
+ }
1542
+ function isHighSurrogate(text, i) {
1543
+ const c = text.charCodeAt(i);
1544
+ return c >= 0xd800 && c <= 0xdbff;
1545
+ }
1546
+ /** Whether a word is under way after a box, as `after` says where white
1547
+ * space stands: an inline box leaves it as its content did, an atomic
1548
+ * inline and a block end it, and a float or a positioned box leaves the
1549
+ * word around it as it was. */
1550
+ function letteredAfterFlow(flow, inside, before) {
1551
+ if (flow === 'inline')
1552
+ return inside;
1553
+ if (flow === 'out')
1554
+ return before;
1555
+ return false;
1556
+ }
1557
+ /**
1558
+ * A text's first letter, with the punctuation before and after it that CSS
1559
+ * 2.1 5.12.2 counts in — the Ps, Pe, Pi, Pf and Po classes — and the white
1560
+ * space before it in the first group. No match when the text ends, or
1561
+ * reaches a space, before any letter: its first letter is in a later text,
1562
+ * as browsers read it.
1563
+ */
1564
+ const FIRST_LETTER = /^([ \t\n\r\f\u00a0]*)(?:[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*)*[^ \t\n\r\f\u00a0\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*(?:[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*)*/u;
1565
+ /** A text that is punctuation and nothing else, but for the white space
1566
+ * before it in the first group. */
1567
+ const PUNCTUATION_ONLY = /^([ \t\n\r\f\u00a0]*)(?:[\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]\p{M}*)+$/u;
1568
+ /** Put punctuation a search styled back in its own box and style: the
1569
+ * letter it was waiting for is not on its line. */
1570
+ function giveBack(search) {
1571
+ for (const { box, text, style } of search.punctuation) {
1572
+ const parent = box.parent;
1573
+ if (!parent)
1574
+ continue;
1575
+ const at = parent.children.indexOf(box);
1576
+ if (at < 0)
1577
+ continue;
1578
+ parent.children[at] = text;
1579
+ text.parent = parent;
1580
+ text.style = style;
1581
+ }
1582
+ search.punctuation.length = 0;
1583
+ }
1584
+ /** Whether a box of this display is a block container, which is what can
1585
+ * have a first letter. */
1586
+ function hasFirstLetter(display) {
1587
+ switch (display) {
1588
+ case 'block':
1589
+ case 'inline-block':
1590
+ case 'list-item':
1591
+ case 'table-cell':
1592
+ case 'table-caption':
1593
+ return true;
1594
+ default:
1595
+ return false;
1596
+ }
1597
+ }
1598
+ /**
1599
+ * How a box sits in its parent's inline content, for white space. An inline
1600
+ * box's content is its parent's own; an atomic inline — an inline-block, an
1601
+ * image — is one piece of it; a block ends the line it interrupts and starts
1602
+ * another; a float or a positioned box is no part of it at all.
1603
+ */
1604
+ function flowOf(style, box) {
1605
+ if (box.outOfFlow || box.isFloat)
1606
+ return 'out';
1607
+ // an image told to be a table's part is an inline one (`isBlockLevel`)
1608
+ if (box.kind === 'replaced' && style.display.startsWith('table-')) {
1609
+ return 'atomic';
1610
+ }
1611
+ switch (style.display) {
1612
+ case 'inline':
1613
+ return box.kind === 'replaced' ? 'atomic' : 'inline';
1614
+ case 'inline-block':
1615
+ case 'inline-table':
1616
+ case 'inline-flex':
1617
+ return 'atomic';
1618
+ case 'table-row-group':
1619
+ case 'table-header-group':
1620
+ case 'table-footer-group':
1621
+ case 'table-row':
1622
+ case 'table-cell':
1623
+ case 'table-caption':
1624
+ case 'table-column':
1625
+ case 'table-column-group':
1626
+ // table parts in an inline box are given an inline table around them
1627
+ // (CSS 2.1 17.2.1), which sits in the line as an inline-block does:
1628
+ // the white space either side of it is the line's
1629
+ return box.parent?.kind === 'inline' ? 'atomic' : 'block';
1630
+ default:
1631
+ return 'block';
1632
+ }
1633
+ }
1634
+ /** Where the inline content stands after a box, from where it stood inside
1635
+ * the box and before it. */
1636
+ function after(flow, inside, before) {
1637
+ switch (flow) {
1638
+ case 'inline':
1639
+ return inside;
1640
+ case 'atomic':
1641
+ return 'content';
1642
+ case 'out':
1643
+ return before;
1644
+ default:
1645
+ return 'start';
1646
+ }
1647
+ }
371
1648
  function boxKindFor(display) {
372
1649
  switch (display) {
373
1650
  case 'inline':
@@ -400,18 +1677,37 @@ function boxKindFor(display) {
400
1677
  return 'block';
401
1678
  }
402
1679
  }
1680
+ /** A replaced box's intrinsic size from what its content says, in CSS
1681
+ * pixels: an axis it does not give takes the default object size. */
1682
+ function setIntrinsics(box, size, scale) {
1683
+ box.intrinsic = {
1684
+ width: (size.width ?? 300) * scale,
1685
+ height: (size.height ?? 150) * scale,
1686
+ missing: (size.width === null ? 1 : 0) | (size.height === null ? 2 : 0),
1687
+ ratio: size.ratio,
1688
+ };
1689
+ }
403
1690
  function replacedKind(el, tag) {
404
1691
  switch (tag) {
405
1692
  case 'img':
406
1693
  return 'image';
1694
+ case 'svg':
1695
+ // XHTML's `<svg:svg>` too, which `tagOf` names `svg`
1696
+ return 'svg';
407
1697
  case 'hr':
408
1698
  return 'hr';
1699
+ case 'iframe':
1700
+ case 'video':
1701
+ case 'embed':
1702
+ case 'canvas':
1703
+ return 'frame';
409
1704
  case 'textarea':
410
1705
  return 'textarea';
411
1706
  case 'select':
412
1707
  return 'select';
413
- case 'button':
414
- return 'button';
1708
+ // A `<button>` is not one: its content is the document's, laid out and
1709
+ // drawn like any box's (the UA sheet's `button` rule), where an
1710
+ // `<input type=submit>` has only a value to show and is a widget.
415
1711
  case 'input': {
416
1712
  const type = (attr(el, 'type') ?? 'text').toLowerCase();
417
1713
  if (type === 'checkbox')
@@ -428,82 +1724,28 @@ function replacedKind(el, tag) {
428
1724
  return 'none';
429
1725
  }
430
1726
  }
431
- const ROMAN = [
432
- [1000, 'm'],
433
- [900, 'cm'],
434
- [500, 'd'],
435
- [400, 'cd'],
436
- [100, 'c'],
437
- [90, 'xc'],
438
- [50, 'l'],
439
- [40, 'xl'],
440
- [10, 'x'],
441
- [9, 'ix'],
442
- [5, 'v'],
443
- [4, 'iv'],
444
- [1, 'i'],
445
- ];
446
1727
  /**
447
- * The marker a `list-item` draws. The counter is the *builder's*, not the
448
- * element's, because `value` on an `<li>` restarts it and a nested list has
449
- * its own — both of which are lost if the number is derived from the index
450
- * of the child in its parent.
1728
+ * The marker a `list-item` draws: its `list-item` counter written in its
1729
+ * `list-style-type`, between the style's prefix and suffix, or the string
1730
+ * the type is. The counter is the builder's (`CounterScopes`), which is
1731
+ * what lets `value` on an `<li>`, `start` and `reversed` on an `<ol>`, a
1732
+ * list in a list and an author's own `counter-reset` and `counter-set` all
1733
+ * count as a browser counts them. A suffix that ends in a space — `. `, a
1734
+ * bullet's — is written without it, and the layout sets the marker off by
1735
+ * its own gap; one that does not, `、`, sets the marker against the text.
451
1736
  */
452
- function markerFor(el, style, counters) {
1737
+ function markerFor(style, n, styles) {
453
1738
  const type = style.listStyleType;
454
1739
  if (type === 'none')
455
- return '';
456
- const depth = counters.length;
457
- if (depth) {
458
- const value = numberAttr(el, 'value');
459
- if (value !== null)
460
- counters[depth - 1] = value;
461
- }
462
- const n = depth ? counters[depth - 1]++ : 1;
463
- switch (type) {
464
- case 'decimal':
465
- return `${n}.`;
466
- case 'decimal-leading-zero':
467
- return `${n < 10 ? '0' : ''}${n}.`;
468
- case 'lower-alpha':
469
- case 'lower-latin':
470
- return `${alpha(n).toLowerCase()}.`;
471
- case 'upper-alpha':
472
- case 'upper-latin':
473
- return `${alpha(n)}.`;
474
- case 'lower-roman':
475
- return `${roman(n)}.`;
476
- case 'upper-roman':
477
- return `${roman(n).toUpperCase()}.`;
478
- case 'circle':
479
- return '◦';
480
- case 'square':
481
- return '▪';
482
- case 'disc':
483
- default:
484
- return '•';
485
- }
486
- }
487
- function alpha(n) {
488
- let out = '';
489
- let v = Math.max(1, n);
490
- while (v > 0) {
491
- const rem = (v - 1) % 26;
492
- out = String.fromCharCode(65 + rem) + out;
493
- v = Math.floor((v - 1) / 26);
494
- }
495
- return out;
496
- }
497
- function roman(n) {
498
- let v = Math.max(1, Math.min(3999, n));
499
- let out = '';
500
- for (const [value, sym] of ROMAN) {
501
- while (v >= value) {
502
- out += sym;
503
- v -= value;
504
- }
505
- }
506
- return out;
1740
+ return { text: '', flush: false };
1741
+ if (type.startsWith('"'))
1742
+ return { text: type.slice(1), flush: true };
1743
+ const { prefix, text, suffix } = styles.marker(n, type, style.direction === 'rtl');
1744
+ const spaced = /\s$/.test(suffix);
1745
+ return {
1746
+ text: prefix + text + (spaced ? suffix.trimEnd() : suffix),
1747
+ flush: !spaced,
1748
+ };
507
1749
  }
508
1750
  /**
509
1751
  * Give every box the document range its subtree covers. Runs after `fixUp`,
@@ -546,23 +1788,38 @@ function assignSubtreeRanges(box) {
546
1788
  * straight to `<tr>`, and CSS says the missing row group is generated
547
1789
  * rather than the rows being dropped.
548
1790
  */
549
- function fixUp(box) {
1791
+ function fixUp(box, anonymous) {
550
1792
  for (const child of box.children)
551
- fixUp(child);
1793
+ fixUp(child, anonymous);
1794
+ fixUpOwn(box, anonymous);
1795
+ }
1796
+ /** `fixUp` of a box whose children have had theirs. */
1797
+ function fixUpOwn(box, anonymous) {
552
1798
  if (box.kind === 'table') {
553
- fixUpTable(box);
1799
+ fixUpTable(box, anonymous);
554
1800
  return;
555
1801
  }
556
1802
  if (box.kind === 'table-row-group') {
557
- wrapOrphans(box, 'table-row', (k) => k === 'table-row');
1803
+ wrapOrphans(box, 'table-row', (k) => k === 'table-row', anonymous);
558
1804
  return;
559
1805
  }
560
1806
  if (box.kind === 'table-row') {
561
- wrapOrphans(box, 'table-cell', (k) => k === 'table-cell');
1807
+ wrapOrphans(box, 'table-cell', (k) => k === 'table-cell', anonymous);
562
1808
  return;
563
1809
  }
1810
+ // a column group holds its columns, which are where they belong, and the
1811
+ // builder kept nothing else in it
1812
+ if (box.style.display === 'table-column-group')
1813
+ return;
564
1814
  if (!box.children.length)
565
1815
  return;
1816
+ wrapTableParts(box, anonymous);
1817
+ // An inline box holds no block: a block in one is its block container's,
1818
+ // which breaks the inline box around it
1819
+ if (box.kind === 'inline')
1820
+ return;
1821
+ if (box.kind !== 'flex')
1822
+ breakInlines(box);
566
1823
  let hasBlockLevel = false;
567
1824
  let hasInlineLevel = false;
568
1825
  for (const child of box.children) {
@@ -591,7 +1848,7 @@ function fixUp(box) {
591
1848
  // finds itself; it does not force an anonymous block on its own.
592
1849
  if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
593
1850
  if (run) {
594
- next.push(anonymousBlock(box, run));
1851
+ next.push(anonymousOf(box, 'block', run, anonymous));
595
1852
  run = null;
596
1853
  }
597
1854
  next.push(child);
@@ -599,28 +1856,140 @@ function fixUp(box) {
599
1856
  }
600
1857
  // Whitespace between two blocks is not content and must not generate a
601
1858
  // line box — `<div><p>a</p> <p>b</p></div>` has no blank line in it.
602
- if (!run && child.kind === 'text' && !child.text.trim())
1859
+ if (!run && child.kind === 'text' && isBlank(child.text))
603
1860
  continue;
604
1861
  (run ??= []).push(child);
605
1862
  }
606
1863
  if (run) {
607
- if (run.every((c) => c.kind === 'text' && !c.text.trim())) {
1864
+ if (run.every((c) => c.kind === 'text' && isBlank(c.text))) {
608
1865
  // trailing whitespace after the last block: same rule
609
1866
  }
610
1867
  else {
611
- next.push(anonymousBlock(box, run));
1868
+ next.push(anonymousOf(box, 'block', run, anonymous));
612
1869
  }
613
1870
  }
614
1871
  box.children = next;
615
1872
  }
616
- function anonymousBlock(parent, run) {
617
- const box = new Box('block', null, parent.style);
618
- box.parent = parent;
619
- for (const child of run) {
620
- child.parent = box;
621
- box.children.push(child);
1873
+ /**
1874
+ * Break a block container's inline children around the blocks in them
1875
+ * (CSS 2.1 9.2.1.1): an inline box with an in-flow block in it — at any
1876
+ * depth of inline box — becomes the pieces of it before, between and after
1877
+ * its blocks, and the blocks stand beside them as the container's own, for
1878
+ * the mixed-content rule to wrap the pieces in anonymous blocks.
1879
+ * `<font><p>One</p><p>Two</p></font>` laid its paragraphs side by side as
1880
+ * inline-blocks, and a link around a card's blocks set them in a line.
1881
+ */
1882
+ function breakInlines(box) {
1883
+ let next = null;
1884
+ for (let i = 0; i < box.children.length; i += 1) {
1885
+ const child = box.children[i];
1886
+ const pieces = child.kind === 'inline' && holdsBlock(child) ? breakAround(child) : null;
1887
+ if (!pieces) {
1888
+ next?.push(child);
1889
+ continue;
1890
+ }
1891
+ next ??= box.children.slice(0, i);
1892
+ for (const piece of pieces) {
1893
+ piece.parent = box;
1894
+ next.push(piece);
1895
+ }
622
1896
  }
623
- return box;
1897
+ if (next)
1898
+ box.children = next;
1899
+ }
1900
+ /** Whether an inline box has an in-flow block in it, at any depth of
1901
+ * inline box: asked first, and without making anything, since nearly
1902
+ * every inline box has none. */
1903
+ function holdsBlock(inline) {
1904
+ for (const child of inline.children) {
1905
+ if (child.kind === 'inline') {
1906
+ if (holdsBlock(child))
1907
+ return true;
1908
+ }
1909
+ else if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
1910
+ return true;
1911
+ }
1912
+ }
1913
+ return false;
1914
+ }
1915
+ /**
1916
+ * An inline box broken around the in-flow blocks in it: its pieces, each a
1917
+ * box of its element and style holding the content between two blocks,
1918
+ * with the blocks between them; null where it has none, which is nearly
1919
+ * every inline box. A piece has no edge on a side a block cut: the start of
1920
+ * the first and the end of the last are the box's own.
1921
+ */
1922
+ function breakAround(inline) {
1923
+ let out = null;
1924
+ const pieces = [];
1925
+ let run = [];
1926
+ const close = () => {
1927
+ const piece = new Box('inline', inline.el, inline.style);
1928
+ piece.pseudo = inline.pseudo;
1929
+ for (const child of run)
1930
+ piece.append(child);
1931
+ pieces.push(piece);
1932
+ (out ??= []).push(piece);
1933
+ run = [];
1934
+ };
1935
+ const blocks = [];
1936
+ for (const child of inline.children) {
1937
+ if (isBlockLevel(child) && !child.outOfFlow && !child.isFloat) {
1938
+ close();
1939
+ out.push(child);
1940
+ blocks.push(child);
1941
+ continue;
1942
+ }
1943
+ const inner = child.kind === 'inline' && holdsBlock(child) ? breakAround(child) : null;
1944
+ if (!inner) {
1945
+ run.push(child);
1946
+ continue;
1947
+ }
1948
+ for (const piece of inner) {
1949
+ if (piece.kind === 'inline') {
1950
+ run.push(piece);
1951
+ }
1952
+ else {
1953
+ close();
1954
+ out.push(piece);
1955
+ blocks.push(piece);
1956
+ }
1957
+ }
1958
+ }
1959
+ if (!out)
1960
+ return null;
1961
+ close();
1962
+ for (let i = 0; i < pieces.length; i += 1) {
1963
+ pieces[i].cut = (i > 0 ? 1 : 0) | (i < pieces.length - 1 ? 2 : 0);
1964
+ }
1965
+ if (isRelative(inline.style))
1966
+ CUT_BLOCKS.set(pieces[0], blocks);
1967
+ const fade = inline.style.opacity;
1968
+ if (fade < 1) {
1969
+ for (const block of blocks) {
1970
+ FADED_BLOCKS.set(block, (FADED_BLOCKS.get(block) ?? 1) * fade);
1971
+ }
1972
+ }
1973
+ return out;
1974
+ }
1975
+ function anonymousStyles(initial) {
1976
+ const made = new WeakMap();
1977
+ return (parent, display) => {
1978
+ let byDisplay = made.get(parent.style);
1979
+ if (!byDisplay) {
1980
+ byDisplay = new Map();
1981
+ made.set(parent.style, byDisplay);
1982
+ }
1983
+ let style = byDisplay.get(display);
1984
+ if (!style) {
1985
+ // `display` is the one property it does not start from: it is the
1986
+ // box the fix-up made, and layout asks the style what a box is
1987
+ style = inherit(parent.style, initial);
1988
+ style.display = display;
1989
+ byDisplay.set(display, style);
1990
+ }
1991
+ return style;
1992
+ };
624
1993
  }
625
1994
  function isBlockLevel(box) {
626
1995
  switch (box.kind) {
@@ -634,6 +2003,15 @@ function isBlockLevel(box) {
634
2003
  // An `inline-block` or `inline-flex` is a block *container* with an
635
2004
  // inline-level outer role, so it belongs to the inline run around it.
636
2005
  return !isInlineLevelDisplay(box.style.display);
2006
+ case 'replaced':
2007
+ // an image is inline unless it is told otherwise, and then it is a
2008
+ // block: `img { display: block }`, which mail writes to lose the gap
2009
+ // under its images, stacks them. A table's part is no display it
2010
+ // can take, and one told to be a cell is inline (CSS Display 3,
2011
+ // 2.4), which is what a table wraps a cell around.
2012
+ return (box.style.display !== 'inline' &&
2013
+ !isInlineLevelDisplay(box.style.display) &&
2014
+ !box.style.display.startsWith('table-'));
637
2015
  default:
638
2016
  return false;
639
2017
  }
@@ -644,7 +2022,7 @@ function isInlineLevelDisplay(display) {
644
2022
  display === 'inline-table');
645
2023
  }
646
2024
  /** Wrap children that are not of `expect` in an anonymous box that is. */
647
- function wrapOrphans(box, kind, accept) {
2025
+ function wrapOrphans(box, kind, accept, anonymous) {
648
2026
  let needed = false;
649
2027
  for (const child of box.children) {
650
2028
  if (!accept(child.kind) && !isDroppableWhitespace(child)) {
@@ -654,27 +2032,50 @@ function wrapOrphans(box, kind, accept) {
654
2032
  }
655
2033
  if (!needed)
656
2034
  return;
2035
+ // White space goes only between two of the parts, or beside one at an
2036
+ // end (CSS 2.1 17.2.1, rule 1): beside a child the fix-up wraps, it is
2037
+ // that child's run's, so `<span>a</span> <span>b</span>` in a row is one
2038
+ // anonymous cell of `a b`, where it was `ab`
657
2039
  const next = [];
658
2040
  let run = null;
2041
+ let space = [];
659
2042
  for (const child of box.children) {
660
2043
  if (accept(child.kind)) {
661
2044
  if (run) {
662
- next.push(anonymousOf(box, kind, run));
2045
+ run.push(...space);
2046
+ next.push(anonymousOf(box, kind, run, anonymous));
663
2047
  run = null;
664
2048
  }
2049
+ space = [];
665
2050
  next.push(child);
666
2051
  continue;
667
2052
  }
668
- if (isDroppableWhitespace(child))
2053
+ if (isDroppableWhitespace(child)) {
2054
+ space.push(child);
669
2055
  continue;
670
- (run ??= []).push(child);
2056
+ }
2057
+ (run ??= []).push(...space, child);
2058
+ space = [];
2059
+ }
2060
+ if (run) {
2061
+ run.push(...space);
2062
+ next.push(anonymousOf(box, kind, run, anonymous));
671
2063
  }
672
- if (run)
673
- next.push(anonymousOf(box, kind, run));
674
2064
  box.children = next;
2065
+ // A cell made here is a block container the fix-up has not been to: its
2066
+ // children had theirs, as the table's, before it held them. Text beside a
2067
+ // block in it goes in an anonymous block as in any other (CSS 2.1
2068
+ // 9.2.1.1) — `<span style="display: inline-table">bcd<div>x</div>` laid
2069
+ // `bcd` out as nothing, and drew the table as wide as the `x`.
2070
+ if (kind === 'table-cell') {
2071
+ for (const child of next) {
2072
+ if (child.kind === 'table-cell' && !child.el)
2073
+ fixUpOwn(child, anonymous);
2074
+ }
2075
+ }
675
2076
  }
676
- function anonymousOf(parent, kind, run) {
677
- const box = new Box(kind, null, parent.style);
2077
+ function anonymousOf(parent, kind, run, anonymous, display = kind) {
2078
+ const box = new Box(kind, null, anonymous(parent, display));
678
2079
  box.parent = parent;
679
2080
  for (const child of run) {
680
2081
  child.parent = box;
@@ -682,17 +2083,140 @@ function anonymousOf(parent, kind, run) {
682
2083
  }
683
2084
  return box;
684
2085
  }
2086
+ /** White space alone, which the table fix-up drops between the parts of a
2087
+ * table whether it is kept or not (CSS 2.1 17.2.1, rule 1). Kept, under
2088
+ * `white-space: pre`, it made a cell of the line break before every row. */
685
2089
  function isDroppableWhitespace(box) {
686
- return box.kind === 'text' && !box.text.trim();
2090
+ return box.kind === 'text' && isBlank(box.text);
2091
+ }
2092
+ /**
2093
+ * Whether a text is white space alone: CSS's white space, which is the
2094
+ * space, the tab and the three line breaks and nothing else (CSS Text 3,
2095
+ * 4.1). JavaScript's `\s` and `trim` take in the no-break space and the
2096
+ * other Unicode spaces too, so a `<p>&nbsp;</p>` spacer or a
2097
+ * `<td>&nbsp;</td>` beside a block was dropped as if it held nothing. Tested
2098
+ * rather than trimmed, which copies a paragraph's text to find out.
2099
+ */
2100
+ export function isBlank(text) {
2101
+ return BLANK.test(text);
2102
+ }
2103
+ const BLANK = /^[ \t\n\r\f]*$/;
2104
+ /** White space that collapsing would change: anything but a lone space.
2105
+ * Most of a document's text has none, and is its own collapsed form. */
2106
+ const COLLAPSIBLE = /[\t\n\r\f]| {2}/;
2107
+ function isRelative(style) {
2108
+ return style.position === 'relative' || style.position === 'sticky';
2109
+ }
2110
+ /** Whether `position: relative` moves a box: an inset is set. An inline box
2111
+ * that is moved has its text laid out apart (`collect`), so one whose
2112
+ * insets resolve to nothing still costs its paragraph the layout of a line
2113
+ * at a time, which a box with every inset `auto` does not. */
2114
+ export function isOffset(style) {
2115
+ if (!isRelative(style))
2116
+ return false;
2117
+ return (style.top !== AUTO ||
2118
+ style.right !== AUTO ||
2119
+ style.bottom !== AUTO ||
2120
+ style.left !== AUTO);
2121
+ }
2122
+ /** Whether a style may put its box under its stacking context's flow: a
2123
+ * negative `z-index` on a positioned box, or on a flex item, which that
2124
+ * makes a stacking context as it is (`stacksLayers`). */
2125
+ function isNegative(style) {
2126
+ return typeof style.zIndex === 'number' && style.zIndex < 0;
2127
+ }
2128
+ /** A box that belongs inside a table: a row group, a row, a cell, a
2129
+ * caption or a column. Asked of every child of every box in a build, so
2130
+ * a text or an inline box is answered from its kind, without its style. */
2131
+ function isTablePart(box) {
2132
+ switch (box.kind) {
2133
+ case 'table-row-group':
2134
+ case 'table-row':
2135
+ case 'table-cell':
2136
+ case 'table-caption':
2137
+ return !box.outOfFlow && !box.isFloat;
2138
+ case 'block': {
2139
+ if (box.outOfFlow || box.isFloat)
2140
+ return false;
2141
+ const display = box.style.display;
2142
+ return display === 'table-column' || display === 'table-column-group';
2143
+ }
2144
+ default:
2145
+ return false;
2146
+ }
687
2147
  }
688
- function fixUpTable(table) {
2148
+ /**
2149
+ * Table parts outside a table get one around them (CSS 2.1 17.2.1, rule 3):
2150
+ * each run of them — the white space between them no part of it — is
2151
+ * wrapped in an anonymous table, an inline one where the parent is inline,
2152
+ * and the table is then completed as any other. Laid out on their own, two
2153
+ * cells side by side in a line were two inline-blocks with a space between
2154
+ * them, and in a block two blocks, one above the other.
2155
+ */
2156
+ function wrapTableParts(box, anonymous) {
2157
+ if (!box.children.some(isTablePart))
2158
+ return;
2159
+ const display = box.kind === 'inline' ? 'inline-table' : 'table';
2160
+ const next = [];
2161
+ let run = null;
2162
+ let space = [];
2163
+ const flush = () => {
2164
+ if (!run)
2165
+ return;
2166
+ const table = anonymousOf(box, 'table', run, anonymous, display);
2167
+ fixUpTable(table, anonymous);
2168
+ next.push(table);
2169
+ run = null;
2170
+ };
2171
+ for (const child of box.children) {
2172
+ if (isTablePart(child)) {
2173
+ run ??= [];
2174
+ run.push(child);
2175
+ space = [];
2176
+ continue;
2177
+ }
2178
+ if (run && isDroppableWhitespace(child)) {
2179
+ // held back: between two parts it goes, after the last it stays
2180
+ space.push(child);
2181
+ continue;
2182
+ }
2183
+ flush();
2184
+ next.push(...space, child);
2185
+ space = [];
2186
+ }
2187
+ flush();
2188
+ next.push(...space);
2189
+ box.children = next;
2190
+ for (const child of next)
2191
+ child.parent = box;
2192
+ }
2193
+ function fixUpTable(table, anonymous) {
689
2194
  const groups = [];
690
2195
  const captions = [];
2196
+ const columns = [];
691
2197
  let looseRows = null;
2198
+ let looseCells = null;
2199
+ const flushCells = () => {
2200
+ if (!looseCells)
2201
+ return;
2202
+ (looseRows ??= []).push(anonymousOf(table, 'table-row', looseCells, anonymous));
2203
+ looseCells = null;
2204
+ };
692
2205
  for (const child of table.children) {
693
- if (child.kind === 'table-row-group') {
2206
+ const display = child.style.display;
2207
+ // an image told to be a column is an inline image, as one told to be a
2208
+ // cell is (`flowOf`): taken for a column, it was never drawn
2209
+ if ((display === 'table-column' || display === 'table-column-group') &&
2210
+ child.kind !== 'replaced') {
2211
+ // A column is the table's, beside its rows: it lays out nothing and
2212
+ // paints nothing (17.2.1). Taken for a stray child, it was wrapped in
2213
+ // a row of its own and drawn as a cell.
2214
+ columns.push(child);
2215
+ }
2216
+ else if (child.kind === 'table-row-group') {
2217
+ flushCells();
694
2218
  if (looseRows) {
695
- groups.push(anonymousOf(table, 'table-row-group', looseRows));
2219
+ groups.push(anonymousOf(table, 'table-row-group', looseRows, anonymous));
696
2220
  looseRows = null;
697
2221
  }
698
2222
  groups.push(child);
@@ -701,27 +2225,35 @@ function fixUpTable(table) {
701
2225
  captions.push(child);
702
2226
  }
703
2227
  else if (isDroppableWhitespace(child)) {
2228
+ // after a loose child it goes with the loose children, and the cells
2229
+ // they are wrapped in say whether it stays (`wrapOrphans`)
2230
+ if (looseCells)
2231
+ looseCells.push(child);
704
2232
  continue;
705
2233
  }
2234
+ else if (child.kind === 'table-row') {
2235
+ flushCells();
2236
+ (looseRows ??= []).push(child);
2237
+ }
706
2238
  else {
707
- // A `<tr>`, or anything else that ended up here: rows go into an
708
- // anonymous group, and anything that is not a row becomes a cell in
709
- // one, which is how a browser rescues `<table>text</table>`.
710
- const row = child.kind === 'table-row'
711
- ? child
712
- : anonymousOf(table, 'table-row', [child]);
713
- (looseRows ??= []).push(row);
2239
+ // Anything else that ended up here is a cell, or goes in one, and a
2240
+ // run of them shares an anonymous row — which is how a browser
2241
+ // rescues `<table>text</table>`, and how three cells in a table
2242
+ // with no row are one row of three rather than three rows of one.
2243
+ (looseCells ??= []).push(child);
714
2244
  }
715
2245
  }
716
- if (looseRows)
717
- groups.push(anonymousOf(table, 'table-row-group', looseRows));
2246
+ flushCells();
2247
+ if (looseRows) {
2248
+ groups.push(anonymousOf(table, 'table-row-group', looseRows, anonymous));
2249
+ }
718
2250
  for (const group of groups) {
719
- wrapOrphans(group, 'table-row', (k) => k === 'table-row');
2251
+ wrapOrphans(group, 'table-row', (k) => k === 'table-row', anonymous);
720
2252
  for (const row of group.children) {
721
- wrapOrphans(row, 'table-cell', (k) => k === 'table-cell');
2253
+ wrapOrphans(row, 'table-cell', (k) => k === 'table-cell', anonymous);
722
2254
  }
723
2255
  }
724
- table.children = [...captions, ...groups];
2256
+ table.children = [...captions, ...columns, ...groups];
725
2257
  for (const child of table.children)
726
2258
  child.parent = table;
727
2259
  }