html2canvas-pro 1.6.7 → 2.0.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 (122) hide show
  1. package/README.md +1 -0
  2. package/demo/image-smoothing-demo.html +256 -0
  3. package/demo/refactoring-test.html +602 -0
  4. package/dist/html2canvas-pro.esm.js +2788 -1321
  5. package/dist/html2canvas-pro.esm.js.map +1 -1
  6. package/dist/html2canvas-pro.js +2791 -1320
  7. package/dist/html2canvas-pro.js.map +1 -1
  8. package/dist/html2canvas-pro.min.js +5 -4
  9. package/dist/lib/__tests__/index.js +8 -2
  10. package/dist/lib/__tests__/index.js.map +1 -1
  11. package/dist/lib/config.js +72 -0
  12. package/dist/lib/config.js.map +1 -0
  13. package/dist/lib/core/__tests__/cache-storage.js +6 -3
  14. package/dist/lib/core/__tests__/cache-storage.js.map +1 -1
  15. package/dist/lib/core/__tests__/cache-storage.test.js +158 -0
  16. package/dist/lib/core/__tests__/cache-storage.test.js.map +1 -0
  17. package/dist/lib/core/__tests__/validator.js +296 -0
  18. package/dist/lib/core/__tests__/validator.js.map +1 -0
  19. package/dist/lib/core/cache-storage.js +130 -11
  20. package/dist/lib/core/cache-storage.js.map +1 -1
  21. package/dist/lib/core/context.js +5 -2
  22. package/dist/lib/core/context.js.map +1 -1
  23. package/dist/lib/core/debugger.js +3 -0
  24. package/dist/lib/core/debugger.js.map +1 -1
  25. package/dist/lib/core/origin-checker.js +54 -0
  26. package/dist/lib/core/origin-checker.js.map +1 -0
  27. package/dist/lib/core/performance-monitor.js +208 -0
  28. package/dist/lib/core/performance-monitor.js.map +1 -0
  29. package/dist/lib/core/validator.js +501 -0
  30. package/dist/lib/core/validator.js.map +1 -0
  31. package/dist/lib/css/index.js +2 -0
  32. package/dist/lib/css/index.js.map +1 -1
  33. package/dist/lib/css/property-descriptors/__tests__/background-tests.js +7 -1
  34. package/dist/lib/css/property-descriptors/__tests__/background-tests.js.map +1 -1
  35. package/dist/lib/css/property-descriptors/__tests__/image-rendering-integration.test.js +142 -0
  36. package/dist/lib/css/property-descriptors/__tests__/image-rendering-integration.test.js.map +1 -0
  37. package/dist/lib/css/property-descriptors/__tests__/image-rendering-performance.test.js +167 -0
  38. package/dist/lib/css/property-descriptors/__tests__/image-rendering-performance.test.js.map +1 -0
  39. package/dist/lib/css/property-descriptors/__tests__/image-rendering.test.js +61 -0
  40. package/dist/lib/css/property-descriptors/__tests__/image-rendering.test.js.map +1 -0
  41. package/dist/lib/css/property-descriptors/image-rendering.js +34 -0
  42. package/dist/lib/css/property-descriptors/image-rendering.js.map +1 -0
  43. package/dist/lib/css/types/__tests__/image-tests.js +7 -1
  44. package/dist/lib/css/types/__tests__/image-tests.js.map +1 -1
  45. package/dist/lib/css/types/color-math.js +26 -0
  46. package/dist/lib/css/types/color-math.js.map +1 -0
  47. package/dist/lib/css/types/color-spaces/srgb.js +6 -6
  48. package/dist/lib/css/types/color-spaces/srgb.js.map +1 -1
  49. package/dist/lib/css/types/color-utilities.js +13 -22
  50. package/dist/lib/css/types/color-utilities.js.map +1 -1
  51. package/dist/lib/dom/__tests__/dom-normalizer.test.js +113 -0
  52. package/dist/lib/dom/__tests__/dom-normalizer.test.js.map +1 -0
  53. package/dist/lib/dom/__tests__/element-container.test.js +109 -0
  54. package/dist/lib/dom/__tests__/element-container.test.js.map +1 -0
  55. package/dist/lib/dom/document-cloner.js +3 -3
  56. package/dist/lib/dom/document-cloner.js.map +1 -1
  57. package/dist/lib/dom/dom-normalizer.js +80 -0
  58. package/dist/lib/dom/dom-normalizer.js.map +1 -0
  59. package/dist/lib/dom/element-container.js +32 -15
  60. package/dist/lib/dom/element-container.js.map +1 -1
  61. package/dist/lib/dom/node-parser.js +16 -20
  62. package/dist/lib/dom/node-parser.js.map +1 -1
  63. package/dist/lib/dom/node-type-guards.js +44 -0
  64. package/dist/lib/dom/node-type-guards.js.map +1 -0
  65. package/dist/lib/dom/replaced-elements/iframe-element-container.js +5 -4
  66. package/dist/lib/dom/replaced-elements/iframe-element-container.js.map +1 -1
  67. package/dist/lib/index.js +148 -41
  68. package/dist/lib/index.js.map +1 -1
  69. package/dist/lib/render/canvas/__tests__/background-renderer.test.js +65 -0
  70. package/dist/lib/render/canvas/__tests__/background-renderer.test.js.map +1 -0
  71. package/dist/lib/render/canvas/__tests__/border-renderer.test.js +23 -0
  72. package/dist/lib/render/canvas/__tests__/border-renderer.test.js.map +1 -0
  73. package/dist/lib/render/canvas/__tests__/effects-renderer.test.js +30 -0
  74. package/dist/lib/render/canvas/__tests__/effects-renderer.test.js.map +1 -0
  75. package/dist/lib/render/canvas/__tests__/text-renderer.test.js +63 -0
  76. package/dist/lib/render/canvas/__tests__/text-renderer.test.js.map +1 -0
  77. package/dist/lib/render/canvas/background-renderer.js +222 -0
  78. package/dist/lib/render/canvas/background-renderer.js.map +1 -0
  79. package/dist/lib/render/canvas/border-renderer.js +185 -0
  80. package/dist/lib/render/canvas/border-renderer.js.map +1 -0
  81. package/dist/lib/render/canvas/canvas-renderer.js +61 -689
  82. package/dist/lib/render/canvas/canvas-renderer.js.map +1 -1
  83. package/dist/lib/render/canvas/effects-renderer.js +89 -0
  84. package/dist/lib/render/canvas/effects-renderer.js.map +1 -0
  85. package/dist/lib/render/canvas/text-renderer.js +508 -0
  86. package/dist/lib/render/canvas/text-renderer.js.map +1 -0
  87. package/dist/lib/render/renderer-interface.js +3 -0
  88. package/dist/lib/render/renderer-interface.js.map +1 -0
  89. package/dist/types/config.d.ts +54 -0
  90. package/dist/types/core/__tests__/cache-storage.test.d.ts +1 -0
  91. package/dist/types/core/__tests__/validator.d.ts +1 -0
  92. package/dist/types/core/cache-storage.d.ts +42 -1
  93. package/dist/types/core/context.d.ts +5 -1
  94. package/dist/types/core/origin-checker.d.ts +33 -0
  95. package/dist/types/core/performance-monitor.d.ts +131 -0
  96. package/dist/types/core/validator.d.ts +132 -0
  97. package/dist/types/css/index.d.ts +2 -0
  98. package/dist/types/css/property-descriptors/__tests__/image-rendering-integration.test.d.ts +1 -0
  99. package/dist/types/css/property-descriptors/__tests__/image-rendering-performance.test.d.ts +1 -0
  100. package/dist/types/css/property-descriptors/__tests__/image-rendering.test.d.ts +1 -0
  101. package/dist/types/css/property-descriptors/image-rendering.d.ts +8 -0
  102. package/dist/types/css/types/color-math.d.ts +12 -0
  103. package/dist/types/css/types/color-utilities.d.ts +2 -3
  104. package/dist/types/dom/__tests__/dom-normalizer.test.d.ts +1 -0
  105. package/dist/types/dom/__tests__/element-container.test.d.ts +1 -0
  106. package/dist/types/dom/dom-normalizer.d.ts +43 -0
  107. package/dist/types/dom/element-container.d.ts +20 -1
  108. package/dist/types/dom/node-parser.d.ts +2 -7
  109. package/dist/types/dom/node-type-guards.d.ts +33 -0
  110. package/dist/types/dom/replaced-elements/iframe-element-container.d.ts +4 -1
  111. package/dist/types/index.d.ts +48 -3
  112. package/dist/types/render/canvas/__tests__/background-renderer.test.d.ts +1 -0
  113. package/dist/types/render/canvas/__tests__/border-renderer.test.d.ts +1 -0
  114. package/dist/types/render/canvas/__tests__/effects-renderer.test.d.ts +1 -0
  115. package/dist/types/render/canvas/__tests__/text-renderer.test.d.ts +1 -0
  116. package/dist/types/render/canvas/background-renderer.d.ts +87 -0
  117. package/dist/types/render/canvas/border-renderer.d.ts +67 -0
  118. package/dist/types/render/canvas/canvas-renderer.d.ts +19 -23
  119. package/dist/types/render/canvas/effects-renderer.d.ts +64 -0
  120. package/dist/types/render/canvas/text-renderer.d.ts +57 -0
  121. package/dist/types/render/renderer-interface.d.ts +26 -0
  122. package/package.json +2 -1
@@ -7,32 +7,31 @@ const path_1 = require("../path");
7
7
  const bound_curves_1 = require("../bound-curves");
8
8
  const bezier_curve_1 = require("../bezier-curve");
9
9
  const vector_1 = require("../vector");
10
- const image_1 = require("../../css/types/image");
11
- const border_1 = require("../border");
12
10
  const background_1 = require("../background");
13
- const parser_1 = require("../../css/syntax/parser");
14
11
  const text_1 = require("../../css/layout/text");
15
12
  const image_element_container_1 = require("../../dom/replaced-elements/image-element-container");
16
13
  const box_sizing_1 = require("../box-sizing");
17
14
  const canvas_element_container_1 = require("../../dom/replaced-elements/canvas-element-container");
18
15
  const svg_element_container_1 = require("../../dom/replaced-elements/svg-element-container");
19
- const effects_1 = require("../effects");
20
16
  const bitwise_1 = require("../../core/bitwise");
21
- const gradient_1 = require("../../css/types/functions/gradient");
22
17
  const length_percentage_1 = require("../../css/types/length-percentage");
23
18
  const font_metrics_1 = require("../font-metrics");
24
19
  const bounds_1 = require("../../css/layout/bounds");
20
+ const image_rendering_1 = require("../../css/property-descriptors/image-rendering");
25
21
  const line_height_1 = require("../../css/property-descriptors/line-height");
26
22
  const input_element_container_1 = require("../../dom/replaced-elements/input-element-container");
27
23
  const textarea_element_container_1 = require("../../dom/elements/textarea-element-container");
28
24
  const select_element_container_1 = require("../../dom/elements/select-element-container");
29
25
  const iframe_element_container_1 = require("../../dom/replaced-elements/iframe-element-container");
30
26
  const renderer_1 = require("../renderer");
27
+ const background_renderer_1 = require("./background-renderer");
28
+ const border_renderer_1 = require("./border-renderer");
29
+ const effects_renderer_1 = require("./effects-renderer");
30
+ const text_renderer_1 = require("./text-renderer");
31
31
  const MASK_OFFSET = 10000;
32
32
  class CanvasRenderer extends renderer_1.Renderer {
33
33
  constructor(context, options) {
34
34
  super(context, options);
35
- this._activeEffects = [];
36
35
  this.canvas = options.canvas ? options.canvas : document.createElement('canvas');
37
36
  this.ctx = this.canvas.getContext('2d');
38
37
  if (!options.canvas) {
@@ -45,35 +44,36 @@ class CanvasRenderer extends renderer_1.Renderer {
45
44
  this.ctx.scale(this.options.scale, this.options.scale);
46
45
  this.ctx.translate(-options.x, -options.y);
47
46
  this.ctx.textBaseline = 'bottom';
48
- this._activeEffects = [];
47
+ // Set image smoothing options
48
+ if (options.imageSmoothing !== undefined) {
49
+ this.ctx.imageSmoothingEnabled = options.imageSmoothing;
50
+ }
51
+ if (options.imageSmoothingQuality) {
52
+ this.ctx.imageSmoothingQuality = options.imageSmoothingQuality;
53
+ }
54
+ // Initialize specialized renderers
55
+ this.backgroundRenderer = new background_renderer_1.BackgroundRenderer({
56
+ ctx: this.ctx,
57
+ context: this.context,
58
+ canvas: this.canvas,
59
+ options: {
60
+ width: options.width,
61
+ height: options.height,
62
+ scale: options.scale
63
+ }
64
+ });
65
+ this.borderRenderer = new border_renderer_1.BorderRenderer({ ctx: this.ctx }, {
66
+ path: (paths) => this.path(paths),
67
+ formatPath: (paths) => this.formatPath(paths)
68
+ });
69
+ this.effectsRenderer = new effects_renderer_1.EffectsRenderer({ ctx: this.ctx }, { path: (paths) => this.path(paths) });
70
+ this.textRenderer = new text_renderer_1.TextRenderer({
71
+ ctx: this.ctx,
72
+ context: this.context,
73
+ options: { scale: options.scale }
74
+ });
49
75
  this.context.logger.debug(`Canvas renderer initialized (${options.width}x${options.height}) with scale ${options.scale}`);
50
76
  }
51
- applyEffects(effects) {
52
- while (this._activeEffects.length) {
53
- this.popEffect();
54
- }
55
- effects.forEach((effect) => this.applyEffect(effect));
56
- }
57
- applyEffect(effect) {
58
- this.ctx.save();
59
- if ((0, effects_1.isOpacityEffect)(effect)) {
60
- this.ctx.globalAlpha = effect.opacity;
61
- }
62
- if ((0, effects_1.isTransformEffect)(effect)) {
63
- this.ctx.translate(effect.offsetX, effect.offsetY);
64
- this.ctx.transform(effect.matrix[0], effect.matrix[1], effect.matrix[2], effect.matrix[3], effect.matrix[4], effect.matrix[5]);
65
- this.ctx.translate(-effect.offsetX, -effect.offsetY);
66
- }
67
- if ((0, effects_1.isClipEffect)(effect)) {
68
- this.path(effect.path);
69
- this.ctx.clip();
70
- }
71
- this._activeEffects.push(effect);
72
- }
73
- popEffect() {
74
- this._activeEffects.pop();
75
- this.ctx.restore();
76
- }
77
77
  async renderStack(stack) {
78
78
  const styles = stack.element.container.styles;
79
79
  if (styles.isVisible()) {
@@ -89,427 +89,11 @@ class CanvasRenderer extends renderer_1.Renderer {
89
89
  await this.renderNodeContent(paint);
90
90
  }
91
91
  }
92
- renderTextWithLetterSpacing(text, letterSpacing, baseline) {
93
- if (letterSpacing === 0) {
94
- // Use alphabetic baseline for consistent text positioning across browsers
95
- // Issue #129: text.bounds.top + text.bounds.height causes text to render too low
96
- this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + baseline);
97
- }
98
- else {
99
- const letters = (0, text_1.segmentGraphemes)(text.text);
100
- letters.reduce((left, letter) => {
101
- this.ctx.fillText(letter, left, text.bounds.top + baseline);
102
- return left + this.ctx.measureText(letter).width;
103
- }, text.bounds.left);
104
- }
105
- }
106
92
  /**
107
93
  * Helper method to render text with paint order support
108
94
  * Reduces code duplication in line-clamp and normal rendering
109
95
  */
110
- renderTextBoundWithPaintOrder(textBound, styles, paintOrderLayers) {
111
- paintOrderLayers.forEach((paintOrderLayer) => {
112
- switch (paintOrderLayer) {
113
- case 0 /* PAINT_ORDER_LAYER.FILL */:
114
- this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
115
- this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number);
116
- break;
117
- case 1 /* PAINT_ORDER_LAYER.STROKE */:
118
- if (styles.webkitTextStrokeWidth && textBound.text.trim().length) {
119
- this.ctx.strokeStyle = (0, color_utilities_1.asString)(styles.webkitTextStrokeColor);
120
- this.ctx.lineWidth = styles.webkitTextStrokeWidth;
121
- this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
122
- this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number);
123
- }
124
- break;
125
- }
126
- });
127
- }
128
- renderTextDecoration(bounds, styles) {
129
- this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.textDecorationColor || styles.color);
130
- // Calculate decoration line thickness
131
- let thickness = 1; // default
132
- if (typeof styles.textDecorationThickness === 'number') {
133
- thickness = styles.textDecorationThickness;
134
- }
135
- else if (styles.textDecorationThickness === 'from-font') {
136
- // Use a reasonable default based on font size
137
- thickness = Math.max(1, Math.floor(styles.fontSize.number * 0.05));
138
- }
139
- // 'auto' uses default thickness of 1
140
- // Calculate underline offset
141
- let underlineOffset = 0;
142
- if (typeof styles.textUnderlineOffset === 'number') {
143
- // It's a pixel value
144
- underlineOffset = styles.textUnderlineOffset;
145
- }
146
- // 'auto' uses default offset of 0
147
- const decorationStyle = styles.textDecorationStyle;
148
- styles.textDecorationLine.forEach((textDecorationLine) => {
149
- let y = 0;
150
- switch (textDecorationLine) {
151
- case 1 /* TEXT_DECORATION_LINE.UNDERLINE */:
152
- y = bounds.top + bounds.height - thickness + underlineOffset;
153
- break;
154
- case 2 /* TEXT_DECORATION_LINE.OVERLINE */:
155
- y = bounds.top;
156
- break;
157
- case 3 /* TEXT_DECORATION_LINE.LINE_THROUGH */:
158
- y = bounds.top + (bounds.height / 2 - thickness / 2);
159
- break;
160
- default:
161
- return;
162
- }
163
- this.drawDecorationLine(bounds.left, y, bounds.width, thickness, decorationStyle);
164
- });
165
- }
166
- drawDecorationLine(x, y, width, thickness, style) {
167
- switch (style) {
168
- case 0 /* TEXT_DECORATION_STYLE.SOLID */:
169
- // Solid line (default)
170
- this.ctx.fillRect(x, y, width, thickness);
171
- break;
172
- case 1 /* TEXT_DECORATION_STYLE.DOUBLE */:
173
- // Double line
174
- const gap = Math.max(1, thickness);
175
- this.ctx.fillRect(x, y, width, thickness);
176
- this.ctx.fillRect(x, y + thickness + gap, width, thickness);
177
- break;
178
- case 2 /* TEXT_DECORATION_STYLE.DOTTED */:
179
- // Dotted line
180
- this.ctx.save();
181
- this.ctx.beginPath();
182
- this.ctx.setLineDash([thickness, thickness * 2]);
183
- this.ctx.lineWidth = thickness;
184
- this.ctx.strokeStyle = this.ctx.fillStyle;
185
- this.ctx.moveTo(x, y + thickness / 2);
186
- this.ctx.lineTo(x + width, y + thickness / 2);
187
- this.ctx.stroke();
188
- this.ctx.restore();
189
- break;
190
- case 3 /* TEXT_DECORATION_STYLE.DASHED */:
191
- // Dashed line
192
- this.ctx.save();
193
- this.ctx.beginPath();
194
- this.ctx.setLineDash([thickness * 3, thickness * 2]);
195
- this.ctx.lineWidth = thickness;
196
- this.ctx.strokeStyle = this.ctx.fillStyle;
197
- this.ctx.moveTo(x, y + thickness / 2);
198
- this.ctx.lineTo(x + width, y + thickness / 2);
199
- this.ctx.stroke();
200
- this.ctx.restore();
201
- break;
202
- case 4 /* TEXT_DECORATION_STYLE.WAVY */:
203
- // Wavy line (approximation using quadratic curves)
204
- this.ctx.save();
205
- this.ctx.beginPath();
206
- this.ctx.lineWidth = thickness;
207
- this.ctx.strokeStyle = this.ctx.fillStyle;
208
- const amplitude = thickness * 2;
209
- const wavelength = thickness * 4;
210
- let currentX = x;
211
- this.ctx.moveTo(currentX, y + thickness / 2);
212
- while (currentX < x + width) {
213
- const nextX = Math.min(currentX + wavelength / 2, x + width);
214
- this.ctx.quadraticCurveTo(currentX + wavelength / 4, y + thickness / 2 - amplitude, nextX, y + thickness / 2);
215
- currentX = nextX;
216
- if (currentX < x + width) {
217
- const nextX2 = Math.min(currentX + wavelength / 2, x + width);
218
- this.ctx.quadraticCurveTo(currentX + wavelength / 4, y + thickness / 2 + amplitude, nextX2, y + thickness / 2);
219
- currentX = nextX2;
220
- }
221
- }
222
- this.ctx.stroke();
223
- this.ctx.restore();
224
- break;
225
- default:
226
- // Fallback to solid
227
- this.ctx.fillRect(x, y, width, thickness);
228
- }
229
- }
230
96
  // Helper method to truncate text and add ellipsis if needed
231
- truncateTextWithEllipsis(text, maxWidth, letterSpacing) {
232
- const ellipsis = '...';
233
- const ellipsisWidth = this.ctx.measureText(ellipsis).width;
234
- if (letterSpacing === 0) {
235
- let truncated = text;
236
- while (this.ctx.measureText(truncated).width + ellipsisWidth > maxWidth && truncated.length > 0) {
237
- truncated = truncated.slice(0, -1);
238
- }
239
- return truncated + ellipsis;
240
- }
241
- else {
242
- const letters = (0, text_1.segmentGraphemes)(text);
243
- let width = ellipsisWidth;
244
- let result = [];
245
- for (const letter of letters) {
246
- const letterWidth = this.ctx.measureText(letter).width + letterSpacing;
247
- if (width + letterWidth > maxWidth) {
248
- break;
249
- }
250
- result.push(letter);
251
- width += letterWidth;
252
- }
253
- return result.join('') + ellipsis;
254
- }
255
- }
256
- createFontStyle(styles) {
257
- const fontVariant = styles.fontVariant
258
- .filter((variant) => variant === 'normal' || variant === 'small-caps')
259
- .join('');
260
- const fontFamily = fixIOSSystemFonts(styles.fontFamily).join(', ');
261
- const fontSize = (0, parser_1.isDimensionToken)(styles.fontSize)
262
- ? `${styles.fontSize.number}${styles.fontSize.unit}`
263
- : `${styles.fontSize.number}px`;
264
- return [
265
- [styles.fontStyle, fontVariant, styles.fontWeight, fontSize, fontFamily].join(' '),
266
- fontFamily,
267
- fontSize
268
- ];
269
- }
270
- async renderTextNode(text, styles, containerBounds) {
271
- const [font] = this.createFontStyle(styles);
272
- this.ctx.font = font;
273
- this.ctx.direction = styles.direction === 1 /* DIRECTION.RTL */ ? 'rtl' : 'ltr';
274
- this.ctx.textAlign = 'left';
275
- this.ctx.textBaseline = 'alphabetic';
276
- const paintOrder = styles.paintOrder;
277
- // Calculate line height for text layout detection (used by both line-clamp and ellipsis)
278
- const lineHeight = styles.fontSize.number * 1.5;
279
- // Check if we need to apply -webkit-line-clamp
280
- // This limits text to a specific number of lines with ellipsis
281
- const shouldApplyLineClamp = styles.webkitLineClamp > 0 &&
282
- (styles.display & 2 /* DISPLAY.BLOCK */) !== 0 &&
283
- styles.overflowY === 1 /* OVERFLOW.HIDDEN */ &&
284
- text.textBounds.length > 0;
285
- if (shouldApplyLineClamp) {
286
- // Group text bounds by lines based on their Y position
287
- const lines = [];
288
- let currentLine = [];
289
- let currentLineTop = text.textBounds[0].bounds.top;
290
- text.textBounds.forEach((tb) => {
291
- // If this text bound is on a different line, start a new line
292
- if (Math.abs(tb.bounds.top - currentLineTop) >= lineHeight * 0.5) {
293
- if (currentLine.length > 0) {
294
- lines.push(currentLine);
295
- }
296
- currentLine = [tb];
297
- currentLineTop = tb.bounds.top;
298
- }
299
- else {
300
- currentLine.push(tb);
301
- }
302
- });
303
- // Don't forget the last line
304
- if (currentLine.length > 0) {
305
- lines.push(currentLine);
306
- }
307
- // Only render up to webkitLineClamp lines
308
- const maxLines = styles.webkitLineClamp;
309
- if (lines.length > maxLines) {
310
- // Render only the first (maxLines - 1) complete lines
311
- for (let i = 0; i < maxLines - 1; i++) {
312
- lines[i].forEach((textBound) => {
313
- this.renderTextBoundWithPaintOrder(textBound, styles, paintOrder);
314
- });
315
- }
316
- // For the last line, truncate with ellipsis
317
- const lastLine = lines[maxLines - 1];
318
- if (lastLine && lastLine.length > 0 && containerBounds) {
319
- const lastLineText = lastLine.map((tb) => tb.text).join('');
320
- const firstBound = lastLine[0];
321
- const availableWidth = containerBounds.width - (firstBound.bounds.left - containerBounds.left);
322
- const truncatedText = this.truncateTextWithEllipsis(lastLineText, availableWidth, styles.letterSpacing);
323
- paintOrder.forEach((paintOrderLayer) => {
324
- switch (paintOrderLayer) {
325
- case 0 /* PAINT_ORDER_LAYER.FILL */:
326
- this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
327
- if (styles.letterSpacing === 0) {
328
- this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
329
- }
330
- else {
331
- const letters = (0, text_1.segmentGraphemes)(truncatedText);
332
- letters.reduce((left, letter) => {
333
- this.ctx.fillText(letter, left, firstBound.bounds.top + styles.fontSize.number);
334
- return left + this.ctx.measureText(letter).width + styles.letterSpacing;
335
- }, firstBound.bounds.left);
336
- }
337
- break;
338
- case 1 /* PAINT_ORDER_LAYER.STROKE */:
339
- if (styles.webkitTextStrokeWidth && truncatedText.trim().length) {
340
- this.ctx.strokeStyle = (0, color_utilities_1.asString)(styles.webkitTextStrokeColor);
341
- this.ctx.lineWidth = styles.webkitTextStrokeWidth;
342
- this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
343
- if (styles.letterSpacing === 0) {
344
- this.ctx.strokeText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
345
- }
346
- else {
347
- const letters = (0, text_1.segmentGraphemes)(truncatedText);
348
- letters.reduce((left, letter) => {
349
- this.ctx.strokeText(letter, left, firstBound.bounds.top + styles.fontSize.number);
350
- return left + this.ctx.measureText(letter).width + styles.letterSpacing;
351
- }, firstBound.bounds.left);
352
- }
353
- }
354
- break;
355
- }
356
- });
357
- }
358
- return; // Don't render anything else
359
- }
360
- // If lines.length <= maxLines, fall through to normal rendering
361
- }
362
- // Check if we need to apply text-overflow: ellipsis
363
- // Issue #203: Only apply ellipsis for single-line text overflow
364
- // Multi-line text truncation (like -webkit-line-clamp) should not be affected
365
- const shouldApplyEllipsis = styles.textOverflow === 1 /* TEXT_OVERFLOW.ELLIPSIS */ &&
366
- containerBounds &&
367
- styles.overflowX === 1 /* OVERFLOW.HIDDEN */ &&
368
- text.textBounds.length > 0;
369
- // Calculate total text width if ellipsis might be needed
370
- let needsEllipsis = false;
371
- let truncatedText = '';
372
- if (shouldApplyEllipsis) {
373
- // Check if all text bounds are on approximately the same line (single-line scenario)
374
- // For multi-line text (like -webkit-line-clamp), textBounds will have different Y positions
375
- const firstTop = text.textBounds[0].bounds.top;
376
- const isSingleLine = text.textBounds.every((tb) => Math.abs(tb.bounds.top - firstTop) < lineHeight * 0.5);
377
- if (isSingleLine) {
378
- // Measure the full text content
379
- // Note: text.textBounds may contain whitespace characters from HTML formatting
380
- // We need to collapse them like the browser does for white-space: nowrap
381
- let fullText = text.textBounds.map((tb) => tb.text).join('');
382
- // Collapse whitespace: replace sequences of whitespace (including newlines) with single spaces
383
- // and trim leading/trailing whitespace
384
- fullText = fullText.replace(/\s+/g, ' ').trim();
385
- const fullTextWidth = this.ctx.measureText(fullText).width;
386
- const availableWidth = containerBounds.width;
387
- if (fullTextWidth > availableWidth) {
388
- needsEllipsis = true;
389
- truncatedText = this.truncateTextWithEllipsis(fullText, availableWidth, styles.letterSpacing);
390
- }
391
- }
392
- }
393
- // If ellipsis is needed, render the truncated text once
394
- if (needsEllipsis) {
395
- const firstBound = text.textBounds[0];
396
- paintOrder.forEach((paintOrderLayer) => {
397
- switch (paintOrderLayer) {
398
- case 0 /* PAINT_ORDER_LAYER.FILL */:
399
- this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
400
- if (styles.letterSpacing === 0) {
401
- this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
402
- }
403
- else {
404
- const letters = (0, text_1.segmentGraphemes)(truncatedText);
405
- letters.reduce((left, letter) => {
406
- this.ctx.fillText(letter, left, firstBound.bounds.top + styles.fontSize.number);
407
- return left + this.ctx.measureText(letter).width + styles.letterSpacing;
408
- }, firstBound.bounds.left);
409
- }
410
- const textShadows = styles.textShadow;
411
- if (textShadows.length && truncatedText.trim().length) {
412
- textShadows
413
- .slice(0)
414
- .reverse()
415
- .forEach((textShadow) => {
416
- this.ctx.shadowColor = (0, color_utilities_1.asString)(textShadow.color);
417
- this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
418
- this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
419
- this.ctx.shadowBlur = textShadow.blur.number;
420
- if (styles.letterSpacing === 0) {
421
- this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
422
- }
423
- else {
424
- const letters = (0, text_1.segmentGraphemes)(truncatedText);
425
- letters.reduce((left, letter) => {
426
- this.ctx.fillText(letter, left, firstBound.bounds.top + styles.fontSize.number);
427
- return left + this.ctx.measureText(letter).width + styles.letterSpacing;
428
- }, firstBound.bounds.left);
429
- }
430
- });
431
- this.ctx.shadowColor = '';
432
- this.ctx.shadowOffsetX = 0;
433
- this.ctx.shadowOffsetY = 0;
434
- this.ctx.shadowBlur = 0;
435
- }
436
- break;
437
- case 1 /* PAINT_ORDER_LAYER.STROKE */:
438
- if (styles.webkitTextStrokeWidth && truncatedText.trim().length) {
439
- this.ctx.strokeStyle = (0, color_utilities_1.asString)(styles.webkitTextStrokeColor);
440
- this.ctx.lineWidth = styles.webkitTextStrokeWidth;
441
- this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
442
- if (styles.letterSpacing === 0) {
443
- this.ctx.strokeText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
444
- }
445
- else {
446
- const letters = (0, text_1.segmentGraphemes)(truncatedText);
447
- letters.reduce((left, letter) => {
448
- this.ctx.strokeText(letter, left, firstBound.bounds.top + styles.fontSize.number);
449
- return left + this.ctx.measureText(letter).width + styles.letterSpacing;
450
- }, firstBound.bounds.left);
451
- }
452
- }
453
- break;
454
- }
455
- });
456
- return;
457
- }
458
- // Normal rendering (no ellipsis needed)
459
- text.textBounds.forEach((text) => {
460
- paintOrder.forEach((paintOrderLayer) => {
461
- switch (paintOrderLayer) {
462
- case 0 /* PAINT_ORDER_LAYER.FILL */:
463
- this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
464
- this.renderTextWithLetterSpacing(text, styles.letterSpacing, styles.fontSize.number);
465
- const textShadows = styles.textShadow;
466
- if (textShadows.length && text.text.trim().length) {
467
- textShadows
468
- .slice(0)
469
- .reverse()
470
- .forEach((textShadow) => {
471
- this.ctx.shadowColor = (0, color_utilities_1.asString)(textShadow.color);
472
- this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
473
- this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
474
- this.ctx.shadowBlur = textShadow.blur.number;
475
- this.renderTextWithLetterSpacing(text, styles.letterSpacing, styles.fontSize.number);
476
- });
477
- this.ctx.shadowColor = '';
478
- this.ctx.shadowOffsetX = 0;
479
- this.ctx.shadowOffsetY = 0;
480
- this.ctx.shadowBlur = 0;
481
- }
482
- if (styles.textDecorationLine.length) {
483
- this.renderTextDecoration(text.bounds, styles);
484
- }
485
- break;
486
- case 1 /* PAINT_ORDER_LAYER.STROKE */:
487
- if (styles.webkitTextStrokeWidth && text.text.trim().length) {
488
- this.ctx.strokeStyle = (0, color_utilities_1.asString)(styles.webkitTextStrokeColor);
489
- this.ctx.lineWidth = styles.webkitTextStrokeWidth;
490
- this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
491
- // Issue #110: Use baseline (fontSize) for consistent positioning with fill
492
- // Previously used text.bounds.height which caused stroke to render too low
493
- const baseline = styles.fontSize.number;
494
- if (styles.letterSpacing === 0) {
495
- this.ctx.strokeText(text.text, text.bounds.left, text.bounds.top + baseline);
496
- }
497
- else {
498
- const letters = (0, text_1.segmentGraphemes)(text.text);
499
- letters.reduce((left, letter) => {
500
- this.ctx.strokeText(letter, left, text.bounds.top + baseline);
501
- return left + this.ctx.measureText(letter).width;
502
- }, text.bounds.left);
503
- }
504
- }
505
- this.ctx.strokeStyle = '';
506
- this.ctx.lineWidth = 0;
507
- this.ctx.lineJoin = 'miter';
508
- break;
509
- }
510
- });
511
- });
512
- }
513
97
  renderReplacedElement(container, curves, image) {
514
98
  const intrinsicWidth = image.naturalWidth || container.intrinsicWidth;
515
99
  const intrinsicHeight = image.naturalHeight || container.intrinsicHeight;
@@ -598,7 +182,7 @@ class CanvasRenderer extends renderer_1.Renderer {
598
182
  }
599
183
  }
600
184
  async renderNodeContent(paint) {
601
- this.applyEffects(paint.getEffects(4 /* EffectTarget.CONTENT */));
185
+ this.effectsRenderer.applyEffects(paint.getEffects(4 /* EffectTarget.CONTENT */));
602
186
  const container = paint.container;
603
187
  const curves = paint.curves;
604
188
  const styles = container.styles;
@@ -606,12 +190,27 @@ class CanvasRenderer extends renderer_1.Renderer {
606
190
  // This matches browser behavior where text-overflow uses the content width
607
191
  const textBounds = (0, box_sizing_1.contentBox)(container);
608
192
  for (const child of container.textNodes) {
609
- await this.renderTextNode(child, styles, textBounds);
193
+ await this.textRenderer.renderTextNode(child, styles, textBounds);
610
194
  }
611
195
  if (container instanceof image_element_container_1.ImageElementContainer) {
612
196
  try {
613
197
  const image = await this.context.cache.match(container.src);
198
+ // Apply image smoothing based on CSS image-rendering property and global options
199
+ const prevSmoothing = this.ctx.imageSmoothingEnabled;
200
+ // CSS image-rendering property overrides global settings
201
+ if (styles.imageRendering === image_rendering_1.IMAGE_RENDERING.PIXELATED ||
202
+ styles.imageRendering === image_rendering_1.IMAGE_RENDERING.CRISP_EDGES) {
203
+ this.context.logger.debug(`Disabling image smoothing for ${container.src} due to CSS image-rendering: ${styles.imageRendering === image_rendering_1.IMAGE_RENDERING.PIXELATED ? 'pixelated' : 'crisp-edges'}`);
204
+ this.ctx.imageSmoothingEnabled = false;
205
+ }
206
+ else if (styles.imageRendering === image_rendering_1.IMAGE_RENDERING.SMOOTH) {
207
+ this.context.logger.debug(`Enabling image smoothing for ${container.src} due to CSS image-rendering: smooth`);
208
+ this.ctx.imageSmoothingEnabled = true;
209
+ }
210
+ // IMAGE_RENDERING.AUTO: keep current global setting
614
211
  this.renderReplacedElement(container, curves, image);
212
+ // Restore previous smoothing state
213
+ this.ctx.imageSmoothingEnabled = prevSmoothing;
615
214
  }
616
215
  catch (e) {
617
216
  this.context.logger.error(`Error loading image ${container.src}`);
@@ -674,7 +273,7 @@ class CanvasRenderer extends renderer_1.Renderer {
674
273
  }
675
274
  }
676
275
  if (isTextInputElement(container) && container.value.length) {
677
- const [font, fontFamily, fontSize] = this.createFontStyle(styles);
276
+ const [font, fontFamily, fontSize] = this.textRenderer.createFontStyle(styles);
678
277
  const { baseline } = this.fontMetrics.getMetrics(fontFamily, fontSize);
679
278
  this.ctx.font = font;
680
279
  // Fix for Issue #92: Use placeholder color when rendering placeholder text
@@ -710,7 +309,7 @@ class CanvasRenderer extends renderer_1.Renderer {
710
309
  new vector_1.Vector(bounds.left, bounds.top + bounds.height)
711
310
  ]);
712
311
  this.ctx.clip();
713
- this.renderTextWithLetterSpacing(new text_1.TextBounds(container.value, textBounds), styles.letterSpacing, baseline);
312
+ this.textRenderer.renderTextWithLetterSpacing(new text_1.TextBounds(container.value, textBounds), styles.letterSpacing, baseline);
714
313
  this.ctx.restore();
715
314
  this.ctx.textBaseline = 'alphabetic';
716
315
  this.ctx.textAlign = 'left';
@@ -731,13 +330,13 @@ class CanvasRenderer extends renderer_1.Renderer {
731
330
  }
732
331
  }
733
332
  else if (paint.listValue && container.styles.listStyleType !== -1 /* LIST_STYLE_TYPE.NONE */) {
734
- const [font] = this.createFontStyle(styles);
333
+ const [font] = this.textRenderer.createFontStyle(styles);
735
334
  this.ctx.font = font;
736
335
  this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
737
336
  this.ctx.textBaseline = 'middle';
738
337
  this.ctx.textAlign = 'right';
739
338
  const bounds = new bounds_1.Bounds(container.bounds.left, container.bounds.top + (0, length_percentage_1.getAbsoluteValue)(container.styles.paddingTop, container.bounds.width), container.bounds.width, (0, line_height_1.computeLineHeight)(styles.lineHeight, styles.fontSize.number) / 2 + 1);
740
- this.renderTextWithLetterSpacing(new text_1.TextBounds(paint.listValue, bounds), styles.letterSpacing, (0, line_height_1.computeLineHeight)(styles.lineHeight, styles.fontSize.number) / 2 + 2);
339
+ this.textRenderer.renderTextWithLetterSpacing(new text_1.TextBounds(paint.listValue, bounds), styles.letterSpacing, (0, line_height_1.computeLineHeight)(styles.lineHeight, styles.fontSize.number) / 2 + 2);
741
340
  this.ctx.textBaseline = 'bottom';
742
341
  this.ctx.textAlign = 'left';
743
342
  }
@@ -824,128 +423,8 @@ class CanvasRenderer extends renderer_1.Renderer {
824
423
  }
825
424
  });
826
425
  }
827
- renderRepeat(path, pattern, offsetX, offsetY) {
828
- this.path(path);
829
- this.ctx.fillStyle = pattern;
830
- this.ctx.translate(offsetX, offsetY);
831
- this.ctx.fill();
832
- this.ctx.translate(-offsetX, -offsetY);
833
- }
834
- resizeImage(image, width, height) {
835
- // https://github.com/niklasvh/html2canvas/pull/2911
836
- // if (image.width === width && image.height === height) {
837
- // return image;
838
- // }
839
- const ownerDocument = this.canvas.ownerDocument ?? document;
840
- const canvas = ownerDocument.createElement('canvas');
841
- canvas.width = Math.max(1, width);
842
- canvas.height = Math.max(1, height);
843
- const ctx = canvas.getContext('2d');
844
- ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height);
845
- return canvas;
846
- }
847
- async renderBackgroundImage(container) {
848
- let index = container.styles.backgroundImage.length - 1;
849
- for (const backgroundImage of container.styles.backgroundImage.slice(0).reverse()) {
850
- if (backgroundImage.type === 0 /* CSSImageType.URL */) {
851
- let image;
852
- const url = backgroundImage.url;
853
- try {
854
- image = await this.context.cache.match(url);
855
- }
856
- catch (e) {
857
- this.context.logger.error(`Error loading background-image ${url}`);
858
- }
859
- if (image) {
860
- const imageWidth = isNaN(image.width) || image.width === 0 ? 1 : image.width;
861
- const imageHeight = isNaN(image.height) || image.height === 0 ? 1 : image.height;
862
- const [path, x, y, width, height] = (0, background_1.calculateBackgroundRendering)(container, index, [
863
- imageWidth,
864
- imageHeight,
865
- imageWidth / imageHeight
866
- ]);
867
- const pattern = this.ctx.createPattern(this.resizeImage(image, width, height), 'repeat');
868
- this.renderRepeat(path, pattern, x, y);
869
- }
870
- }
871
- else if ((0, image_1.isLinearGradient)(backgroundImage)) {
872
- const [path, x, y, width, height] = (0, background_1.calculateBackgroundRendering)(container, index, [null, null, null]);
873
- const [lineLength, x0, x1, y0, y1] = (0, gradient_1.calculateGradientDirection)(backgroundImage.angle, width, height);
874
- const canvas = document.createElement('canvas');
875
- canvas.width = width;
876
- canvas.height = height;
877
- const ctx = canvas.getContext('2d');
878
- const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
879
- (0, gradient_1.processColorStops)(backgroundImage.stops, lineLength || 1).forEach((colorStop) => gradient.addColorStop(colorStop.stop, (0, color_utilities_1.asString)(colorStop.color)));
880
- ctx.fillStyle = gradient;
881
- ctx.fillRect(0, 0, width, height);
882
- if (width > 0 && height > 0) {
883
- const pattern = this.ctx.createPattern(canvas, 'repeat');
884
- this.renderRepeat(path, pattern, x, y);
885
- }
886
- }
887
- else if ((0, image_1.isRadialGradient)(backgroundImage)) {
888
- const [path, left, top, width, height] = (0, background_1.calculateBackgroundRendering)(container, index, [
889
- null,
890
- null,
891
- null
892
- ]);
893
- const position = backgroundImage.position.length === 0 ? [length_percentage_1.FIFTY_PERCENT] : backgroundImage.position;
894
- const x = (0, length_percentage_1.getAbsoluteValue)(position[0], width);
895
- const y = (0, length_percentage_1.getAbsoluteValue)(position[position.length - 1], height);
896
- let [rx, ry] = (0, gradient_1.calculateRadius)(backgroundImage, x, y, width, height);
897
- // Handle edge case where radial gradient size is 0
898
- // Use a minimum value of 0.01 to ensure gradient is still rendered
899
- if (rx === 0 || ry === 0) {
900
- rx = Math.max(rx, 0.01);
901
- ry = Math.max(ry, 0.01);
902
- }
903
- if (rx > 0 && ry > 0) {
904
- const radialGradient = this.ctx.createRadialGradient(left + x, top + y, 0, left + x, top + y, rx);
905
- (0, gradient_1.processColorStops)(backgroundImage.stops, rx * 2).forEach((colorStop) => radialGradient.addColorStop(colorStop.stop, (0, color_utilities_1.asString)(colorStop.color)));
906
- this.path(path);
907
- this.ctx.fillStyle = radialGradient;
908
- if (rx !== ry) {
909
- // transforms for elliptical radial gradient
910
- const midX = container.bounds.left + 0.5 * container.bounds.width;
911
- const midY = container.bounds.top + 0.5 * container.bounds.height;
912
- const f = ry / rx;
913
- const invF = 1 / f;
914
- this.ctx.save();
915
- this.ctx.translate(midX, midY);
916
- this.ctx.transform(1, 0, 0, f, 0, 0);
917
- this.ctx.translate(-midX, -midY);
918
- this.ctx.fillRect(left, invF * (top - midY) + midY, width, height * invF);
919
- this.ctx.restore();
920
- }
921
- else {
922
- this.ctx.fill();
923
- }
924
- }
925
- }
926
- index--;
927
- }
928
- }
929
- async renderSolidBorder(color, side, curvePoints) {
930
- this.path((0, border_1.parsePathForBorder)(curvePoints, side));
931
- this.ctx.fillStyle = (0, color_utilities_1.asString)(color);
932
- this.ctx.fill();
933
- }
934
- async renderDoubleBorder(color, width, side, curvePoints) {
935
- if (width < 3) {
936
- await this.renderSolidBorder(color, side, curvePoints);
937
- return;
938
- }
939
- const outerPaths = (0, border_1.parsePathForBorderDoubleOuter)(curvePoints, side);
940
- this.path(outerPaths);
941
- this.ctx.fillStyle = (0, color_utilities_1.asString)(color);
942
- this.ctx.fill();
943
- const innerPaths = (0, border_1.parsePathForBorderDoubleInner)(curvePoints, side);
944
- this.path(innerPaths);
945
- this.ctx.fill();
946
- }
947
426
  async renderNodeBackgroundAndBorders(paint) {
948
- this.applyEffects(paint.getEffects(2 /* EffectTarget.BACKGROUND_BORDERS */));
427
+ this.effectsRenderer.applyEffects(paint.getEffects(2 /* EffectTarget.BACKGROUND_BORDERS */));
949
428
  const styles = paint.container.styles;
950
429
  const hasBackground = !(0, color_utilities_1.isTransparent)(styles.backgroundColor) || styles.backgroundImage.length;
951
430
  const borders = [
@@ -963,7 +442,7 @@ class CanvasRenderer extends renderer_1.Renderer {
963
442
  this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.backgroundColor);
964
443
  this.ctx.fill();
965
444
  }
966
- await this.renderBackgroundImage(paint.container);
445
+ await this.backgroundRenderer.renderBackgroundImage(paint.container);
967
446
  this.ctx.restore();
968
447
  styles.boxShadow
969
448
  .slice(0)
@@ -996,121 +475,21 @@ class CanvasRenderer extends renderer_1.Renderer {
996
475
  for (const border of borders) {
997
476
  if (border.style !== 0 /* BORDER_STYLE.NONE */ && !(0, color_utilities_1.isTransparent)(border.color) && border.width > 0) {
998
477
  if (border.style === 2 /* BORDER_STYLE.DASHED */) {
999
- await this.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 2 /* BORDER_STYLE.DASHED */);
478
+ await this.borderRenderer.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 2 /* BORDER_STYLE.DASHED */);
1000
479
  }
1001
480
  else if (border.style === 3 /* BORDER_STYLE.DOTTED */) {
1002
- await this.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 3 /* BORDER_STYLE.DOTTED */);
481
+ await this.borderRenderer.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 3 /* BORDER_STYLE.DOTTED */);
1003
482
  }
1004
483
  else if (border.style === 4 /* BORDER_STYLE.DOUBLE */) {
1005
- await this.renderDoubleBorder(border.color, border.width, side, paint.curves);
484
+ await this.borderRenderer.renderDoubleBorder(border.color, border.width, side, paint.curves);
1006
485
  }
1007
486
  else {
1008
- await this.renderSolidBorder(border.color, side, paint.curves);
487
+ await this.borderRenderer.renderSolidBorder(border.color, side, paint.curves);
1009
488
  }
1010
489
  }
1011
490
  side++;
1012
491
  }
1013
492
  }
1014
- async renderDashedDottedBorder(color, width, side, curvePoints, style) {
1015
- this.ctx.save();
1016
- const strokePaths = (0, border_1.parsePathForBorderStroke)(curvePoints, side);
1017
- const boxPaths = (0, border_1.parsePathForBorder)(curvePoints, side);
1018
- if (style === 2 /* BORDER_STYLE.DASHED */) {
1019
- this.path(boxPaths);
1020
- this.ctx.clip();
1021
- }
1022
- let startX, startY, endX, endY;
1023
- if ((0, bezier_curve_1.isBezierCurve)(boxPaths[0])) {
1024
- startX = boxPaths[0].start.x;
1025
- startY = boxPaths[0].start.y;
1026
- }
1027
- else {
1028
- startX = boxPaths[0].x;
1029
- startY = boxPaths[0].y;
1030
- }
1031
- if ((0, bezier_curve_1.isBezierCurve)(boxPaths[1])) {
1032
- endX = boxPaths[1].end.x;
1033
- endY = boxPaths[1].end.y;
1034
- }
1035
- else {
1036
- endX = boxPaths[1].x;
1037
- endY = boxPaths[1].y;
1038
- }
1039
- let length;
1040
- if (side === 0 || side === 2) {
1041
- length = Math.abs(startX - endX);
1042
- }
1043
- else {
1044
- length = Math.abs(startY - endY);
1045
- }
1046
- this.ctx.beginPath();
1047
- if (style === 3 /* BORDER_STYLE.DOTTED */) {
1048
- this.formatPath(strokePaths);
1049
- }
1050
- else {
1051
- this.formatPath(boxPaths.slice(0, 2));
1052
- }
1053
- let dashLength = width < 3 ? width * 3 : width * 2;
1054
- let spaceLength = width < 3 ? width * 2 : width;
1055
- if (style === 3 /* BORDER_STYLE.DOTTED */) {
1056
- dashLength = width;
1057
- spaceLength = width;
1058
- }
1059
- let useLineDash = true;
1060
- if (length <= dashLength * 2) {
1061
- useLineDash = false;
1062
- }
1063
- else if (length <= dashLength * 2 + spaceLength) {
1064
- const multiplier = length / (2 * dashLength + spaceLength);
1065
- dashLength *= multiplier;
1066
- spaceLength *= multiplier;
1067
- }
1068
- else {
1069
- const numberOfDashes = Math.floor((length + spaceLength) / (dashLength + spaceLength));
1070
- const minSpace = (length - numberOfDashes * dashLength) / (numberOfDashes - 1);
1071
- const maxSpace = (length - (numberOfDashes + 1) * dashLength) / numberOfDashes;
1072
- spaceLength =
1073
- maxSpace <= 0 || Math.abs(spaceLength - minSpace) < Math.abs(spaceLength - maxSpace)
1074
- ? minSpace
1075
- : maxSpace;
1076
- }
1077
- if (useLineDash) {
1078
- if (style === 3 /* BORDER_STYLE.DOTTED */) {
1079
- this.ctx.setLineDash([0, dashLength + spaceLength]);
1080
- }
1081
- else {
1082
- this.ctx.setLineDash([dashLength, spaceLength]);
1083
- }
1084
- }
1085
- if (style === 3 /* BORDER_STYLE.DOTTED */) {
1086
- this.ctx.lineCap = 'round';
1087
- this.ctx.lineWidth = width;
1088
- }
1089
- else {
1090
- this.ctx.lineWidth = width * 2 + 1.1;
1091
- }
1092
- this.ctx.strokeStyle = (0, color_utilities_1.asString)(color);
1093
- this.ctx.stroke();
1094
- this.ctx.setLineDash([]);
1095
- // dashed round edge gap
1096
- if (style === 2 /* BORDER_STYLE.DASHED */) {
1097
- if ((0, bezier_curve_1.isBezierCurve)(boxPaths[0])) {
1098
- const path1 = boxPaths[3];
1099
- const path2 = boxPaths[0];
1100
- this.ctx.beginPath();
1101
- this.formatPath([new vector_1.Vector(path1.end.x, path1.end.y), new vector_1.Vector(path2.start.x, path2.start.y)]);
1102
- this.ctx.stroke();
1103
- }
1104
- if ((0, bezier_curve_1.isBezierCurve)(boxPaths[1])) {
1105
- const path1 = boxPaths[1];
1106
- const path2 = boxPaths[2];
1107
- this.ctx.beginPath();
1108
- this.formatPath([new vector_1.Vector(path1.end.x, path1.end.y), new vector_1.Vector(path2.start.x, path2.start.y)]);
1109
- this.ctx.stroke();
1110
- }
1111
- }
1112
- this.ctx.restore();
1113
- }
1114
493
  async render(element) {
1115
494
  if (this.options.backgroundColor) {
1116
495
  this.ctx.fillStyle = (0, color_utilities_1.asString)(this.options.backgroundColor);
@@ -1118,7 +497,7 @@ class CanvasRenderer extends renderer_1.Renderer {
1118
497
  }
1119
498
  const stack = (0, stacking_context_1.parseStackingContexts)(element);
1120
499
  await this.renderStack(stack);
1121
- this.applyEffects([]);
500
+ this.effectsRenderer.applyEffects([]);
1122
501
  return this.canvas;
1123
502
  }
1124
503
  }
@@ -1157,11 +536,4 @@ const canvasTextAlign = (textAlign) => {
1157
536
  return 'left';
1158
537
  }
1159
538
  };
1160
- // see https://github.com/niklasvh/html2canvas/pull/2645
1161
- const iOSBrokenFonts = ['-apple-system', 'system-ui'];
1162
- const fixIOSSystemFonts = (fontFamilies) => {
1163
- return /iPhone OS 15_(0|1)/.test(window.navigator.userAgent)
1164
- ? fontFamilies.filter((fontFamily) => iOSBrokenFonts.indexOf(fontFamily) === -1)
1165
- : fontFamilies;
1166
- };
1167
539
  //# sourceMappingURL=canvas-renderer.js.map