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