html2canvas-pro 2.1.1 → 2.2.1

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 (92) hide show
  1. package/README.md +19 -19
  2. package/dist/html2canvas-pro.esm.js +10232 -10540
  3. package/dist/html2canvas-pro.esm.js.map +1 -1
  4. package/dist/html2canvas-pro.js +10875 -11185
  5. package/dist/html2canvas-pro.js.map +1 -1
  6. package/dist/html2canvas-pro.min.js +8 -8
  7. package/dist/lib/config.js +0 -22
  8. package/dist/lib/core/cache-storage.js +1 -38
  9. package/dist/lib/core/constants.js +25 -0
  10. package/dist/lib/core/context.js +1 -0
  11. package/dist/lib/core/features.js +1 -0
  12. package/dist/lib/core/render-element.js +2 -1
  13. package/dist/lib/core/validator.js +3 -3
  14. package/dist/lib/css/grouped/background-styles.js +36 -0
  15. package/dist/lib/css/grouped/border-styles.js +75 -0
  16. package/dist/lib/css/grouped/font-styles.js +93 -0
  17. package/dist/lib/css/grouped/layout-styles.js +127 -0
  18. package/dist/lib/css/index.js +81 -47
  19. package/dist/lib/css/layout/text.js +7 -6
  20. package/dist/lib/css/property-descriptors/background-blend-mode.js +41 -0
  21. package/dist/lib/css/property-descriptors/border-image-repeat.js +42 -0
  22. package/dist/lib/css/property-descriptors/border-image-slice.js +45 -0
  23. package/dist/lib/css/property-descriptors/border-image-source.js +21 -0
  24. package/dist/lib/css/property-descriptors/border-radius.js +1 -1
  25. package/dist/lib/css/property-descriptors/box-decoration-break.js +18 -0
  26. package/dist/lib/css/property-descriptors/counter-increment.js +17 -12
  27. package/dist/lib/css/property-descriptors/counter-reset.js +4 -12
  28. package/dist/lib/css/property-descriptors/filter.js +76 -0
  29. package/dist/lib/css/property-descriptors/font-variant-ligatures.js +34 -0
  30. package/dist/lib/css/property-descriptors/object-fit.js +1 -1
  31. package/dist/lib/css/property-descriptors/object-position.js +42 -0
  32. package/dist/lib/css/property-descriptors/visibility.js +1 -1
  33. package/dist/lib/css/property-descriptors/zoom.js +18 -0
  34. package/dist/lib/css/syntax/parser.js +0 -1
  35. package/dist/lib/css/types/color.js +5 -1
  36. package/dist/lib/css/types/functions/repeating-linear-gradient.js +9 -0
  37. package/dist/lib/css/types/image.js +12 -2
  38. package/dist/lib/css/types/length-percentage.js +6 -2
  39. package/dist/lib/css/types/safe-eval.js +80 -0
  40. package/dist/lib/dom/document-cloner.js +23 -163
  41. package/dist/lib/dom/slot-cloner.js +176 -0
  42. package/dist/lib/index.js +1 -17
  43. package/dist/lib/render/canvas/background-renderer.js +165 -32
  44. package/dist/lib/render/canvas/border-image-renderer.js +153 -0
  45. package/dist/lib/render/canvas/canvas-renderer.js +34 -189
  46. package/dist/lib/render/canvas/content-renderer.js +202 -0
  47. package/dist/lib/render/canvas/effects-renderer.js +10 -0
  48. package/dist/lib/render/canvas/text/text-decoration-renderer.js +99 -0
  49. package/dist/lib/render/canvas/text-renderer.js +100 -224
  50. package/dist/lib/render/effects.js +38 -3
  51. package/dist/lib/render/object-fit.js +19 -15
  52. package/dist/lib/render/stacking-context.js +11 -0
  53. package/dist/types/config.d.ts +0 -10
  54. package/dist/types/core/cache-storage.d.ts +0 -24
  55. package/dist/types/core/constants.d.ts +22 -0
  56. package/dist/types/core/context.d.ts +3 -0
  57. package/dist/types/core/performance-monitor.d.ts +4 -4
  58. package/dist/types/core/validator.d.ts +6 -8
  59. package/dist/types/css/grouped/background-styles.d.ts +16 -0
  60. package/dist/types/css/grouped/border-styles.d.ts +31 -0
  61. package/dist/types/css/grouped/font-styles.d.ts +35 -0
  62. package/dist/types/css/grouped/layout-styles.d.ts +46 -0
  63. package/dist/types/css/index.d.ts +30 -0
  64. package/dist/types/css/property-descriptors/background-blend-mode.d.ts +23 -0
  65. package/dist/types/css/property-descriptors/border-image-repeat.d.ts +12 -0
  66. package/dist/types/css/property-descriptors/border-image-slice.d.ts +10 -0
  67. package/dist/types/css/property-descriptors/border-image-source.d.ts +4 -0
  68. package/dist/types/css/property-descriptors/box-decoration-break.d.ts +6 -0
  69. package/dist/types/css/property-descriptors/counter-increment.d.ts +3 -0
  70. package/dist/types/css/property-descriptors/filter.d.ts +3 -0
  71. package/dist/types/css/property-descriptors/font-variant-ligatures.d.ts +14 -0
  72. package/dist/types/css/property-descriptors/object-position.d.ts +4 -0
  73. package/dist/types/css/property-descriptors/zoom.d.ts +3 -0
  74. package/dist/types/css/types/functions/repeating-linear-gradient.d.ts +4 -0
  75. package/dist/types/css/types/image.d.ts +4 -2
  76. package/dist/types/css/types/safe-eval.d.ts +8 -0
  77. package/dist/types/dom/document-cloner.d.ts +3 -44
  78. package/dist/types/dom/slot-cloner.d.ts +66 -0
  79. package/dist/types/index.d.ts +3 -7
  80. package/dist/types/options.d.ts +11 -0
  81. package/dist/types/render/canvas/background-renderer.d.ts +23 -0
  82. package/dist/types/render/canvas/border-image-renderer.d.ts +18 -0
  83. package/dist/types/render/canvas/canvas-renderer.d.ts +1 -0
  84. package/dist/types/render/canvas/content-renderer.d.ts +44 -0
  85. package/dist/types/render/canvas/text/text-decoration-renderer.d.ts +18 -0
  86. package/dist/types/render/canvas/text-renderer.d.ts +12 -1
  87. package/dist/types/render/effects.d.ts +12 -2
  88. package/dist/types/render/object-fit.d.ts +2 -1
  89. package/dist/types/render/renderer-interface.d.ts +11 -9
  90. package/package.json +5 -10
  91. package/dist/lib/dom/replaced-elements/pseudo-elements.js +0 -0
  92. package/dist/types/dom/replaced-elements/pseudo-elements.d.ts +0 -0
@@ -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,20 +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
- if (!ctx) {
91
- return;
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
+ }
92
199
  }
93
- const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
94
- (0, gradient_1.processColorStops)(backgroundImage.stops, lineLength || 1).forEach((colorStop) => gradient.addColorStop(colorStop.stop, (0, color_utilities_1.asString)(colorStop.color)));
95
- ctx.fillStyle = gradient;
96
- ctx.fillRect(0, 0, width, height);
97
- if (width > 0 && height > 0) {
98
- const pattern = this.ctx.createPattern(canvas, 'repeat');
200
+ if (pattern) {
99
201
  this.renderRepeat(path, pattern, x, y);
100
202
  }
101
203
  }
@@ -115,26 +217,37 @@ class BackgroundRenderer {
115
217
  ry = Math.max(ry, 0.01);
116
218
  }
117
219
  if (rx > 0 && ry > 0) {
118
- const radialGradient = this.ctx.createRadialGradient(left + x, top + y, 0, left + x, top + y, rx);
119
- (0, gradient_1.processColorStops)(backgroundImage.stops, rx * 2).forEach((colorStop) => radialGradient.addColorStop(colorStop.stop, (0, color_utilities_1.asString)(colorStop.color)));
120
- this.path(path);
121
- this.ctx.fillStyle = radialGradient;
122
- if (rx !== ry) {
123
- // transforms for elliptical radial gradient
124
- const midX = container.bounds.left + 0.5 * container.bounds.width;
125
- const midY = container.bounds.top + 0.5 * container.bounds.height;
126
- const f = ry / rx;
127
- 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);
128
244
  this.ctx.save();
129
- this.ctx.translate(midX, midY);
130
- this.ctx.transform(1, 0, 0, f, 0, 0);
131
- this.ctx.translate(-midX, -midY);
132
- 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);
133
249
  this.ctx.restore();
134
250
  }
135
- else {
136
- this.ctx.fill();
137
- }
138
251
  }
139
252
  }
140
253
  /**
@@ -202,5 +315,25 @@ class BackgroundRenderer {
202
315
  path(paths) {
203
316
  (0, canvas_path_1.createCanvasPath)(this.ctx, paths);
204
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
+ }
205
337
  }
206
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;