html2canvas-pro 2.1.0 → 2.2.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 (266) hide show
  1. package/dist/html2canvas-pro.esm.js +10226 -10526
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +10869 -11171
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +8 -8
  6. package/dist/lib/config.js +0 -22
  7. package/dist/lib/core/cache-storage.js +3 -40
  8. package/dist/lib/core/constants.js +25 -0
  9. package/dist/lib/core/context.js +1 -0
  10. package/dist/lib/core/features.js +3 -2
  11. package/dist/lib/core/validator.js +3 -3
  12. package/dist/lib/css/grouped/background-styles.js +36 -0
  13. package/dist/lib/css/grouped/border-styles.js +75 -0
  14. package/dist/lib/css/grouped/font-styles.js +93 -0
  15. package/dist/lib/css/grouped/layout-styles.js +127 -0
  16. package/dist/lib/css/index.js +74 -46
  17. package/dist/lib/css/layout/text.js +7 -6
  18. package/dist/lib/css/property-descriptors/background-blend-mode.js +41 -0
  19. package/dist/lib/css/property-descriptors/border-image-repeat.js +42 -0
  20. package/dist/lib/css/property-descriptors/border-image-slice.js +45 -0
  21. package/dist/lib/css/property-descriptors/border-image-source.js +21 -0
  22. package/dist/lib/css/property-descriptors/border-radius.js +1 -1
  23. package/dist/lib/css/property-descriptors/box-decoration-break.js +18 -0
  24. package/dist/lib/css/property-descriptors/counter-increment.js +17 -12
  25. package/dist/lib/css/property-descriptors/counter-reset.js +4 -12
  26. package/dist/lib/css/property-descriptors/filter.js +76 -0
  27. package/dist/lib/css/property-descriptors/font-variant-ligatures.js +34 -0
  28. package/dist/lib/css/property-descriptors/object-fit.js +1 -1
  29. package/dist/lib/css/property-descriptors/object-position.js +42 -0
  30. package/dist/lib/css/property-descriptors/visibility.js +1 -1
  31. package/dist/lib/css/property-descriptors/zoom.js +18 -0
  32. package/dist/lib/css/syntax/parser.js +0 -1
  33. package/dist/lib/css/types/color.js +5 -1
  34. package/dist/lib/css/types/functions/repeating-linear-gradient.js +9 -0
  35. package/dist/lib/css/types/image.js +12 -2
  36. package/dist/lib/css/types/length-percentage.js +6 -2
  37. package/dist/lib/css/types/safe-eval.js +80 -0
  38. package/dist/lib/dom/document-cloner.js +23 -163
  39. package/dist/lib/dom/slot-cloner.js +176 -0
  40. package/dist/lib/index.js +1 -17
  41. package/dist/lib/render/canvas/background-renderer.js +169 -30
  42. package/dist/lib/render/canvas/border-image-renderer.js +153 -0
  43. package/dist/lib/render/canvas/canvas-renderer.js +39 -190
  44. package/dist/lib/render/canvas/content-renderer.js +202 -0
  45. package/dist/lib/render/canvas/effects-renderer.js +3 -0
  46. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  47. package/dist/lib/render/canvas/text/text-decoration-renderer.js +99 -0
  48. package/dist/lib/render/canvas/text-renderer.js +100 -224
  49. package/dist/lib/render/effects.js +38 -3
  50. package/dist/lib/render/object-fit.js +19 -15
  51. package/dist/lib/render/stacking-context.js +11 -0
  52. package/dist/types/config.d.ts +0 -10
  53. package/dist/types/core/cache-storage.d.ts +0 -24
  54. package/dist/types/core/constants.d.ts +22 -0
  55. package/dist/types/core/context.d.ts +3 -0
  56. package/dist/types/core/performance-monitor.d.ts +4 -4
  57. package/dist/types/core/validator.d.ts +6 -8
  58. package/dist/types/css/grouped/background-styles.d.ts +16 -0
  59. package/dist/types/css/grouped/border-styles.d.ts +31 -0
  60. package/dist/types/css/grouped/font-styles.d.ts +35 -0
  61. package/dist/types/css/grouped/layout-styles.d.ts +46 -0
  62. package/dist/types/css/index.d.ts +30 -0
  63. package/dist/types/css/property-descriptors/background-blend-mode.d.ts +23 -0
  64. package/dist/types/css/property-descriptors/border-image-repeat.d.ts +12 -0
  65. package/dist/types/css/property-descriptors/border-image-slice.d.ts +10 -0
  66. package/dist/types/css/property-descriptors/border-image-source.d.ts +4 -0
  67. package/dist/types/css/property-descriptors/box-decoration-break.d.ts +6 -0
  68. package/dist/types/css/property-descriptors/counter-increment.d.ts +3 -0
  69. package/dist/types/css/property-descriptors/filter.d.ts +3 -0
  70. package/dist/types/css/property-descriptors/font-variant-ligatures.d.ts +14 -0
  71. package/dist/types/css/property-descriptors/object-position.d.ts +4 -0
  72. package/dist/types/css/property-descriptors/zoom.d.ts +3 -0
  73. package/dist/types/css/types/functions/repeating-linear-gradient.d.ts +4 -0
  74. package/dist/types/css/types/image.d.ts +4 -2
  75. package/dist/types/css/types/safe-eval.d.ts +8 -0
  76. package/dist/types/dom/document-cloner.d.ts +3 -44
  77. package/dist/types/dom/slot-cloner.d.ts +66 -0
  78. package/dist/types/index.d.ts +3 -7
  79. package/dist/types/options.d.ts +11 -0
  80. package/dist/types/render/canvas/background-renderer.d.ts +23 -0
  81. package/dist/types/render/canvas/border-image-renderer.d.ts +18 -0
  82. package/dist/types/render/canvas/canvas-renderer.d.ts +1 -0
  83. package/dist/types/render/canvas/content-renderer.d.ts +44 -0
  84. package/dist/types/render/canvas/text/text-decoration-renderer.d.ts +18 -0
  85. package/dist/types/render/canvas/text-renderer.d.ts +12 -1
  86. package/dist/types/render/effects.d.ts +12 -2
  87. package/dist/types/render/object-fit.d.ts +2 -1
  88. package/dist/types/render/renderer-interface.d.ts +11 -9
  89. package/package.json +7 -20
  90. package/dist/lib/dom/replaced-elements/pseudo-elements.js +0 -0
  91. package/dist/lib/invariant.js +0 -9
  92. package/dist/types/dom/replaced-elements/pseudo-elements.d.ts +0 -0
  93. package/dist/types/invariant.d.ts +0 -1
  94. package/src/__tests__/index.ts +0 -99
  95. package/src/config.ts +0 -107
  96. package/src/core/__mocks__/cache-storage.ts +0 -1
  97. package/src/core/__mocks__/context.ts +0 -19
  98. package/src/core/__mocks__/features.ts +0 -8
  99. package/src/core/__mocks__/logger.ts +0 -17
  100. package/src/core/__tests__/cache-storage.test.ts +0 -205
  101. package/src/core/__tests__/cache-storage.ts +0 -278
  102. package/src/core/__tests__/logger.ts +0 -29
  103. package/src/core/__tests__/validator.ts +0 -359
  104. package/src/core/bitwise.ts +0 -1
  105. package/src/core/cache-storage.ts +0 -315
  106. package/src/core/context.ts +0 -31
  107. package/src/core/debugger.ts +0 -32
  108. package/src/core/features.ts +0 -222
  109. package/src/core/logger.ts +0 -64
  110. package/src/core/origin-checker.ts +0 -57
  111. package/src/core/performance-monitor.ts +0 -241
  112. package/src/core/render-element.ts +0 -272
  113. package/src/core/util.ts +0 -1
  114. package/src/core/validator.ts +0 -593
  115. package/src/css/index.ts +0 -427
  116. package/src/css/layout/__mocks__/bounds.ts +0 -6
  117. package/src/css/layout/bounds.ts +0 -79
  118. package/src/css/layout/text.ts +0 -161
  119. package/src/css/property-descriptor.ts +0 -49
  120. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  121. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  122. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  123. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  124. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  125. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  126. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  127. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  128. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  129. package/src/css/property-descriptors/background-clip.ts +0 -30
  130. package/src/css/property-descriptors/background-color.ts +0 -9
  131. package/src/css/property-descriptors/background-image.ts +0 -27
  132. package/src/css/property-descriptors/background-origin.ts +0 -31
  133. package/src/css/property-descriptors/background-position.ts +0 -38
  134. package/src/css/property-descriptors/background-repeat.ts +0 -44
  135. package/src/css/property-descriptors/background-size.ts +0 -27
  136. package/src/css/property-descriptors/border-color.ts +0 -13
  137. package/src/css/property-descriptors/border-radius.ts +0 -19
  138. package/src/css/property-descriptors/border-style.ts +0 -34
  139. package/src/css/property-descriptors/border-width.ts +0 -20
  140. package/src/css/property-descriptors/box-shadow.ts +0 -60
  141. package/src/css/property-descriptors/clip-path.ts +0 -271
  142. package/src/css/property-descriptors/color.ts +0 -9
  143. package/src/css/property-descriptors/content.ts +0 -26
  144. package/src/css/property-descriptors/counter-increment.ts +0 -43
  145. package/src/css/property-descriptors/counter-reset.ts +0 -36
  146. package/src/css/property-descriptors/direction.ts +0 -23
  147. package/src/css/property-descriptors/display.ts +0 -117
  148. package/src/css/property-descriptors/duration.ts +0 -14
  149. package/src/css/property-descriptors/float.ts +0 -29
  150. package/src/css/property-descriptors/font-family.ts +0 -38
  151. package/src/css/property-descriptors/font-size.ts +0 -9
  152. package/src/css/property-descriptors/font-style.ts +0 -25
  153. package/src/css/property-descriptors/font-variant.ts +0 -12
  154. package/src/css/property-descriptors/font-weight.ts +0 -26
  155. package/src/css/property-descriptors/image-rendering.ts +0 -33
  156. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  157. package/src/css/property-descriptors/line-break.ts +0 -22
  158. package/src/css/property-descriptors/line-height.ts +0 -22
  159. package/src/css/property-descriptors/list-style-image.ts +0 -19
  160. package/src/css/property-descriptors/list-style-position.ts +0 -22
  161. package/src/css/property-descriptors/list-style-type.ts +0 -179
  162. package/src/css/property-descriptors/margin.ts +0 -13
  163. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  164. package/src/css/property-descriptors/object-fit.ts +0 -39
  165. package/src/css/property-descriptors/opacity.ts +0 -15
  166. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  167. package/src/css/property-descriptors/overflow.ts +0 -34
  168. package/src/css/property-descriptors/padding.ts +0 -14
  169. package/src/css/property-descriptors/paint-order.ts +0 -42
  170. package/src/css/property-descriptors/position.ts +0 -30
  171. package/src/css/property-descriptors/quotes.ts +0 -57
  172. package/src/css/property-descriptors/rotate.ts +0 -34
  173. package/src/css/property-descriptors/text-align.ts +0 -26
  174. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  175. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  176. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  177. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  178. package/src/css/property-descriptors/text-overflow.ts +0 -23
  179. package/src/css/property-descriptors/text-shadow.ts +0 -52
  180. package/src/css/property-descriptors/text-transform.ts +0 -27
  181. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  182. package/src/css/property-descriptors/transform-origin.ts +0 -29
  183. package/src/css/property-descriptors/transform.ts +0 -74
  184. package/src/css/property-descriptors/visibility.ts +0 -25
  185. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  186. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  187. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  188. package/src/css/property-descriptors/word-break.ts +0 -25
  189. package/src/css/property-descriptors/writing-mode.ts +0 -37
  190. package/src/css/property-descriptors/z-index.ts +0 -27
  191. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  192. package/src/css/syntax/parser.ts +0 -188
  193. package/src/css/syntax/tokenizer.ts +0 -822
  194. package/src/css/type-descriptor.ts +0 -7
  195. package/src/css/types/__tests__/color-tests.ts +0 -147
  196. package/src/css/types/__tests__/image-tests.ts +0 -239
  197. package/src/css/types/angle.ts +0 -86
  198. package/src/css/types/color-math.ts +0 -22
  199. package/src/css/types/color-spaces/a98.ts +0 -86
  200. package/src/css/types/color-spaces/p3.ts +0 -92
  201. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  202. package/src/css/types/color-spaces/rec2020.ts +0 -90
  203. package/src/css/types/color-spaces/srgb.ts +0 -87
  204. package/src/css/types/color-utilities.ts +0 -452
  205. package/src/css/types/color.ts +0 -485
  206. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  207. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  208. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  209. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  210. package/src/css/types/functions/counter.ts +0 -511
  211. package/src/css/types/functions/gradient.ts +0 -206
  212. package/src/css/types/functions/linear-gradient.ts +0 -28
  213. package/src/css/types/functions/radial-gradient.ts +0 -101
  214. package/src/css/types/image.ts +0 -120
  215. package/src/css/types/index.ts +0 -1
  216. package/src/css/types/length-percentage.ts +0 -137
  217. package/src/css/types/length.ts +0 -7
  218. package/src/css/types/time.ts +0 -20
  219. package/src/dom/__mocks__/document-cloner.ts +0 -22
  220. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  221. package/src/dom/__tests__/element-container.test.ts +0 -129
  222. package/src/dom/document-cloner.ts +0 -929
  223. package/src/dom/dom-normalizer.ts +0 -133
  224. package/src/dom/element-container.ts +0 -75
  225. package/src/dom/elements/li-element-container.ts +0 -10
  226. package/src/dom/elements/ol-element-container.ts +0 -12
  227. package/src/dom/elements/select-element-container.ts +0 -10
  228. package/src/dom/elements/textarea-element-container.ts +0 -9
  229. package/src/dom/node-parser.ts +0 -177
  230. package/src/dom/node-type-guards.ts +0 -70
  231. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  232. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  233. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  234. package/src/dom/replaced-elements/index.ts +0 -5
  235. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  236. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  237. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  238. package/src/dom/text-container.ts +0 -42
  239. package/src/global.d.ts +0 -19
  240. package/src/index.ts +0 -82
  241. package/src/invariant.ts +0 -5
  242. package/src/options.ts +0 -55
  243. package/src/render/__tests__/object-fit.test.ts +0 -85
  244. package/src/render/background.ts +0 -298
  245. package/src/render/bezier-curve.ts +0 -47
  246. package/src/render/border.ts +0 -165
  247. package/src/render/bound-curves.ts +0 -388
  248. package/src/render/box-sizing.ts +0 -31
  249. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  250. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  251. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  252. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  253. package/src/render/canvas/background-renderer.ts +0 -271
  254. package/src/render/canvas/border-renderer.ts +0 -224
  255. package/src/render/canvas/canvas-path.ts +0 -31
  256. package/src/render/canvas/canvas-renderer.ts +0 -641
  257. package/src/render/canvas/effects-renderer.ts +0 -130
  258. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  259. package/src/render/canvas/text-renderer.ts +0 -700
  260. package/src/render/effects.ts +0 -75
  261. package/src/render/font-metrics.ts +0 -72
  262. package/src/render/object-fit.ts +0 -100
  263. package/src/render/path.ts +0 -37
  264. package/src/render/renderer-interface.ts +0 -28
  265. package/src/render/stacking-context.ts +0 -386
  266. package/src/render/vector.ts +0 -19
@@ -26,10 +26,19 @@ const canvas_path_1 = require("./canvas-path");
26
26
  */
27
27
  class BackgroundRenderer {
28
28
  constructor(deps) {
29
+ /**
30
+ * Instance-level LRU cache for background-image patterns.
31
+ * CanvasPatterns are tied to the rendering context and must not be
32
+ * shared across different render passes. This cache lives for the
33
+ * duration of one html2canvas() call.
34
+ *
35
+ * Also reused for linear-gradient and repeating-linear-gradient
36
+ * pattern canvases to avoid redundant offscreen canvas allocation.
37
+ */
38
+ this.patternCache = new Map();
29
39
  this.ctx = deps.ctx;
30
40
  this.context = deps.context;
31
41
  this.canvas = deps.canvas;
32
- // Options stored in deps but not needed as instance property
33
42
  }
34
43
  /**
35
44
  * Render background images for a container
@@ -39,17 +48,37 @@ class BackgroundRenderer {
39
48
  */
40
49
  async renderBackgroundImage(container) {
41
50
  let index = container.styles.backgroundImage.length - 1;
51
+ const blendModes = container.styles.backgroundBlendMode;
52
+ let layerCount = 0;
42
53
  for (const backgroundImage of container.styles.backgroundImage.slice(0).reverse()) {
54
+ // Save context and apply blend mode for non-first layers
55
+ if (layerCount > 0) {
56
+ const blendMode = blendModes[layerCount] ?? blendModes[0] ?? 'normal';
57
+ if (blendMode !== 'normal') {
58
+ this.ctx.save();
59
+ this.ctx.globalCompositeOperation = blendMode;
60
+ }
61
+ }
43
62
  if (backgroundImage.type === 0 /* CSSImageType.URL */) {
44
63
  await this.renderBackgroundURLImage(container, backgroundImage, index);
45
64
  }
46
65
  else if ((0, image_1.isLinearGradient)(backgroundImage)) {
47
66
  this.renderLinearGradient(container, backgroundImage, index);
48
67
  }
68
+ else if ((0, image_1.isRepeatingLinearGradient)(backgroundImage)) {
69
+ this.renderRepeatingLinearGradient(container, backgroundImage, index);
70
+ }
49
71
  else if ((0, image_1.isRadialGradient)(backgroundImage)) {
50
72
  this.renderRadialGradient(container, backgroundImage, index);
51
73
  }
74
+ if (layerCount > 0) {
75
+ const blendMode = blendModes[layerCount] ?? blendModes[0] ?? 'normal';
76
+ if (blendMode !== 'normal') {
77
+ this.ctx.restore();
78
+ }
79
+ }
52
80
  index--;
81
+ layerCount++;
53
82
  }
54
83
  }
55
84
  /**
@@ -63,6 +92,7 @@ class BackgroundRenderer {
63
92
  }
64
93
  catch (e) {
65
94
  this.context.logger.error(`Error loading background-image ${url}`);
95
+ this.context.onError?.(e instanceof Error ? e : new Error(String(e)));
66
96
  }
67
97
  if (image) {
68
98
  const imageWidth = isNaN(image.width) || image.width === 0 ? 1 : image.width;
@@ -72,7 +102,71 @@ class BackgroundRenderer {
72
102
  imageHeight,
73
103
  imageWidth / imageHeight
74
104
  ]);
75
- const pattern = this.ctx.createPattern(this.resizeImage(image, width, height, container.styles.imageRendering), 'repeat');
105
+ // Cache key: URL + resized dimensions + imageRendering (pattern is dependent on all three)
106
+ const cacheKey = `${url}|${Math.round(width)}x${Math.round(height)}|${container.styles.imageRendering}`;
107
+ let pattern = this.lruGet(this.patternCache, cacheKey);
108
+ if (!pattern) {
109
+ const resized = this.resizeImage(image, width, height, container.styles.imageRendering);
110
+ pattern = this.ctx.createPattern(resized, 'repeat');
111
+ this.lruSet(this.patternCache, cacheKey, pattern);
112
+ }
113
+ this.renderRepeat(path, pattern, x, y);
114
+ }
115
+ }
116
+ /**
117
+ * Render a repeating linear gradient background.
118
+ * Renders one cycle of the gradient to a pattern canvas, then fills
119
+ * the background area using createPattern('repeat').
120
+ */
121
+ renderRepeatingLinearGradient(container, backgroundImage, index) {
122
+ const [path, x, y, width, height] = (0, background_1.calculateBackgroundRendering)(container, index, [null, null, null]);
123
+ const [lineLength, x0, x1, y0, y1] = (0, gradient_1.calculateGradientDirection)(backgroundImage.angle, width, height);
124
+ // Determine the repeating pattern length from color stops
125
+ const processedStops = (0, gradient_1.processColorStops)(backgroundImage.stops, lineLength || 1);
126
+ const lastStop = processedStops[processedStops.length - 1];
127
+ const firstStop = processedStops[0];
128
+ const patternLength = lastStop.stop - firstStop.stop;
129
+ if (patternLength <= 0) {
130
+ // Fallback: render as normal linear gradient
131
+ this.renderLinearGradient(container, backgroundImage, index);
132
+ return;
133
+ }
134
+ // Scale direction vectors to match the pattern length
135
+ const dirX = x1 - x0;
136
+ const dirY = y1 - y0;
137
+ const totalLength = Math.sqrt(dirX * dirX + dirY * dirY);
138
+ const scale = patternLength / (totalLength || 1);
139
+ const pX0 = x0;
140
+ const pY0 = y0;
141
+ const pX1 = x0 + dirX * scale;
142
+ const pY1 = y0 + dirY * scale;
143
+ const ownerDocument = this.canvas.ownerDocument ?? document;
144
+ // Cache key for this repeating gradient pattern
145
+ const cacheKey = `rlg|${backgroundImage.angle}|${Math.round(patternLength)}|${JSON.stringify(backgroundImage.stops)}`;
146
+ let pattern = this.lruGet(this.patternCache, cacheKey);
147
+ if (!pattern) {
148
+ const canvas = ownerDocument.createElement('canvas');
149
+ // Create a canvas large enough to hold one full repeating unit
150
+ const canvasSize = Math.max(1, Math.ceil(patternLength));
151
+ canvas.width = canvasSize;
152
+ canvas.height = canvasSize;
153
+ const ctx = canvas.getContext('2d');
154
+ if (!ctx) {
155
+ return;
156
+ }
157
+ const gradient = ctx.createLinearGradient(pX0 - x, pY0 - y, pX1 - x, pY1 - y);
158
+ // Normalize stops to [0, 1] range for one repeating unit
159
+ processedStops.forEach((colorStop) => {
160
+ gradient.addColorStop((colorStop.stop - firstStop.stop) / patternLength, (0, color_utilities_1.asString)(colorStop.color));
161
+ });
162
+ ctx.fillStyle = gradient;
163
+ ctx.fillRect(0, 0, canvasSize, canvasSize);
164
+ if (canvasSize > 0) {
165
+ pattern = this.ctx.createPattern(canvas, 'repeat');
166
+ this.lruSet(this.patternCache, cacheKey, pattern);
167
+ }
168
+ }
169
+ if (pattern) {
76
170
  this.renderRepeat(path, pattern, x, y);
77
171
  }
78
172
  }
@@ -82,17 +176,28 @@ class BackgroundRenderer {
82
176
  renderLinearGradient(container, backgroundImage, index) {
83
177
  const [path, x, y, width, height] = (0, background_1.calculateBackgroundRendering)(container, index, [null, null, null]);
84
178
  const [lineLength, x0, x1, y0, y1] = (0, gradient_1.calculateGradientDirection)(backgroundImage.angle, width, height);
85
- const ownerDocument = this.canvas.ownerDocument ?? document;
86
- const canvas = ownerDocument.createElement('canvas');
87
- canvas.width = width;
88
- canvas.height = height;
89
- const ctx = canvas.getContext('2d');
90
- const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
91
- (0, gradient_1.processColorStops)(backgroundImage.stops, lineLength || 1).forEach((colorStop) => gradient.addColorStop(colorStop.stop, (0, color_utilities_1.asString)(colorStop.color)));
92
- ctx.fillStyle = gradient;
93
- ctx.fillRect(0, 0, width, height);
94
- if (width > 0 && height > 0) {
95
- const pattern = this.ctx.createPattern(canvas, 'repeat');
179
+ // Cache key: angle + dimensions + serialised colour stops
180
+ const cacheKey = `lg|${backgroundImage.angle}|${Math.round(width)}x${Math.round(height)}|${JSON.stringify(backgroundImage.stops)}`;
181
+ let pattern = this.lruGet(this.patternCache, cacheKey);
182
+ if (!pattern) {
183
+ const ownerDocument = this.canvas.ownerDocument ?? document;
184
+ const canvas = ownerDocument.createElement('canvas');
185
+ canvas.width = width;
186
+ canvas.height = height;
187
+ const ctx = canvas.getContext('2d');
188
+ if (!ctx) {
189
+ return;
190
+ }
191
+ const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
192
+ (0, gradient_1.processColorStops)(backgroundImage.stops, lineLength || 1).forEach((colorStop) => gradient.addColorStop(colorStop.stop, (0, color_utilities_1.asString)(colorStop.color)));
193
+ ctx.fillStyle = gradient;
194
+ ctx.fillRect(0, 0, width, height);
195
+ if (width > 0 && height > 0) {
196
+ pattern = this.ctx.createPattern(canvas, 'repeat');
197
+ this.lruSet(this.patternCache, cacheKey, pattern);
198
+ }
199
+ }
200
+ if (pattern) {
96
201
  this.renderRepeat(path, pattern, x, y);
97
202
  }
98
203
  }
@@ -112,26 +217,37 @@ class BackgroundRenderer {
112
217
  ry = Math.max(ry, 0.01);
113
218
  }
114
219
  if (rx > 0 && ry > 0) {
115
- const radialGradient = this.ctx.createRadialGradient(left + x, top + y, 0, left + x, top + y, rx);
116
- (0, gradient_1.processColorStops)(backgroundImage.stops, rx * 2).forEach((colorStop) => radialGradient.addColorStop(colorStop.stop, (0, color_utilities_1.asString)(colorStop.color)));
117
- this.path(path);
118
- this.ctx.fillStyle = radialGradient;
119
- if (rx !== ry) {
120
- // transforms for elliptical radial gradient
121
- const midX = container.bounds.left + 0.5 * container.bounds.width;
122
- const midY = container.bounds.top + 0.5 * container.bounds.height;
123
- const f = ry / rx;
124
- const invF = 1 / f;
220
+ // Cache key for radial gradient: position + radii + colour stops
221
+ const cacheKey = `rg|${Math.round(x)}x${Math.round(y)}|${Math.round(rx)}x${Math.round(ry)}|${JSON.stringify(backgroundImage.stops)}`;
222
+ let pattern = this.lruGet(this.patternCache, cacheKey);
223
+ if (!pattern) {
224
+ const ownerDocument = this.canvas.ownerDocument ?? document;
225
+ const size = Math.ceil(Math.max(rx, ry) * 2);
226
+ const offscreen = ownerDocument.createElement('canvas');
227
+ offscreen.width = size;
228
+ offscreen.height = size;
229
+ const offCtx = offscreen.getContext('2d');
230
+ if (offCtx) {
231
+ const offRadius = Math.max(rx, ry);
232
+ const gradient = offCtx.createRadialGradient(offRadius, offRadius, 0, offRadius, offRadius, offRadius);
233
+ (0, gradient_1.processColorStops)(backgroundImage.stops, offRadius * 2).forEach((s) => gradient.addColorStop(s.stop, (0, color_utilities_1.asString)(s.color)));
234
+ offCtx.fillStyle = gradient;
235
+ if (rx !== ry)
236
+ offCtx.scale(1, ry / rx);
237
+ offCtx.fillRect(0, 0, offRadius * 2, offRadius * 2);
238
+ pattern = this.ctx.createPattern(offscreen, 'no-repeat');
239
+ this.lruSet(this.patternCache, cacheKey, pattern);
240
+ }
241
+ }
242
+ if (pattern) {
243
+ this.path(path);
125
244
  this.ctx.save();
126
- this.ctx.translate(midX, midY);
127
- this.ctx.transform(1, 0, 0, f, 0, 0);
128
- this.ctx.translate(-midX, -midY);
129
- this.ctx.fillRect(left, invF * (top - midY) + midY, width, height * invF);
245
+ this.ctx.clip();
246
+ this.ctx.translate(left + x - Math.max(rx, ry), top + y - Math.max(rx, ry));
247
+ this.ctx.fillStyle = pattern;
248
+ this.ctx.fillRect(0, 0, Math.max(rx, ry) * 2, Math.max(rx, ry) * 2);
130
249
  this.ctx.restore();
131
250
  }
132
- else {
133
- this.ctx.fill();
134
- }
135
251
  }
136
252
  }
137
253
  /**
@@ -168,6 +284,9 @@ class BackgroundRenderer {
168
284
  canvas.width = Math.max(1, width);
169
285
  canvas.height = Math.max(1, height);
170
286
  const ctx = canvas.getContext('2d');
287
+ if (!ctx) {
288
+ return image;
289
+ }
171
290
  // Apply image smoothing based on CSS image-rendering property
172
291
  if (imageRendering === image_rendering_1.IMAGE_RENDERING.PIXELATED || imageRendering === image_rendering_1.IMAGE_RENDERING.CRISP_EDGES) {
173
292
  this.context.logger.debug(`Disabling image smoothing for background image due to CSS image-rendering`);
@@ -196,5 +315,25 @@ class BackgroundRenderer {
196
315
  path(paths) {
197
316
  (0, canvas_path_1.createCanvasPath)(this.ctx, paths);
198
317
  }
318
+ /**
319
+ * LRU-aware get: returns value and promotes the entry to end of Map.
320
+ */
321
+ lruGet(cache, key) {
322
+ const value = cache.get(key);
323
+ if (value !== undefined) {
324
+ cache.delete(key);
325
+ cache.set(key, value);
326
+ }
327
+ return value;
328
+ }
329
+ /** LRU-aware set for CanvasPattern caches. Evicts oldest entry on overflow. */
330
+ lruSet(cache, key, value) {
331
+ if (cache.size >= BackgroundRenderer.PATTERN_CACHE_MAX) {
332
+ const oldestKey = cache.keys().next().value;
333
+ cache.delete(oldestKey);
334
+ }
335
+ cache.set(key, value);
336
+ }
199
337
  }
200
338
  exports.BackgroundRenderer = BackgroundRenderer;
339
+ BackgroundRenderer.PATTERN_CACHE_MAX = 50;
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Border Image Renderer
4
+ *
5
+ * Renders CSS border-image using 9-slice scaling.
6
+ * The source image is divided into 9 regions (4 corners, 4 edges, 1 center)
7
+ * based on border-image-slice values, then each region is drawn to the
8
+ * corresponding area of the element's border box.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.BorderImageRenderer = void 0;
12
+ const border_image_repeat_1 = require("../../css/property-descriptors/border-image-repeat");
13
+ class BorderImageRenderer {
14
+ constructor(ctx) {
15
+ this.ctx = ctx;
16
+ }
17
+ renderBorderImage(bounds, image, slice, repeat, borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth) {
18
+ const imgW = image.naturalWidth || image.width;
19
+ const imgH = image.naturalHeight || image.height;
20
+ if (imgW <= 0 || imgH <= 0) {
21
+ return;
22
+ }
23
+ // Calculate source slice positions in image pixel space
24
+ const sT = Math.min(slice.unit === 'percent' ? (slice.top / 100) * imgH : Math.min(slice.top, imgH), imgH);
25
+ const sR = Math.min(slice.unit === 'percent' ? (slice.right / 100) * imgW : Math.min(slice.right, imgW), imgW);
26
+ const sB = Math.min(slice.unit === 'percent' ? (slice.bottom / 100) * imgH : Math.min(slice.bottom, imgH), imgH);
27
+ const sL = Math.min(slice.unit === 'percent' ? (slice.left / 100) * imgW : Math.min(slice.left, imgW), imgW);
28
+ const { left, top, width, height } = bounds;
29
+ if (width <= 0 || height <= 0) {
30
+ return;
31
+ }
32
+ // Clamp border widths to available box dimensions
33
+ const dT = Math.min(borderTopWidth, height);
34
+ const dR = Math.min(borderRightWidth, width);
35
+ const dB = Math.min(borderBottomWidth, height - dT);
36
+ const dL = Math.min(borderLeftWidth, width - dR);
37
+ // Draw corners
38
+ this.drawRegion(image, 0, 0, sL, sT, left, top, dL, dT);
39
+ this.drawRegion(image, imgW - sR, 0, sR, sT, left + width - dR, top, dR, dT);
40
+ this.drawRegion(image, imgW - sR, imgH - sB, sR, sB, left + width - dR, top + height - dB, dR, dB);
41
+ this.drawRegion(image, 0, imgH - sB, sL, sB, left, top + height - dB, dL, dB);
42
+ // Draw edges
43
+ const edges = [
44
+ {
45
+ sx: sL,
46
+ sy: 0,
47
+ sw: imgW - sL - sR,
48
+ sh: sT,
49
+ dx: left + dL,
50
+ dy: top,
51
+ dw: width - dL - dR,
52
+ dh: dT,
53
+ repeat: repeat.horizontal
54
+ },
55
+ {
56
+ sx: imgW - sR,
57
+ sy: sT,
58
+ sw: sR,
59
+ sh: imgH - sT - sB,
60
+ dx: left + width - dR,
61
+ dy: top + dT,
62
+ dw: dR,
63
+ dh: height - dT - dB,
64
+ repeat: repeat.vertical
65
+ },
66
+ {
67
+ sx: sL,
68
+ sy: imgH - sB,
69
+ sw: imgW - sL - sR,
70
+ sh: sB,
71
+ dx: left + dL,
72
+ dy: top + height - dB,
73
+ dw: width - dL - dR,
74
+ dh: dB,
75
+ repeat: repeat.horizontal
76
+ },
77
+ {
78
+ sx: 0,
79
+ sy: sT,
80
+ sw: sL,
81
+ sh: imgH - sT - sB,
82
+ dx: left,
83
+ dy: top + dT,
84
+ dw: dL,
85
+ dh: height - dT - dB,
86
+ repeat: repeat.vertical
87
+ }
88
+ ];
89
+ for (const edge of edges) {
90
+ if (edge.sw <= 0 || edge.sh <= 0 || edge.dw <= 0 || edge.dh <= 0)
91
+ continue;
92
+ const isHorizontal = edge.dw >= edge.dh;
93
+ if (edge.repeat === border_image_repeat_1.BORDER_IMAGE_REPEAT.STRETCH) {
94
+ this.ctx.drawImage(image, edge.sx, edge.sy, edge.sw, edge.sh, edge.dx, edge.dy, edge.dw, edge.dh);
95
+ }
96
+ else if (edge.repeat === border_image_repeat_1.BORDER_IMAGE_REPEAT.REPEAT || edge.repeat === border_image_repeat_1.BORDER_IMAGE_REPEAT.ROUND) {
97
+ this.drawRepeatedEdge(image, edge, isHorizontal, edge.repeat === border_image_repeat_1.BORDER_IMAGE_REPEAT.ROUND);
98
+ }
99
+ }
100
+ // Draw center if fill is specified
101
+ if (slice.fill) {
102
+ const cx = sL;
103
+ const cy = sT;
104
+ const cw = imgW - sL - sR;
105
+ const ch = imgH - sT - sB;
106
+ const tcx = left + dL;
107
+ const tcy = top + dT;
108
+ const tcw = width - dL - dR;
109
+ const tch = height - dT - dB;
110
+ this.drawRegion(image, cx, cy, cw, ch, tcx, tcy, tcw, tch);
111
+ }
112
+ }
113
+ drawRegion(image, sx, sy, sw, sh, dx, dy, dw, dh) {
114
+ if (sw > 0 && sh > 0 && dw > 0 && dh > 0) {
115
+ this.ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
116
+ }
117
+ }
118
+ drawRepeatedEdge(image, edge, isHorizontal, round) {
119
+ const srcLength = isHorizontal ? edge.sw : edge.sh;
120
+ const tgLength = isHorizontal ? edge.dw : edge.dh;
121
+ if (srcLength <= 0 || tgLength <= 0)
122
+ return;
123
+ let tileSize;
124
+ let tileCount;
125
+ if (round) {
126
+ tileCount = Math.max(1, Math.round(tgLength / srcLength));
127
+ tileSize = tgLength / tileCount;
128
+ }
129
+ else {
130
+ tileSize = srcLength;
131
+ tileCount = Math.ceil(tgLength / tileSize);
132
+ }
133
+ this.ctx.save();
134
+ this.ctx.beginPath();
135
+ this.ctx.rect(edge.dx, edge.dy, edge.dw, edge.dh);
136
+ this.ctx.clip();
137
+ for (let i = 0; i < tileCount; i++) {
138
+ const offset = i * tileSize;
139
+ const remaining = tgLength - offset;
140
+ const clampedSize = Math.min(tileSize, remaining);
141
+ if (clampedSize <= 0)
142
+ break;
143
+ if (isHorizontal) {
144
+ this.ctx.drawImage(image, edge.sx, edge.sy, edge.sw, edge.sh, edge.dx + offset, edge.dy, clampedSize, edge.dh);
145
+ }
146
+ else {
147
+ this.ctx.drawImage(image, edge.sx, edge.sy, edge.sw, edge.sh, edge.dx, edge.dy + offset, edge.dw, clampedSize);
148
+ }
149
+ }
150
+ this.ctx.restore();
151
+ }
152
+ }
153
+ exports.BorderImageRenderer = BorderImageRenderer;