pagyra-js 0.0.20 → 0.0.21

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 (286) hide show
  1. package/README.md +55 -0
  2. package/dist/assets/fonts/licenses/selawik/SIL Open Font License.txt +43 -0
  3. package/dist/assets/fonts/ttf/arimo/Arimo-Bold.ttf +0 -0
  4. package/dist/assets/fonts/ttf/arimo/Arimo-BoldItalic.ttf +0 -0
  5. package/dist/assets/fonts/ttf/arimo/Arimo-Italic.ttf +0 -0
  6. package/dist/assets/fonts/ttf/arimo/Arimo-Regular.ttf +0 -0
  7. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Black.ttf +0 -0
  8. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Bold.ttf +0 -0
  9. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Regular.ttf +0 -0
  10. package/dist/assets/fonts/ttf/dejavu/DejaVuSans.ttf +0 -0
  11. package/dist/assets/fonts/ttf/firecode/FiraCode-Bold.ttf +0 -0
  12. package/dist/assets/fonts/ttf/firecode/FiraCode-Light.ttf +0 -0
  13. package/dist/assets/fonts/ttf/firecode/FiraCode-Medium.ttf +0 -0
  14. package/dist/assets/fonts/ttf/firecode/FiraCode-Regular.ttf +0 -0
  15. package/dist/assets/fonts/ttf/firecode/FiraCode-SemiBold.ttf +0 -0
  16. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Bold.ttf +0 -0
  17. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Light.ttf +0 -0
  18. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Medium.ttf +0 -0
  19. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Regular.ttf +0 -0
  20. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-SemiBold.ttf +0 -0
  21. package/dist/assets/fonts/ttf/notosans/NotoSans-Regular.ttf +0 -0
  22. package/dist/assets/fonts/ttf/roboto/Roboto-Bold.ttf +0 -0
  23. package/dist/assets/fonts/ttf/roboto/Roboto-BoldItalic.ttf +0 -0
  24. package/dist/assets/fonts/ttf/roboto/Roboto-Italic.ttf +0 -0
  25. package/dist/assets/fonts/ttf/roboto/Roboto-Regular.ttf +0 -0
  26. package/dist/assets/fonts/ttf/selawik/selawk.ttf +0 -0
  27. package/dist/assets/fonts/ttf/selawik/selawkb.ttf +0 -0
  28. package/dist/assets/fonts/ttf/selawik/selawkl.ttf +0 -0
  29. package/dist/assets/fonts/ttf/selawik/selawksb.ttf +0 -0
  30. package/dist/assets/fonts/ttf/selawik/selawksl.ttf +0 -0
  31. package/dist/assets/fonts/ttf/stixtwomath/STIXTwoMath-Regular.ttf +0 -0
  32. package/dist/assets/fonts/ttf/tinos/Tinos-Bold.ttf +0 -0
  33. package/dist/assets/fonts/ttf/tinos/Tinos-BoldItalic.ttf +0 -0
  34. package/dist/assets/fonts/ttf/tinos/Tinos-Italic.ttf +0 -0
  35. package/dist/assets/fonts/ttf/tinos/Tinos-Regular.ttf +0 -0
  36. package/dist/assets/fonts/woff/lato/lato-latin-400-italic.woff +0 -0
  37. package/dist/assets/fonts/woff/lato/lato-latin-400-normal.woff +0 -0
  38. package/dist/assets/fonts/woff/lato/lato-latin-700-italic.woff +0 -0
  39. package/dist/assets/fonts/woff/lato/lato-latin-700-normal.woff +0 -0
  40. package/dist/assets/fonts/woff2/caveat/Caveat-Bold.woff2 +0 -0
  41. package/dist/assets/fonts/woff2/caveat/Caveat-Regular.woff2 +0 -0
  42. package/dist/assets/fonts/woff2/lato/lato-latin-400-italic.woff2 +0 -0
  43. package/dist/assets/fonts/woff2/lato/lato-latin-400-normal.woff2 +0 -0
  44. package/dist/assets/fonts/woff2/lato/lato-latin-700-italic.woff2 +0 -0
  45. package/dist/assets/fonts/woff2/lato/lato-latin-700-normal.woff2 +0 -0
  46. package/dist/browser/pagyra.min.js +34 -34
  47. package/dist/browser/pagyra.min.js.map +4 -4
  48. package/dist/playground/server.js +2 -0
  49. package/dist/src/css/compute-style/base-options.d.ts +7 -0
  50. package/dist/src/css/compute-style/base-options.js +24 -0
  51. package/dist/src/css/compute-style/declarations.d.ts +10 -0
  52. package/dist/src/css/compute-style/declarations.js +77 -0
  53. package/dist/src/css/compute-style/decoration.d.ts +8 -0
  54. package/dist/src/css/compute-style/decoration.js +55 -0
  55. package/dist/src/css/compute-style/defaults.d.ts +3 -0
  56. package/dist/src/css/compute-style/defaults.js +34 -0
  57. package/dist/src/css/compute-style/display.d.ts +3 -0
  58. package/dist/src/css/compute-style/display.js +85 -0
  59. package/dist/src/css/compute-style/float.d.ts +2 -0
  60. package/dist/src/css/compute-style/float.js +13 -0
  61. package/dist/src/css/compute-style/font.d.ts +12 -0
  62. package/dist/src/css/compute-style/font.js +57 -0
  63. package/dist/src/css/compute-style/overrides.d.ts +3 -0
  64. package/dist/src/css/compute-style/overrides.js +241 -0
  65. package/dist/src/css/compute-style.d.ts +2 -0
  66. package/dist/src/css/compute-style.js +34 -487
  67. package/dist/src/css/enums.d.ts +4 -0
  68. package/dist/src/css/enums.js +5 -0
  69. package/dist/src/css/layout-property-resolver.js +30 -18
  70. package/dist/src/css/length.d.ts +26 -2
  71. package/dist/src/css/length.js +48 -0
  72. package/dist/src/css/parsers/background-parser.js +1 -1
  73. package/dist/src/css/parsers/calc-parser.d.ts +2 -0
  74. package/dist/src/css/parsers/calc-parser.js +310 -0
  75. package/dist/src/css/parsers/content-parser.d.ts +2 -1
  76. package/dist/src/css/parsers/content-parser.js +7 -2
  77. package/dist/src/css/parsers/dimension-parser.js +37 -18
  78. package/dist/src/css/parsers/display-flex-parser.d.ts +4 -0
  79. package/dist/src/css/parsers/display-flex-parser.js +97 -0
  80. package/dist/src/css/parsers/filter-parser.d.ts +14 -0
  81. package/dist/src/css/parsers/filter-parser.js +255 -0
  82. package/dist/src/css/parsers/grid-parser-extended.d.ts +1 -0
  83. package/dist/src/css/parsers/grid-parser-extended.js +40 -1
  84. package/dist/src/css/parsers/grid-parser.d.ts +5 -2
  85. package/dist/src/css/parsers/grid-parser.js +71 -7
  86. package/dist/src/css/parsers/length-parser.d.ts +8 -3
  87. package/dist/src/css/parsers/length-parser.js +45 -2
  88. package/dist/src/css/parsers/margin-block-parser.js +3 -3
  89. package/dist/src/css/parsers/margin-parser.js +3 -3
  90. package/dist/src/css/parsers/padding-block-parser.js +3 -3
  91. package/dist/src/css/parsers/padding-inline-parser.js +3 -3
  92. package/dist/src/css/parsers/padding-parser.js +6 -6
  93. package/dist/src/css/parsers/position-parser.js +2 -22
  94. package/dist/src/css/parsers/register-parsers.js +29 -2
  95. package/dist/src/css/parsers/word-break-parser.d.ts +2 -0
  96. package/dist/src/css/parsers/word-break-parser.js +23 -0
  97. package/dist/src/css/properties/grid.d.ts +16 -2
  98. package/dist/src/css/properties/layout.d.ts +3 -1
  99. package/dist/src/css/properties/layout.js +1 -1
  100. package/dist/src/css/properties/misc.d.ts +5 -0
  101. package/dist/src/css/properties/typography.d.ts +3 -0
  102. package/dist/src/css/properties/visual.d.ts +36 -0
  103. package/dist/src/css/shorthands/box-shorthand.d.ts +2 -2
  104. package/dist/src/css/style-inheritance.d.ts +2 -1
  105. package/dist/src/css/style-inheritance.js +1 -0
  106. package/dist/src/css/style.d.ts +30 -10
  107. package/dist/src/css/style.js +8 -1
  108. package/dist/src/css/ua-defaults/base-defaults.d.ts +1 -0
  109. package/dist/src/css/ua-defaults/base-defaults.js +10 -1
  110. package/dist/src/css/ua-defaults/element-defaults.js +0 -2
  111. package/dist/src/html/css/parse-css.d.ts +2 -0
  112. package/dist/src/html/css/parse-css.js +32 -3
  113. package/dist/src/html/dom-converter/background-images.d.ts +3 -0
  114. package/dist/src/html/dom-converter/background-images.js +88 -0
  115. package/dist/src/html/dom-converter/convert-dom-node.d.ts +5 -0
  116. package/dist/src/html/dom-converter/convert-dom-node.js +81 -0
  117. package/dist/src/html/dom-converter/handlers/br-handler.d.ts +2 -0
  118. package/dist/src/html/dom-converter/handlers/br-handler.js +20 -0
  119. package/dist/src/html/dom-converter/handlers/form-control-handler.d.ts +2 -0
  120. package/dist/src/html/dom-converter/handlers/form-control-handler.js +28 -0
  121. package/dist/src/html/dom-converter/handlers/img-handler.d.ts +2 -0
  122. package/dist/src/html/dom-converter/handlers/img-handler.js +4 -0
  123. package/dist/src/html/dom-converter/handlers/index.d.ts +4 -0
  124. package/dist/src/html/dom-converter/handlers/index.js +19 -0
  125. package/dist/src/html/dom-converter/handlers/svg-handler.d.ts +2 -0
  126. package/dist/src/html/dom-converter/handlers/svg-handler.js +32 -0
  127. package/dist/src/html/dom-converter/handlers/types.d.ts +12 -0
  128. package/dist/src/html/dom-converter/handlers/types.js +2 -0
  129. package/dist/src/html/dom-converter/helpers.d.ts +7 -0
  130. package/dist/src/html/dom-converter/helpers.js +35 -0
  131. package/dist/src/html/dom-converter/index.d.ts +1 -0
  132. package/dist/src/html/dom-converter/index.js +1 -0
  133. package/dist/src/html/dom-converter/pseudo-elements.d.ts +6 -0
  134. package/dist/src/html/dom-converter/pseudo-elements.js +48 -0
  135. package/dist/src/html/dom-converter/text.d.ts +15 -0
  136. package/dist/src/html/dom-converter/text.js +170 -0
  137. package/dist/src/html/dom-converter.d.ts +1 -5
  138. package/dist/src/html/dom-converter.js +1 -417
  139. package/dist/src/html/image-converter.d.ts +5 -0
  140. package/dist/src/html-to-pdf/document-css.d.ts +14 -0
  141. package/dist/src/html-to-pdf/document-css.js +45 -0
  142. package/dist/src/html-to-pdf/fonts.d.ts +16 -0
  143. package/dist/src/html-to-pdf/fonts.js +74 -0
  144. package/dist/src/html-to-pdf/header-footer.d.ts +14 -0
  145. package/dist/src/html-to-pdf/header-footer.js +101 -0
  146. package/dist/src/html-to-pdf/html-parser.d.ts +6 -0
  147. package/dist/src/html-to-pdf/html-parser.js +81 -0
  148. package/dist/src/html-to-pdf/index.d.ts +3 -0
  149. package/dist/src/html-to-pdf/index.js +2 -0
  150. package/dist/src/html-to-pdf/layout-build.d.ts +37 -0
  151. package/dist/src/html-to-pdf/layout-build.js +73 -0
  152. package/dist/src/html-to-pdf/prepare-html-render.d.ts +2 -0
  153. package/dist/src/html-to-pdf/prepare-html-render.js +121 -0
  154. package/dist/src/html-to-pdf/render-finalize.d.ts +15 -0
  155. package/dist/src/html-to-pdf/render-finalize.js +27 -0
  156. package/dist/src/html-to-pdf/render-html-to-pdf.d.ts +3 -0
  157. package/dist/src/html-to-pdf/render-html-to-pdf.js +25 -0
  158. package/dist/src/html-to-pdf/resource-loader.d.ts +6 -0
  159. package/dist/src/html-to-pdf/resource-loader.js +120 -0
  160. package/dist/src/html-to-pdf/types.d.ts +38 -0
  161. package/dist/src/html-to-pdf/types.js +2 -0
  162. package/dist/src/html-to-pdf.d.ts +1 -37
  163. package/dist/src/html-to-pdf.js +1 -537
  164. package/dist/src/image/js-png-backend.d.ts +7 -0
  165. package/dist/src/image/js-png-backend.js +9 -0
  166. package/dist/src/image/png-backend.d.ts +5 -0
  167. package/dist/src/image/png-backend.js +1 -0
  168. package/dist/src/image/png-wasm-loader.d.ts +5 -0
  169. package/dist/src/image/png-wasm-loader.js +59 -0
  170. package/dist/src/image/wasm/png_decoder_wasm.d.ts +8 -0
  171. package/dist/src/image/wasm/png_decoder_wasm.js +24 -0
  172. package/dist/src/image/wasm/png_decoder_wasm_bg.js +16 -0
  173. package/dist/src/image/wasm-png-backend.d.ts +6 -0
  174. package/dist/src/image/wasm-png-backend.js +17 -0
  175. package/dist/src/layout/counter.d.ts +1 -2
  176. package/dist/src/layout/counter.js +18 -18
  177. package/dist/src/layout/inline/inline-utils.d.ts +1 -1
  178. package/dist/src/layout/inline/inline-utils.js +8 -7
  179. package/dist/src/layout/inline/layout.js +16 -3
  180. package/dist/src/layout/inline/run-placer.d.ts +1 -0
  181. package/dist/src/layout/inline/run-placer.js +2 -10
  182. package/dist/src/layout/pipeline/out-of-flow-manager.js +25 -1
  183. package/dist/src/layout/strategies/block.js +35 -24
  184. package/dist/src/layout/strategies/flex.js +305 -61
  185. package/dist/src/layout/strategies/form.d.ts +2 -0
  186. package/dist/src/layout/strategies/form.js +38 -13
  187. package/dist/src/layout/strategies/grid.js +166 -29
  188. package/dist/src/layout/strategies/image.js +53 -27
  189. package/dist/src/layout/strategies/inline.js +26 -21
  190. package/dist/src/layout/strategies/table.js +26 -18
  191. package/dist/src/layout/utils/content-measurer.d.ts +1 -1
  192. package/dist/src/layout/utils/content-measurer.js +8 -7
  193. package/dist/src/layout/utils/floats.d.ts +1 -0
  194. package/dist/src/layout/utils/floats.js +14 -12
  195. package/dist/src/layout/utils/margin.d.ts +4 -4
  196. package/dist/src/layout/utils/margin.js +20 -16
  197. package/dist/src/layout/utils/node-math.d.ts +12 -6
  198. package/dist/src/layout/utils/node-math.js +71 -41
  199. package/dist/src/layout/utils/sizing.js +2 -1
  200. package/dist/src/pdf/font-subset/font-registry.d.ts +6 -0
  201. package/dist/src/pdf/font-subset/font-registry.js +30 -2
  202. package/dist/src/pdf/header-footer-renderer.js +12 -1
  203. package/dist/src/pdf/layout-tree-builder.js +5 -1
  204. package/dist/src/pdf/page-painter.js +13 -0
  205. package/dist/src/pdf/pagination.js +2 -2
  206. package/dist/src/pdf/renderer/box-painter.js +28 -3
  207. package/dist/src/pdf/renderer/page-paint.js +11 -3
  208. package/dist/src/pdf/renderers/radius-utils.js +31 -38
  209. package/dist/src/pdf/renderers/shape-renderer.js +1 -1
  210. package/dist/src/pdf/renderers/shape-utils.js +1 -1
  211. package/dist/src/pdf/renderers/text-renderer.d.ts +9 -1
  212. package/dist/src/pdf/renderers/text-renderer.js +36 -2
  213. package/dist/src/pdf/stacking/build-stacking-contexts.js +1 -2
  214. package/dist/src/pdf/stacking/resolve-paint-order.d.ts +5 -6
  215. package/dist/src/pdf/stacking/resolve-paint-order.js +29 -9
  216. package/dist/src/pdf/stacking/types.d.ts +14 -0
  217. package/dist/src/pdf/svg/shape-renderer.js +47 -20
  218. package/dist/src/pdf/types.d.ts +7 -1
  219. package/dist/src/pdf/utils/border-radius-utils.js +31 -38
  220. package/dist/src/pdf/utils/color-utils.js +17 -2
  221. package/dist/src/pdf/utils/filter-utils.d.ts +29 -0
  222. package/dist/src/pdf/utils/filter-utils.js +85 -0
  223. package/dist/src/pdf/utils/node-text-run-factory.js +1 -1
  224. package/dist/src/pdf/utils/text-layout-adjuster.d.ts +0 -8
  225. package/dist/src/pdf/utils/text-layout-adjuster.js +12 -9
  226. package/dist/src/units/units.d.ts +1 -1
  227. package/dist/tests/css/box-sizing.spec.d.ts +1 -0
  228. package/dist/tests/css/box-sizing.spec.js +46 -0
  229. package/dist/tests/css/calc-parser.spec.d.ts +1 -0
  230. package/dist/tests/css/calc-parser.spec.js +68 -0
  231. package/dist/tests/css/container-query-units.spec.d.ts +1 -0
  232. package/dist/tests/css/container-query-units.spec.js +64 -0
  233. package/dist/tests/css/content-parser.spec.js +13 -0
  234. package/dist/tests/css/filter-parser.spec.d.ts +1 -0
  235. package/dist/tests/css/filter-parser.spec.js +116 -0
  236. package/dist/tests/css/flex-shorthand.spec.d.ts +1 -0
  237. package/dist/tests/css/flex-shorthand.spec.js +45 -0
  238. package/dist/tests/css/grid-clamp.spec.d.ts +1 -0
  239. package/dist/tests/css/grid-clamp.spec.js +82 -0
  240. package/dist/tests/css/parse-css-pseudo.spec.d.ts +1 -0
  241. package/dist/tests/css/parse-css-pseudo.spec.js +26 -0
  242. package/dist/tests/helpers/render-utils.d.ts +18 -2
  243. package/dist/tests/helpers/render-utils.js +25 -12
  244. package/dist/tests/html/dom-converter-pseudo-elements.spec.d.ts +1 -0
  245. package/dist/tests/html/dom-converter-pseudo-elements.spec.js +33 -0
  246. package/dist/tests/html/dom-converter-text.spec.d.ts +1 -0
  247. package/dist/tests/html/dom-converter-text.spec.js +67 -0
  248. package/dist/tests/image/png-backend.spec.d.ts +1 -0
  249. package/dist/tests/image/png-backend.spec.js +34 -0
  250. package/dist/tests/layout/box-sizing.spec.d.ts +1 -0
  251. package/dist/tests/layout/box-sizing.spec.js +75 -0
  252. package/dist/tests/layout/calc-padding.spec.d.ts +1 -0
  253. package/dist/tests/layout/calc-padding.spec.js +19 -0
  254. package/dist/tests/layout/container-query-units.spec.d.ts +1 -0
  255. package/dist/tests/layout/container-query-units.spec.js +24 -0
  256. package/dist/tests/layout/flex-auto-height.spec.d.ts +1 -0
  257. package/dist/tests/layout/flex-auto-height.spec.js +35 -0
  258. package/dist/tests/layout/flex-wrap-cards.spec.d.ts +1 -0
  259. package/dist/tests/layout/flex-wrap-cards.spec.js +16 -0
  260. package/dist/tests/layout/flex-wrap-grow-align-content.spec.d.ts +1 -0
  261. package/dist/tests/layout/flex-wrap-grow-align-content.spec.js +20 -0
  262. package/dist/tests/layout/grid-clamp-gap.spec.d.ts +1 -0
  263. package/dist/tests/layout/grid-clamp-gap.spec.js +22 -0
  264. package/dist/tests/layout/inline-fragments.spec.js +38 -0
  265. package/dist/tests/layout/paged-body-margin.spec.d.ts +1 -0
  266. package/dist/tests/layout/paged-body-margin.spec.js +92 -0
  267. package/dist/tests/layout/pseudo-counters-generated-content.spec.d.ts +1 -0
  268. package/dist/tests/layout/pseudo-counters-generated-content.spec.js +51 -0
  269. package/dist/tests/layout/responsive-clamp-grid-parity.spec.d.ts +1 -0
  270. package/dist/tests/layout/responsive-clamp-grid-parity.spec.js +75 -0
  271. package/dist/tests/layout/run-placer-baseline.spec.js +13 -11
  272. package/dist/tests/pdf/backdrop-filter-noop.spec.d.ts +1 -0
  273. package/dist/tests/pdf/backdrop-filter-noop.spec.js +140 -0
  274. package/dist/tests/pdf/filter-drop-shadow.spec.d.ts +1 -0
  275. package/dist/tests/pdf/filter-drop-shadow.spec.js +74 -0
  276. package/dist/tests/pdf/filter-opacity.spec.d.ts +1 -0
  277. package/dist/tests/pdf/filter-opacity.spec.js +30 -0
  278. package/dist/tests/pdf/font-subset-registry-key.spec.d.ts +1 -0
  279. package/dist/tests/pdf/font-subset-registry-key.spec.js +66 -0
  280. package/dist/tests/pdf/selawik-opt-in.spec.d.ts +1 -0
  281. package/dist/tests/pdf/selawik-opt-in.spec.js +106 -0
  282. package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.d.ts +1 -0
  283. package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.js +39 -0
  284. package/dist/tests/pdf/text-renderer-fallback.spec.js +55 -0
  285. package/dist/tests/pdf/text-transform-matrix.spec.js +8 -7
  286. package/package.json +2 -2
@@ -25,9 +25,17 @@ export async function paintLayoutPage({ pageTree, pageNumber, totalPages, pageSi
25
25
  if (headerFooterLayout.layerMode === LayerMode.Under) {
26
26
  await paintHeaderFooter(painter, headerVariant, footerVariant, tokens, pageNumber, totalPages, headerFooterTextOptions, true, hfContext);
27
27
  }
28
- // Paint each box as an atomic unit in the resolved paint order.
29
- for (const box of pageTree.paintOrder) {
30
- await paintBoxAtomic(painter, box);
28
+ // Paint each instruction in the resolved paint order.
29
+ for (const instruction of pageTree.paintOrder) {
30
+ if (instruction.type === 'beginOpacity') {
31
+ painter.beginOpacityScope(instruction.opacity);
32
+ }
33
+ else if (instruction.type === 'endOpacity') {
34
+ painter.endOpacityScope(0);
35
+ }
36
+ else {
37
+ await paintBoxAtomic(painter, instruction.box);
38
+ }
31
39
  }
32
40
  if (headerFooterLayout.layerMode === LayerMode.Over) {
33
41
  await paintHeaderFooter(painter, headerVariant, footerVariant, tokens, pageNumber, totalPages, headerFooterTextOptions, false, hfContext);
@@ -15,45 +15,38 @@ export function normalizeRadiiForRect(width, height, radii) {
15
15
  };
16
16
  const safeWidth = Math.max(width, 0);
17
17
  const safeHeight = Math.max(height, 0);
18
- if (safeWidth <= 0) {
19
- result.topLeft.x = 0;
20
- result.topRight.x = 0;
21
- result.bottomRight.x = 0;
22
- result.bottomLeft.x = 0;
18
+ if (safeWidth <= 0 || safeHeight <= 0) {
19
+ return {
20
+ topLeft: { x: 0, y: 0 },
21
+ topRight: { x: 0, y: 0 },
22
+ bottomRight: { x: 0, y: 0 },
23
+ bottomLeft: { x: 0, y: 0 },
24
+ };
23
25
  }
24
- else {
25
- const topSum = result.topLeft.x + result.topRight.x;
26
- if (topSum > safeWidth && topSum > 0) {
27
- const scale = safeWidth / topSum;
28
- result.topLeft.x *= scale;
29
- result.topRight.x *= scale;
30
- }
31
- const bottomSum = result.bottomLeft.x + result.bottomRight.x;
32
- if (bottomSum > safeWidth && bottomSum > 0) {
33
- const scale = safeWidth / bottomSum;
34
- result.bottomLeft.x *= scale;
35
- result.bottomRight.x *= scale;
36
- }
37
- }
38
- if (safeHeight <= 0) {
39
- result.topLeft.y = 0;
40
- result.topRight.y = 0;
41
- result.bottomRight.y = 0;
42
- result.bottomLeft.y = 0;
43
- }
44
- else {
45
- const leftSum = result.topLeft.y + result.bottomLeft.y;
46
- if (leftSum > safeHeight && leftSum > 0) {
47
- const scale = safeHeight / leftSum;
48
- result.topLeft.y *= scale;
49
- result.bottomLeft.y *= scale;
50
- }
51
- const rightSum = result.topRight.y + result.bottomRight.y;
52
- if (rightSum > safeHeight && rightSum > 0) {
53
- const scale = safeHeight / rightSum;
54
- result.topRight.y *= scale;
55
- result.bottomRight.y *= scale;
56
- }
26
+ // CSS Backgrounds § 5.5: compute a single scale factor f = min(Li/Si)
27
+ // across all four sides, then multiply ALL radii by f if f < 1.
28
+ let f = 1;
29
+ const topSumX = result.topLeft.x + result.topRight.x;
30
+ if (topSumX > 0)
31
+ f = Math.min(f, safeWidth / topSumX);
32
+ const bottomSumX = result.bottomLeft.x + result.bottomRight.x;
33
+ if (bottomSumX > 0)
34
+ f = Math.min(f, safeWidth / bottomSumX);
35
+ const leftSumY = result.topLeft.y + result.bottomLeft.y;
36
+ if (leftSumY > 0)
37
+ f = Math.min(f, safeHeight / leftSumY);
38
+ const rightSumY = result.topRight.y + result.bottomRight.y;
39
+ if (rightSumY > 0)
40
+ f = Math.min(f, safeHeight / rightSumY);
41
+ if (f < 1) {
42
+ result.topLeft.x *= f;
43
+ result.topLeft.y *= f;
44
+ result.topRight.x *= f;
45
+ result.topRight.y *= f;
46
+ result.bottomRight.x *= f;
47
+ result.bottomRight.y *= f;
48
+ result.bottomLeft.x *= f;
49
+ result.bottomLeft.y *= f;
57
50
  }
58
51
  return result;
59
52
  }
@@ -431,7 +431,7 @@ export class ShapeRenderer {
431
431
  const scaleY = this.coordinateTransformer.convertPxToPt(1);
432
432
  const translateX = this.coordinateTransformer.convertPxToPt(relX);
433
433
  const translateY = this.coordinateTransformer.convertPxToPt(-relY); // Negative because PDF y-axis is flipped
434
- return `${formatNumber(scaleX)} 0 0 ${formatNumber(scaleY)} ${formatNumber(translateX)} ${formatNumber(translateY)} cm`;
434
+ return `${formatNumber(scaleX)} 0 0 ${formatNumber(-scaleY)} ${formatNumber(translateX)} ${formatNumber(translateY)} cm`;
435
435
  }
436
436
  // Normal absolute positioning
437
437
  const scaleX = this.coordinateTransformer.convertPxToPt(1);
@@ -133,7 +133,7 @@ export function transformForRect(rect, coordinateTransformer, transformContext)
133
133
  const scaleY = coordinateTransformer.convertPxToPt(1);
134
134
  const translateX = coordinateTransformer.convertPxToPt(relX);
135
135
  const translateY = coordinateTransformer.convertPxToPt(-relY); // Negative because PDF y-axis is flipped
136
- return `${formatNumber(scaleX)} 0 0 ${formatNumber(scaleY)} ${formatNumber(translateX)} ${formatNumber(translateY)} cm`;
136
+ return `${formatNumber(scaleX)} 0 0 ${formatNumber(-scaleY)} ${formatNumber(translateX)} ${formatNumber(translateY)} cm`;
137
137
  }
138
138
  // Normal absolute positioning
139
139
  const scaleX = coordinateTransformer.convertPxToPt(1);
@@ -1,4 +1,4 @@
1
- import type { Run, TextPaintOptions } from "../types.js";
1
+ import type { Run, TextPaintOptions, Rect } from "../types.js";
2
2
  import type { FontRegistry } from "../font/font-registry.js";
3
3
  import type { PdfObjectRef } from "../primitives/pdf-document.js";
4
4
  import { CoordinateTransformer } from "../utils/coordinate-transformer.js";
@@ -21,6 +21,7 @@ export declare class TextRenderer {
21
21
  private readonly fontResolver;
22
22
  private readonly gradientService;
23
23
  private readonly patterns;
24
+ private transformContext;
24
25
  constructor(coordinateTransformer: CoordinateTransformer, fontRegistry: FontRegistry, imageRenderer?: ImageRenderer, graphicsStateManager?: GraphicsStateManager);
25
26
  drawText(text: string, xPx: number, yPx: number, options?: TextPaintOptions): Promise<void>;
26
27
  drawTextRun(run: Run): Promise<void>;
@@ -29,5 +30,12 @@ export declare class TextRenderer {
29
30
  private registerSubsetFont;
30
31
  private fillGradientRect;
31
32
  private fillPatternRect;
33
+ /**
34
+ * Drain (return and clear) all buffered commands so they can be pushed
35
+ * into the unified shape command stream, preserving paint order.
36
+ */
37
+ flushCommands(): string[];
38
+ setTransformContext(rect: Rect): void;
39
+ clearTransformContext(): void;
32
40
  getResult(): TextRendererResult;
33
41
  }
@@ -19,6 +19,7 @@ export class TextRenderer {
19
19
  this.commands = [];
20
20
  this.fonts = new Map();
21
21
  this.patterns = new Map();
22
+ this.transformContext = null;
22
23
  this.graphicsStateManager = graphicsStateManager;
23
24
  this.fontResolver = new TextFontResolver(fontRegistry);
24
25
  this.shadowRenderer = new TextShadowRenderer(coordinateTransformer, fontRegistry, imageRenderer, graphicsStateManager);
@@ -87,7 +88,7 @@ export class TextRenderer {
87
88
  text: run.text.slice(0, 64),
88
89
  glyphCount: glyphRun.glyphIds.length,
89
90
  });
90
- const textMatrix = buildPdfTextMatrix(run, this.coordinateTransformer);
91
+ const textMatrix = buildPdfTextMatrix(run, this.coordinateTransformer, this.transformContext);
91
92
  log("paint", "debug", "drawing text run with glyphs", {
92
93
  text: run.text.slice(0, 32),
93
94
  glyphIds: glyphRun.glyphIds.slice(0, 10),
@@ -198,6 +199,20 @@ export class TextRenderer {
198
199
  "f",
199
200
  ];
200
201
  }
202
+ /**
203
+ * Drain (return and clear) all buffered commands so they can be pushed
204
+ * into the unified shape command stream, preserving paint order.
205
+ */
206
+ flushCommands() {
207
+ const cmds = this.commands.splice(0, this.commands.length);
208
+ return cmds;
209
+ }
210
+ setTransformContext(rect) {
211
+ this.transformContext = rect;
212
+ }
213
+ clearTransformContext() {
214
+ this.transformContext = null;
215
+ }
201
216
  getResult() {
202
217
  return {
203
218
  commands: [...this.commands],
@@ -207,7 +222,7 @@ export class TextRenderer {
207
222
  };
208
223
  }
209
224
  }
210
- function buildPdfTextMatrix(run, transformer) {
225
+ function buildPdfTextMatrix(run, transformer, transformContext = null) {
211
226
  const base = run.lineMatrix ?? { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
212
227
  if (!run.lineMatrix) {
213
228
  log("paint", "debug", "Run provided without lineMatrix, using identity fallback", {
@@ -215,6 +230,25 @@ function buildPdfTextMatrix(run, transformer) {
215
230
  fontFamily: run.fontFamily,
216
231
  });
217
232
  }
233
+ if (transformContext) {
234
+ const relMatrix = {
235
+ a: base.a,
236
+ b: base.b,
237
+ c: base.c,
238
+ d: base.d,
239
+ e: base.e - transformContext.x,
240
+ f: base.f - transformContext.y,
241
+ };
242
+ const pdfPx = svgMatrixToPdf(relMatrix) ?? { a: 1, b: 0, c: 0, d: 1, e: relMatrix.e, f: relMatrix.f };
243
+ return {
244
+ a: pdfPx.a,
245
+ b: pdfPx.b,
246
+ c: pdfPx.c,
247
+ d: pdfPx.d,
248
+ e: transformer.convertPxToPt(pdfPx.e),
249
+ f: transformer.convertPxToPt(pdfPx.f),
250
+ };
251
+ }
218
252
  const offsetPx = transformer.pageOffsetPx;
219
253
  // Normalize to page origin (top-left), removing any page offset.
220
254
  const local = {
@@ -7,8 +7,7 @@ export function getStackingFlags(box) {
7
7
  const raw = box.zIndexComputed;
8
8
  const hasNumericZ = typeof raw === "number" && Number.isFinite(raw);
9
9
  const zIndex = hasNumericZ ? raw : "auto";
10
- // For now, we only treat positioned+numeric z-index as establishing context.
11
- const establishesContext = isPositioned && hasNumericZ;
10
+ const establishesContext = box.establishesStackingContext || (isPositioned && hasNumericZ);
12
11
  return { isPositioned, zIndex, establishesContext };
13
12
  }
14
13
  /**
@@ -1,11 +1,10 @@
1
1
  import type { RenderBox } from "../types.js";
2
+ import type { PaintInstruction } from "./types.js";
2
3
  /**
3
4
  * Resolve a global paint order from the RenderBox tree using stacking contexts.
4
5
  *
5
- * Scope (step 1):
6
- * - Support numeric z-index on positioned elements (absolute/fixed/etc.) vs auto.
7
- * - Respect DOM order for ties.
8
- * - Treat each box as an atomic paint unit (background+border+content together).
9
- * - Keep API simple: return ordered RenderBox list.
6
+ * Returns an ordered list of PaintInstructions that includes opacity scope
7
+ * markers (beginOpacity/endOpacity) around stacking contexts whose root box
8
+ * has effective opacity < 1 (from CSS opacity or filter: opacity()).
10
9
  */
11
- export declare function resolvePaintOrder(root: RenderBox): RenderBox[];
10
+ export declare function resolvePaintOrder(root: RenderBox): PaintInstruction[];
@@ -2,20 +2,32 @@ import { buildStackingContexts, getStackingFlags } from "./build-stacking-contex
2
2
  /**
3
3
  * Resolve a global paint order from the RenderBox tree using stacking contexts.
4
4
  *
5
- * Scope (step 1):
6
- * - Support numeric z-index on positioned elements (absolute/fixed/etc.) vs auto.
7
- * - Respect DOM order for ties.
8
- * - Treat each box as an atomic paint unit (background+border+content together).
9
- * - Keep API simple: return ordered RenderBox list.
5
+ * Returns an ordered list of PaintInstructions that includes opacity scope
6
+ * markers (beginOpacity/endOpacity) around stacking contexts whose root box
7
+ * has effective opacity < 1 (from CSS opacity or filter: opacity()).
10
8
  */
11
9
  export function resolvePaintOrder(root) {
12
10
  const { rootContextId, contexts } = buildStackingContexts(root);
13
11
  const steps = [];
14
12
  resolveContextPaintOrder(rootContextId, contexts, steps);
15
- return steps.map((s) => s.box);
13
+ return steps;
16
14
  }
17
15
  /**
18
- * Compute paint order within a stacking context and append PaintSteps.
16
+ * Compute effective opacity for a box, combining CSS opacity and filter opacity().
17
+ */
18
+ function computeEffectiveOpacity(box) {
19
+ let opacity = box.opacity;
20
+ if (box.filter) {
21
+ for (const fn of box.filter) {
22
+ if (fn.kind === 'opacity') {
23
+ opacity *= fn.value;
24
+ }
25
+ }
26
+ }
27
+ return Math.max(0, Math.min(1, opacity));
28
+ }
29
+ /**
30
+ * Compute paint order within a stacking context and append PaintInstructions.
19
31
  *
20
32
  * Simplified rules (sufficient for our current constraints):
21
33
  * For a given context:
@@ -33,7 +45,7 @@ function resolveContextPaintOrder(contextId, contexts, out) {
33
45
  return;
34
46
  const rootBox = context.box;
35
47
  // 1. Paint the context root box as an atomic unit.
36
- out.push({ box: rootBox });
48
+ out.push({ type: 'box', box: rootBox });
37
49
  // Collect direct descendants belonging to this context (non-root boxes).
38
50
  const descendants = [];
39
51
  collectDescendantsInContext(rootBox, contextId, contexts, descendants);
@@ -86,14 +98,22 @@ function resolveContextPaintOrder(contextId, contexts, out) {
86
98
  /**
87
99
  * Append either a nested stacking context or a regular box.
88
100
  * Nested contexts are treated as atomic units: we delegate to resolveContextPaintOrder.
101
+ * When a nested context root has effective opacity < 1, wrap with begin/endOpacity markers.
89
102
  */
90
103
  function appendBoxOrContext(box, parentContextId, contexts, out) {
91
104
  const nested = findContextByBox(box, parentContextId, contexts);
92
105
  if (nested) {
106
+ const effectiveOpacity = computeEffectiveOpacity(nested.box);
107
+ if (effectiveOpacity < 1) {
108
+ out.push({ type: 'beginOpacity', opacity: effectiveOpacity });
109
+ }
93
110
  resolveContextPaintOrder(nested.id, contexts, out);
111
+ if (effectiveOpacity < 1) {
112
+ out.push({ type: 'endOpacity' });
113
+ }
94
114
  }
95
115
  else {
96
- out.push({ box });
116
+ out.push({ type: 'box', box });
97
117
  }
98
118
  }
99
119
  /**
@@ -34,3 +34,17 @@ export interface StackingFlags {
34
34
  export interface PaintStep {
35
35
  box: RenderBox;
36
36
  }
37
+ /**
38
+ * A paint instruction in the resolved paint order.
39
+ * - `box`: paint a single RenderBox atomically.
40
+ * - `beginOpacity` / `endOpacity`: wrap a stacking context group in an opacity scope.
41
+ */
42
+ export type PaintInstruction = {
43
+ type: 'box';
44
+ box: RenderBox;
45
+ } | {
46
+ type: 'beginOpacity';
47
+ opacity: number;
48
+ } | {
49
+ type: 'endOpacity';
50
+ };
@@ -309,10 +309,24 @@ function svgLinearNodeToLinearGradient(node, context) {
309
309
  const stops = (node.stops ?? []).map((s) => ({ color: s.color, position: s.offset }));
310
310
  // Default: objectBoundingBox coordinates (ratio 0..1)
311
311
  const units = node.gradientUnits === "userSpaceOnUse" ? "userSpace" : "ratio";
312
+ const rawTransform = (node.attributes && (node.attributes["gradientTransform"] ?? node.attributes["gradienttransform"]));
313
+ const transform = rawTransform ? parseTransform(rawTransform) || undefined : undefined;
312
314
  // If userSpaceOnUse, map points to page pixels using the SVG render context
313
315
  if (units === "userSpace" && context) {
314
- const p1 = mapSvgPoint(x1, y1, context);
315
- const p2 = mapSvgPoint(x2, y2, context);
316
+ let ux1 = x1;
317
+ let uy1 = y1;
318
+ let ux2 = x2;
319
+ let uy2 = y2;
320
+ if (transform) {
321
+ const tp1 = applyMatrixToPoint(transform, ux1, uy1);
322
+ const tp2 = applyMatrixToPoint(transform, ux2, uy2);
323
+ ux1 = tp1.x;
324
+ uy1 = tp1.y;
325
+ ux2 = tp2.x;
326
+ uy2 = tp2.y;
327
+ }
328
+ const p1 = mapSvgPoint(ux1, uy1, context);
329
+ const p2 = mapSvgPoint(ux2, uy2, context);
316
330
  if (p1 && p2) {
317
331
  // coords in absolute page pixels; gradient-service will convert to rectangle-local points
318
332
  return { type: "linear", direction: "to right", stops, coords: { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, units: "userSpace" } };
@@ -323,13 +337,9 @@ function svgLinearNodeToLinearGradient(node, context) {
323
337
  // Apply gradientTransform if present — treat it as operating in gradient coordinate space
324
338
  let rp1 = { x: x1, y: y1 };
325
339
  let rp2 = { x: x2, y: y2 };
326
- const rawTransform = (node.attributes && (node.attributes["gradientTransform"] ?? node.attributes["gradienttransform"]));
327
- if (rawTransform) {
328
- const t = parseTransform(rawTransform) || undefined;
329
- if (t) {
330
- rp1 = applyMatrixToPoint(t, rp1.x, rp1.y);
331
- rp2 = applyMatrixToPoint(t, rp2.x, rp2.y);
332
- }
340
+ if (transform) {
341
+ rp1 = applyMatrixToPoint(transform, rp1.x, rp1.y);
342
+ rp2 = applyMatrixToPoint(transform, rp2.x, rp2.y);
333
343
  }
334
344
  // compute direction angle from ratio coords (useful when no explicit coords provided)
335
345
  const dx = rp2.x - rp1.x;
@@ -346,12 +356,33 @@ function svgRadialNodeToRadialGradient(node, context) {
346
356
  const fy = node.fy;
347
357
  const stops = (node.stops ?? []).map((s) => ({ color: s.color, position: s.offset }));
348
358
  const units = node.gradientUnits === "userSpaceOnUse" ? "userSpace" : "ratio";
359
+ const rawTransform = (node.attributes && (node.attributes["gradientTransform"] ?? node.attributes["gradienttransform"]));
360
+ const transform = rawTransform ? parseTransform(rawTransform) || undefined : undefined;
349
361
  if (units === "userSpace" && context) {
350
- const center = mapSvgPoint(cx, cy, context);
351
- const focal = fx !== undefined && fy !== undefined ? mapSvgPoint(fx, fy, context) : undefined;
362
+ let ucx = cx;
363
+ let ucy = cy;
364
+ let ufx = fx;
365
+ let ufy = fy;
366
+ let edgeX = cx + r;
367
+ let edgeY = cy;
368
+ if (transform) {
369
+ const tc = applyMatrixToPoint(transform, ucx, ucy);
370
+ ucx = tc.x;
371
+ ucy = tc.y;
372
+ const te = applyMatrixToPoint(transform, edgeX, edgeY);
373
+ edgeX = te.x;
374
+ edgeY = te.y;
375
+ if (ufx !== undefined && ufy !== undefined) {
376
+ const tf = applyMatrixToPoint(transform, ufx, ufy);
377
+ ufx = tf.x;
378
+ ufy = tf.y;
379
+ }
380
+ }
381
+ const center = mapSvgPoint(ucx, ucy, context);
382
+ const focal = ufx !== undefined && ufy !== undefined ? mapSvgPoint(ufx, ufy, context) : undefined;
352
383
  const radiusPt = (() => {
353
384
  // map a point at cx + r, cy to user space and compute distance
354
- const edge = mapSvgPoint(cx + r, cy, context);
385
+ const edge = mapSvgPoint(edgeX, edgeY, context);
355
386
  if (center && edge) {
356
387
  const dx = edge.x - center.x;
357
388
  const dy = edge.y - center.y;
@@ -392,14 +423,10 @@ function svgRadialNodeToRadialGradient(node, context) {
392
423
  radRatio.fx = fx;
393
424
  radRatio.fy = fy;
394
425
  }
395
- const rawTransform = (node.attributes && (node.attributes["gradientTransform"] ?? node.attributes["gradienttransform"]));
396
- if (rawTransform) {
397
- const t = parseTransform(rawTransform) || undefined;
398
- if (t) {
399
- // Preserve the parsed transform matrix on the RadialGradient for use by the
400
- // gradient service when building the PDF shading dictionary.
401
- radRatio.transform = { a: t.a, b: t.b, c: t.c, d: t.d, e: t.e, f: t.f };
402
- }
426
+ if (transform) {
427
+ // Preserve the parsed transform matrix on the RadialGradient for use by the
428
+ // gradient service when building the PDF shading dictionary.
429
+ radRatio.transform = { a: transform.a, b: transform.b, c: transform.c, d: transform.d, e: transform.e, f: transform.f };
403
430
  }
404
431
  return radRatio;
405
432
  }
@@ -1,5 +1,7 @@
1
1
  import type { BackgroundRepeat, Gradient } from "../css/background-types.js";
2
2
  import type { GlyphRun } from "../layout/text-run.js";
3
+ import type { FilterFunction } from "../css/properties/visual.js";
4
+ import type { PaintInstruction } from "./stacking/types.js";
3
5
  export declare enum NodeKind {
4
6
  Container = "container",
5
7
  TextRuns = "text-runs",
@@ -198,7 +200,7 @@ export interface Positioning {
198
200
  type: "normal" | "float" | "absolute" | "fixed" | "sticky";
199
201
  }
200
202
  export interface LayoutPageTree {
201
- paintOrder: RenderBox[];
203
+ paintOrder: PaintInstruction[];
202
204
  floatLayerOrder: RenderBox[];
203
205
  flowContentOrder: RenderBox[];
204
206
  positionedLayersSortedByZ: PositionedLayer[];
@@ -255,6 +257,10 @@ export interface RenderBox {
255
257
  borderStyle?: BorderStyles;
256
258
  color?: RGBA;
257
259
  transform?: TextMatrix;
260
+ /** Parsed CSS filter functions carried from ComputedStyle */
261
+ filter?: FilterFunction[];
262
+ /** Parsed CSS backdrop-filter functions carried from ComputedStyle */
263
+ backdropFilter?: FilterFunction[];
258
264
  /**
259
265
  * Normalized text alignment of this box (from CSS text-align).
260
266
  * Only the visual modes are captured here.
@@ -35,45 +35,38 @@ export function normalizeBorderRadius(input, width, height) {
35
35
  };
36
36
  const safeWidth = Math.max(width, 0);
37
37
  const safeHeight = Math.max(height, 0);
38
- if (safeWidth <= 0) {
39
- result.topLeft.x = 0;
40
- result.topRight.x = 0;
41
- result.bottomRight.x = 0;
42
- result.bottomLeft.x = 0;
38
+ if (safeWidth <= 0 || safeHeight <= 0) {
39
+ return {
40
+ topLeft: { x: 0, y: 0 },
41
+ topRight: { x: 0, y: 0 },
42
+ bottomRight: { x: 0, y: 0 },
43
+ bottomLeft: { x: 0, y: 0 },
44
+ };
43
45
  }
44
- else {
45
- const topSum = result.topLeft.x + result.topRight.x;
46
- if (topSum > safeWidth && topSum > 0) {
47
- const scale = safeWidth / topSum;
48
- result.topLeft.x *= scale;
49
- result.topRight.x *= scale;
50
- }
51
- const bottomSum = result.bottomLeft.x + result.bottomRight.x;
52
- if (bottomSum > safeWidth && bottomSum > 0) {
53
- const scale = safeWidth / bottomSum;
54
- result.bottomLeft.x *= scale;
55
- result.bottomRight.x *= scale;
56
- }
57
- }
58
- if (safeHeight <= 0) {
59
- result.topLeft.y = 0;
60
- result.topRight.y = 0;
61
- result.bottomRight.y = 0;
62
- result.bottomLeft.y = 0;
63
- }
64
- else {
65
- const leftSum = result.topLeft.y + result.bottomLeft.y;
66
- if (leftSum > safeHeight && leftSum > 0) {
67
- const scale = safeHeight / leftSum;
68
- result.topLeft.y *= scale;
69
- result.bottomLeft.y *= scale;
70
- }
71
- const rightSum = result.topRight.y + result.bottomRight.y;
72
- if (rightSum > safeHeight && rightSum > 0) {
73
- const scale = safeHeight / rightSum;
74
- result.topRight.y *= scale;
75
- result.bottomRight.y *= scale;
76
- }
46
+ // CSS Backgrounds § 5.5: compute a single scale factor f = min(Li/Si)
47
+ // across all four sides, then multiply ALL radii by f if f < 1.
48
+ let f = 1;
49
+ const topSumX = result.topLeft.x + result.topRight.x;
50
+ if (topSumX > 0)
51
+ f = Math.min(f, safeWidth / topSumX);
52
+ const bottomSumX = result.bottomLeft.x + result.bottomRight.x;
53
+ if (bottomSumX > 0)
54
+ f = Math.min(f, safeWidth / bottomSumX);
55
+ const leftSumY = result.topLeft.y + result.bottomLeft.y;
56
+ if (leftSumY > 0)
57
+ f = Math.min(f, safeHeight / leftSumY);
58
+ const rightSumY = result.topRight.y + result.bottomRight.y;
59
+ if (rightSumY > 0)
60
+ f = Math.min(f, safeHeight / rightSumY);
61
+ if (f < 1) {
62
+ result.topLeft.x *= f;
63
+ result.topLeft.y *= f;
64
+ result.topRight.x *= f;
65
+ result.topRight.y *= f;
66
+ result.bottomRight.x *= f;
67
+ result.bottomRight.y *= f;
68
+ result.bottomLeft.x *= f;
69
+ result.bottomLeft.y *= f;
77
70
  }
78
71
  return result;
79
72
  }
@@ -10,7 +10,7 @@ export function parseColor(value) {
10
10
  if (normalized === "transparent") {
11
11
  return undefined;
12
12
  }
13
- const hexMatch = normalized.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
13
+ const hexMatch = normalized.match(/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);
14
14
  if (hexMatch) {
15
15
  const digits = hexMatch[1];
16
16
  if (digits.length === 3) {
@@ -19,10 +19,25 @@ export function parseColor(value) {
19
19
  const b = parseHex(digits[2] + digits[2]);
20
20
  return { r, g, b, a: 1 };
21
21
  }
22
+ if (digits.length === 4) {
23
+ const r = parseHex(digits[0] + digits[0]);
24
+ const g = parseHex(digits[1] + digits[1]);
25
+ const b = parseHex(digits[2] + digits[2]);
26
+ const a = parseHex(digits[3] + digits[3]) / 255;
27
+ return { r, g, b, a };
28
+ }
29
+ if (digits.length === 6) {
30
+ const r = parseHex(digits.slice(0, 2));
31
+ const g = parseHex(digits.slice(2, 4));
32
+ const b = parseHex(digits.slice(4, 6));
33
+ return { r, g, b, a: 1 };
34
+ }
35
+ // 8 digits
22
36
  const r = parseHex(digits.slice(0, 2));
23
37
  const g = parseHex(digits.slice(2, 4));
24
38
  const b = parseHex(digits.slice(4, 6));
25
- return { r, g, b, a: 1 };
39
+ const a = parseHex(digits.slice(6, 8)) / 255;
40
+ return { r, g, b, a };
26
41
  }
27
42
  const rgbMatch = normalized.match(/^rgba?\((.+)\)$/);
28
43
  if (rgbMatch) {
@@ -0,0 +1,29 @@
1
+ import type { FilterFunction } from "../../css/properties/visual.js";
2
+ import type { RGBA, ShadowLayer } from "../types.js";
3
+ /**
4
+ * Extrai o multiplicador de opacity de uma lista de filter functions.
5
+ * Múltiplos `opacity()` são multiplicados entre si (composição sequencial).
6
+ * Retorna 1.0 se não houver `opacity()` na lista.
7
+ */
8
+ export declare function extractOpacityMultiplier(filters: FilterFunction[]): number;
9
+ /**
10
+ * Converte `drop-shadow()` do filter em ShadowLayer[] compatível
11
+ * com o pipeline de box-shadow existente.
12
+ *
13
+ * Diferenças em relação a box-shadow real:
14
+ * - Sem `inset` (sempre false).
15
+ * - Sem `spread-radius` (sempre 0).
16
+ * - Na prática do CSS, drop-shadow segue a forma alpha do conteúdo,
17
+ * mas no MVP aproximamos como sombra retangular (box-shadow).
18
+ */
19
+ export declare function extractDropShadowLayers(filters: FilterFunction[], fallbackColor: RGBA): ShadowLayer[];
20
+ /**
21
+ * Lista nomes de funções de filtro que não são renderizáveis no MVP.
22
+ * Usado para emitir warnings durante a fase de pintura.
23
+ */
24
+ export declare function listUnsupportedFilters(filters: FilterFunction[] | undefined): string[];
25
+ /**
26
+ * Emite warnings para filtros não suportados.
27
+ * Chamado uma vez por box durante a fase de pintura.
28
+ */
29
+ export declare function warnUnsupportedFilters(filters: FilterFunction[] | undefined, label: "filter" | "backdrop-filter", boxId: string): void;