html2canvas-pro 2.1.0 → 2.1.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 (186) hide show
  1. package/dist/html2canvas-pro.esm.js +21 -7
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +21 -7
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +3 -3
  6. package/dist/lib/core/cache-storage.js +2 -2
  7. package/dist/lib/core/features.js +2 -2
  8. package/dist/lib/render/canvas/background-renderer.js +6 -0
  9. package/dist/lib/render/canvas/canvas-renderer.js +5 -1
  10. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  11. package/package.json +3 -11
  12. package/dist/lib/invariant.js +0 -9
  13. package/dist/types/invariant.d.ts +0 -1
  14. package/src/__tests__/index.ts +0 -99
  15. package/src/config.ts +0 -107
  16. package/src/core/__mocks__/cache-storage.ts +0 -1
  17. package/src/core/__mocks__/context.ts +0 -19
  18. package/src/core/__mocks__/features.ts +0 -8
  19. package/src/core/__mocks__/logger.ts +0 -17
  20. package/src/core/__tests__/cache-storage.test.ts +0 -205
  21. package/src/core/__tests__/cache-storage.ts +0 -278
  22. package/src/core/__tests__/logger.ts +0 -29
  23. package/src/core/__tests__/validator.ts +0 -359
  24. package/src/core/bitwise.ts +0 -1
  25. package/src/core/cache-storage.ts +0 -315
  26. package/src/core/context.ts +0 -31
  27. package/src/core/debugger.ts +0 -32
  28. package/src/core/features.ts +0 -222
  29. package/src/core/logger.ts +0 -64
  30. package/src/core/origin-checker.ts +0 -57
  31. package/src/core/performance-monitor.ts +0 -241
  32. package/src/core/render-element.ts +0 -272
  33. package/src/core/util.ts +0 -1
  34. package/src/core/validator.ts +0 -593
  35. package/src/css/index.ts +0 -427
  36. package/src/css/layout/__mocks__/bounds.ts +0 -6
  37. package/src/css/layout/bounds.ts +0 -79
  38. package/src/css/layout/text.ts +0 -161
  39. package/src/css/property-descriptor.ts +0 -49
  40. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  41. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  42. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  43. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  44. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  45. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  46. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  47. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  48. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  49. package/src/css/property-descriptors/background-clip.ts +0 -30
  50. package/src/css/property-descriptors/background-color.ts +0 -9
  51. package/src/css/property-descriptors/background-image.ts +0 -27
  52. package/src/css/property-descriptors/background-origin.ts +0 -31
  53. package/src/css/property-descriptors/background-position.ts +0 -38
  54. package/src/css/property-descriptors/background-repeat.ts +0 -44
  55. package/src/css/property-descriptors/background-size.ts +0 -27
  56. package/src/css/property-descriptors/border-color.ts +0 -13
  57. package/src/css/property-descriptors/border-radius.ts +0 -19
  58. package/src/css/property-descriptors/border-style.ts +0 -34
  59. package/src/css/property-descriptors/border-width.ts +0 -20
  60. package/src/css/property-descriptors/box-shadow.ts +0 -60
  61. package/src/css/property-descriptors/clip-path.ts +0 -271
  62. package/src/css/property-descriptors/color.ts +0 -9
  63. package/src/css/property-descriptors/content.ts +0 -26
  64. package/src/css/property-descriptors/counter-increment.ts +0 -43
  65. package/src/css/property-descriptors/counter-reset.ts +0 -36
  66. package/src/css/property-descriptors/direction.ts +0 -23
  67. package/src/css/property-descriptors/display.ts +0 -117
  68. package/src/css/property-descriptors/duration.ts +0 -14
  69. package/src/css/property-descriptors/float.ts +0 -29
  70. package/src/css/property-descriptors/font-family.ts +0 -38
  71. package/src/css/property-descriptors/font-size.ts +0 -9
  72. package/src/css/property-descriptors/font-style.ts +0 -25
  73. package/src/css/property-descriptors/font-variant.ts +0 -12
  74. package/src/css/property-descriptors/font-weight.ts +0 -26
  75. package/src/css/property-descriptors/image-rendering.ts +0 -33
  76. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  77. package/src/css/property-descriptors/line-break.ts +0 -22
  78. package/src/css/property-descriptors/line-height.ts +0 -22
  79. package/src/css/property-descriptors/list-style-image.ts +0 -19
  80. package/src/css/property-descriptors/list-style-position.ts +0 -22
  81. package/src/css/property-descriptors/list-style-type.ts +0 -179
  82. package/src/css/property-descriptors/margin.ts +0 -13
  83. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  84. package/src/css/property-descriptors/object-fit.ts +0 -39
  85. package/src/css/property-descriptors/opacity.ts +0 -15
  86. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  87. package/src/css/property-descriptors/overflow.ts +0 -34
  88. package/src/css/property-descriptors/padding.ts +0 -14
  89. package/src/css/property-descriptors/paint-order.ts +0 -42
  90. package/src/css/property-descriptors/position.ts +0 -30
  91. package/src/css/property-descriptors/quotes.ts +0 -57
  92. package/src/css/property-descriptors/rotate.ts +0 -34
  93. package/src/css/property-descriptors/text-align.ts +0 -26
  94. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  95. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  96. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  97. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  98. package/src/css/property-descriptors/text-overflow.ts +0 -23
  99. package/src/css/property-descriptors/text-shadow.ts +0 -52
  100. package/src/css/property-descriptors/text-transform.ts +0 -27
  101. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  102. package/src/css/property-descriptors/transform-origin.ts +0 -29
  103. package/src/css/property-descriptors/transform.ts +0 -74
  104. package/src/css/property-descriptors/visibility.ts +0 -25
  105. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  106. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  107. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  108. package/src/css/property-descriptors/word-break.ts +0 -25
  109. package/src/css/property-descriptors/writing-mode.ts +0 -37
  110. package/src/css/property-descriptors/z-index.ts +0 -27
  111. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  112. package/src/css/syntax/parser.ts +0 -188
  113. package/src/css/syntax/tokenizer.ts +0 -822
  114. package/src/css/type-descriptor.ts +0 -7
  115. package/src/css/types/__tests__/color-tests.ts +0 -147
  116. package/src/css/types/__tests__/image-tests.ts +0 -239
  117. package/src/css/types/angle.ts +0 -86
  118. package/src/css/types/color-math.ts +0 -22
  119. package/src/css/types/color-spaces/a98.ts +0 -86
  120. package/src/css/types/color-spaces/p3.ts +0 -92
  121. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  122. package/src/css/types/color-spaces/rec2020.ts +0 -90
  123. package/src/css/types/color-spaces/srgb.ts +0 -87
  124. package/src/css/types/color-utilities.ts +0 -452
  125. package/src/css/types/color.ts +0 -485
  126. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  127. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  128. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  129. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  130. package/src/css/types/functions/counter.ts +0 -511
  131. package/src/css/types/functions/gradient.ts +0 -206
  132. package/src/css/types/functions/linear-gradient.ts +0 -28
  133. package/src/css/types/functions/radial-gradient.ts +0 -101
  134. package/src/css/types/image.ts +0 -120
  135. package/src/css/types/index.ts +0 -1
  136. package/src/css/types/length-percentage.ts +0 -137
  137. package/src/css/types/length.ts +0 -7
  138. package/src/css/types/time.ts +0 -20
  139. package/src/dom/__mocks__/document-cloner.ts +0 -22
  140. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  141. package/src/dom/__tests__/element-container.test.ts +0 -129
  142. package/src/dom/document-cloner.ts +0 -929
  143. package/src/dom/dom-normalizer.ts +0 -133
  144. package/src/dom/element-container.ts +0 -75
  145. package/src/dom/elements/li-element-container.ts +0 -10
  146. package/src/dom/elements/ol-element-container.ts +0 -12
  147. package/src/dom/elements/select-element-container.ts +0 -10
  148. package/src/dom/elements/textarea-element-container.ts +0 -9
  149. package/src/dom/node-parser.ts +0 -177
  150. package/src/dom/node-type-guards.ts +0 -70
  151. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  152. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  153. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  154. package/src/dom/replaced-elements/index.ts +0 -5
  155. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  156. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  157. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  158. package/src/dom/text-container.ts +0 -42
  159. package/src/global.d.ts +0 -19
  160. package/src/index.ts +0 -82
  161. package/src/invariant.ts +0 -5
  162. package/src/options.ts +0 -55
  163. package/src/render/__tests__/object-fit.test.ts +0 -85
  164. package/src/render/background.ts +0 -298
  165. package/src/render/bezier-curve.ts +0 -47
  166. package/src/render/border.ts +0 -165
  167. package/src/render/bound-curves.ts +0 -388
  168. package/src/render/box-sizing.ts +0 -31
  169. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  170. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  171. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  172. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  173. package/src/render/canvas/background-renderer.ts +0 -271
  174. package/src/render/canvas/border-renderer.ts +0 -224
  175. package/src/render/canvas/canvas-path.ts +0 -31
  176. package/src/render/canvas/canvas-renderer.ts +0 -641
  177. package/src/render/canvas/effects-renderer.ts +0 -130
  178. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  179. package/src/render/canvas/text-renderer.ts +0 -700
  180. package/src/render/effects.ts +0 -75
  181. package/src/render/font-metrics.ts +0 -72
  182. package/src/render/object-fit.ts +0 -100
  183. package/src/render/path.ts +0 -37
  184. package/src/render/renderer-interface.ts +0 -28
  185. package/src/render/stacking-context.ts +0 -386
  186. package/src/render/vector.ts +0 -19
@@ -1,641 +0,0 @@
1
- import { ElementPaint, parseStackingContexts, StackingContext } from '../stacking-context';
2
- import { Color } from '../../css/types/color';
3
- import { asString, isTransparent } from '../../css/types/color-utilities';
4
- import { ElementContainer } from '../../dom/element-container';
5
- import { BORDER_STYLE } from '../../css/property-descriptors/border-style';
6
- import { Path, transformPath } from '../path';
7
- import { BACKGROUND_CLIP } from '../../css/property-descriptors/background-clip';
8
- import { BoundCurves, calculateBorderBoxPath, calculateContentBoxPath, calculatePaddingBoxPath } from '../bound-curves';
9
- import { Vector } from '../vector';
10
- import { CSSImageType, CSSURLImage } from '../../css/types/image';
11
- import { getBackgroundValueForIndex } from '../background';
12
- import { TextBounds } from '../../css/layout/text';
13
- import { ImageElementContainer } from '../../dom/replaced-elements/image-element-container';
14
- import { contentBox } from '../box-sizing';
15
- import { CanvasElementContainer } from '../../dom/replaced-elements/canvas-element-container';
16
- import { SVGElementContainer } from '../../dom/replaced-elements/svg-element-container';
17
- import { ReplacedElementContainer } from '../../dom/replaced-elements';
18
- import { EffectTarget } from '../effects';
19
- import { contains } from '../../core/bitwise';
20
- import { getAbsoluteValue } from '../../css/types/length-percentage';
21
- import { FontMetrics } from '../font-metrics';
22
- import { DISPLAY } from '../../css/property-descriptors/display';
23
- import { Bounds } from '../../css/layout/bounds';
24
- import { IMAGE_RENDERING } from '../../css/property-descriptors/image-rendering';
25
- import { LIST_STYLE_TYPE } from '../../css/property-descriptors/list-style-type';
26
- import { computeLineHeight } from '../../css/property-descriptors/line-height';
27
- import {
28
- CHECKBOX,
29
- INPUT_COLOR,
30
- PLACEHOLDER_COLOR,
31
- InputElementContainer,
32
- RADIO
33
- } from '../../dom/replaced-elements/input-element-container';
34
- import { TEXT_ALIGN } from '../../css/property-descriptors/text-align';
35
- import { TextareaElementContainer } from '../../dom/elements/textarea-element-container';
36
- import { SelectElementContainer } from '../../dom/elements/select-element-container';
37
- import { IFrameElementContainer } from '../../dom/replaced-elements/iframe-element-container';
38
- import { Context } from '../../core/context';
39
- import { BackgroundRenderer } from './background-renderer';
40
- import { BorderRenderer } from './border-renderer';
41
- import { EffectsRenderer } from './effects-renderer';
42
- import { TextRenderer } from './text-renderer';
43
- import { createCanvasPath, formatCanvasPath } from './canvas-path';
44
- import { calculateObjectFitRendering } from '../object-fit';
45
-
46
- export type RenderConfigurations = RenderOptions & {
47
- backgroundColor: Color | null;
48
- signal?: AbortSignal;
49
- /**
50
- * Enable/disable image smoothing (anti-aliasing).
51
- * When disabled, images are rendered with pixel-perfect sharpness (no interpolation).
52
- * CSS `image-rendering` property on individual elements takes precedence.
53
- * @default browser default (usually true)
54
- */
55
- imageSmoothing?: boolean;
56
- /**
57
- * Image smoothing quality level when imageSmoothing is enabled.
58
- * Higher quality may be slower for large images.
59
- * Only supported in modern browsers (Chrome 54+, Firefox 94+, Safari 17+).
60
- * Falls back gracefully in older browsers.
61
- * @default browser default
62
- */
63
- imageSmoothingQuality?: 'low' | 'medium' | 'high';
64
- };
65
-
66
- export interface RenderOptions {
67
- scale: number;
68
- canvas?: HTMLCanvasElement;
69
- x: number;
70
- y: number;
71
- width: number;
72
- height: number;
73
- }
74
-
75
- const MASK_OFFSET = 10000;
76
-
77
- export class CanvasRenderer {
78
- canvas: HTMLCanvasElement;
79
- ctx: CanvasRenderingContext2D;
80
- private readonly context: Context;
81
- private readonly options: RenderConfigurations;
82
- private readonly fontMetrics: FontMetrics;
83
- private readonly backgroundRenderer: BackgroundRenderer;
84
- private readonly borderRenderer: BorderRenderer;
85
- private readonly effectsRenderer: EffectsRenderer;
86
- private readonly textRenderer: TextRenderer;
87
-
88
- constructor(context: Context, options: RenderConfigurations) {
89
- this.context = context;
90
- this.options = options;
91
- this.canvas = options.canvas ? options.canvas : document.createElement('canvas');
92
- this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
93
- if (!options.canvas) {
94
- this.canvas.width = Math.floor(options.width * options.scale);
95
- this.canvas.height = Math.floor(options.height * options.scale);
96
- this.canvas.style.width = `${options.width}px`;
97
- this.canvas.style.height = `${options.height}px`;
98
- }
99
- this.fontMetrics = new FontMetrics(document);
100
- this.ctx.scale(this.options.scale, this.options.scale);
101
- this.ctx.translate(-options.x, -options.y);
102
- this.ctx.textBaseline = 'bottom';
103
-
104
- // Set image smoothing options
105
- if (options.imageSmoothing !== undefined) {
106
- this.ctx.imageSmoothingEnabled = options.imageSmoothing;
107
- }
108
- if (options.imageSmoothingQuality) {
109
- this.ctx.imageSmoothingQuality = options.imageSmoothingQuality;
110
- }
111
-
112
- // Initialize specialized renderers
113
- this.backgroundRenderer = new BackgroundRenderer({
114
- ctx: this.ctx,
115
- context: this.context,
116
- canvas: this.canvas,
117
- options: {
118
- width: options.width,
119
- height: options.height,
120
- scale: options.scale
121
- }
122
- });
123
-
124
- this.borderRenderer = new BorderRenderer(
125
- { ctx: this.ctx },
126
- {
127
- path: (paths) => this.path(paths),
128
- formatPath: (paths) => this.formatPath(paths)
129
- }
130
- );
131
-
132
- this.effectsRenderer = new EffectsRenderer({ ctx: this.ctx }, { path: (paths) => this.path(paths) });
133
-
134
- this.textRenderer = new TextRenderer({
135
- ctx: this.ctx,
136
- context: this.context,
137
- options: { scale: options.scale }
138
- });
139
-
140
- this.context.logger.debug(
141
- `Canvas renderer initialized (${options.width}x${options.height}) with scale ${options.scale}`
142
- );
143
- }
144
-
145
- async renderStack(stack: StackingContext): Promise<void> {
146
- const styles = stack.element.container.styles;
147
- if (styles.isVisible()) {
148
- await this.renderStackContent(stack);
149
- }
150
- }
151
-
152
- async renderNode(paint: ElementPaint): Promise<void> {
153
- if (paint.container.debugRender) {
154
- debugger;
155
- }
156
-
157
- if (paint.container.styles.isVisible()) {
158
- await this.renderNodeBackgroundAndBorders(paint);
159
- await this.renderNodeContent(paint);
160
- }
161
- }
162
-
163
- /**
164
- * Helper method to render text with paint order support
165
- * Reduces code duplication in line-clamp and normal rendering
166
- */
167
-
168
- // Helper method to truncate text and add ellipsis if needed
169
-
170
- renderReplacedElement(
171
- container: ReplacedElementContainer,
172
- curves: BoundCurves,
173
- image: HTMLImageElement | HTMLCanvasElement
174
- ): void {
175
- const intrinsicWidth = (image as HTMLImageElement).naturalWidth || container.intrinsicWidth;
176
- const intrinsicHeight = (image as HTMLImageElement).naturalHeight || container.intrinsicHeight;
177
- if (image && intrinsicWidth > 0 && intrinsicHeight > 0) {
178
- const box = contentBox(container);
179
- const path = calculatePaddingBoxPath(curves);
180
- this.path(path);
181
- this.ctx.save();
182
- this.ctx.clip();
183
- const { sx, sy, sw, sh, dx, dy, dw, dh } = calculateObjectFitRendering(
184
- intrinsicWidth,
185
- intrinsicHeight,
186
- box,
187
- container.styles.objectFit
188
- );
189
- this.ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
190
- this.ctx.restore();
191
- }
192
- }
193
-
194
- async renderNodeContent(paint: ElementPaint): Promise<void> {
195
- this.effectsRenderer.applyEffects(paint.getEffects(EffectTarget.CONTENT));
196
- const container = paint.container;
197
- const curves = paint.curves;
198
- const styles = container.styles;
199
- // Use content box for text overflow calculation (excludes padding and border)
200
- // This matches browser behavior where text-overflow uses the content width
201
- const textBounds = contentBox(container);
202
- for (const child of container.textNodes) {
203
- await this.textRenderer.renderTextNode(child, styles, textBounds);
204
- }
205
-
206
- if (container instanceof ImageElementContainer) {
207
- try {
208
- const image = await this.context.cache.match(container.src);
209
-
210
- // Apply image smoothing based on CSS image-rendering property and global options
211
- const prevSmoothing = this.ctx.imageSmoothingEnabled;
212
-
213
- // CSS image-rendering property overrides global settings
214
- if (
215
- styles.imageRendering === IMAGE_RENDERING.PIXELATED ||
216
- styles.imageRendering === IMAGE_RENDERING.CRISP_EDGES
217
- ) {
218
- this.context.logger.debug(
219
- `Disabling image smoothing for ${container.src} due to CSS image-rendering: ${styles.imageRendering === IMAGE_RENDERING.PIXELATED ? 'pixelated' : 'crisp-edges'}`
220
- );
221
- this.ctx.imageSmoothingEnabled = false;
222
- } else if (styles.imageRendering === IMAGE_RENDERING.SMOOTH) {
223
- this.context.logger.debug(
224
- `Enabling image smoothing for ${container.src} due to CSS image-rendering: smooth`
225
- );
226
- this.ctx.imageSmoothingEnabled = true;
227
- }
228
- // IMAGE_RENDERING.AUTO: keep current global setting
229
-
230
- this.renderReplacedElement(container, curves, image!);
231
-
232
- // Restore previous smoothing state
233
- this.ctx.imageSmoothingEnabled = prevSmoothing;
234
- } catch (e) {
235
- this.context.logger.error(`Error loading image ${container.src}`);
236
- }
237
- }
238
-
239
- if (container instanceof CanvasElementContainer) {
240
- this.renderReplacedElement(container, curves, container.canvas);
241
- }
242
-
243
- if (container instanceof SVGElementContainer) {
244
- try {
245
- const image = await this.context.cache.match(container.svg);
246
- this.renderReplacedElement(container, curves, image!);
247
- } catch (e) {
248
- this.context.logger.error(`Error loading svg ${container.svg.substring(0, 255)}`);
249
- }
250
- }
251
-
252
- if (container instanceof IFrameElementContainer && container.tree) {
253
- const iframeRenderer = new CanvasRenderer(this.context, {
254
- scale: this.options.scale,
255
- backgroundColor: container.backgroundColor,
256
- x: 0,
257
- y: 0,
258
- width: container.width,
259
- height: container.height
260
- });
261
-
262
- const canvas = await iframeRenderer.render(container.tree);
263
- if (container.width && container.height) {
264
- this.ctx.drawImage(
265
- canvas,
266
- 0,
267
- 0,
268
- container.width,
269
- container.height,
270
- container.bounds.left,
271
- container.bounds.top,
272
- container.bounds.width,
273
- container.bounds.height
274
- );
275
- }
276
- }
277
-
278
- if (container instanceof InputElementContainer) {
279
- const size = Math.min(container.bounds.width, container.bounds.height);
280
-
281
- if (container.type === CHECKBOX) {
282
- if (container.checked) {
283
- this.ctx.save();
284
- this.path([
285
- new Vector(container.bounds.left + size * 0.39363, container.bounds.top + size * 0.79),
286
- new Vector(container.bounds.left + size * 0.16, container.bounds.top + size * 0.5549),
287
- new Vector(container.bounds.left + size * 0.27347, container.bounds.top + size * 0.44071),
288
- new Vector(container.bounds.left + size * 0.39694, container.bounds.top + size * 0.5649),
289
- new Vector(container.bounds.left + size * 0.72983, container.bounds.top + size * 0.23),
290
- new Vector(container.bounds.left + size * 0.84, container.bounds.top + size * 0.34085),
291
- new Vector(container.bounds.left + size * 0.39363, container.bounds.top + size * 0.79)
292
- ]);
293
-
294
- this.ctx.fillStyle = asString(INPUT_COLOR);
295
- this.ctx.fill();
296
- this.ctx.restore();
297
- }
298
- } else if (container.type === RADIO) {
299
- if (container.checked) {
300
- this.ctx.save();
301
- this.ctx.beginPath();
302
- this.ctx.arc(
303
- container.bounds.left + size / 2,
304
- container.bounds.top + size / 2,
305
- size / 4,
306
- 0,
307
- Math.PI * 2,
308
- true
309
- );
310
- this.ctx.fillStyle = asString(INPUT_COLOR);
311
- this.ctx.fill();
312
- this.ctx.restore();
313
- }
314
- }
315
- }
316
-
317
- if (isTextInputElement(container) && container.value.length) {
318
- const [font, fontFamily, fontSize] = this.textRenderer.createFontStyle(styles);
319
- const { baseline } = this.fontMetrics.getMetrics(fontFamily, fontSize);
320
-
321
- this.ctx.font = font;
322
-
323
- // Fix for Issue #92: Use placeholder color when rendering placeholder text
324
- const isPlaceholder = container instanceof InputElementContainer && container.isPlaceholder;
325
- this.ctx.fillStyle = isPlaceholder ? asString(PLACEHOLDER_COLOR) : asString(styles.color);
326
-
327
- this.ctx.textBaseline = 'alphabetic';
328
- this.ctx.textAlign = canvasTextAlign(container.styles.textAlign);
329
-
330
- const bounds = contentBox(container);
331
-
332
- let x = 0;
333
-
334
- switch (container.styles.textAlign) {
335
- case TEXT_ALIGN.CENTER:
336
- x += bounds.width / 2;
337
- break;
338
- case TEXT_ALIGN.RIGHT:
339
- x += bounds.width;
340
- break;
341
- }
342
-
343
- // Fix for Issue #92: Position text vertically centered in single-line input
344
- // Only apply vertical centering for InputElementContainer, not for textarea or select
345
- let verticalOffset = 0;
346
- if (container instanceof InputElementContainer) {
347
- const fontSizeValue = getAbsoluteValue(styles.fontSize, 0);
348
- verticalOffset = (bounds.height - fontSizeValue) / 2;
349
- }
350
-
351
- // Create text bounds with horizontal and vertical offsets
352
- // Height is not modified as it doesn't affect text rendering position
353
- const textBounds = bounds.add(x, verticalOffset, 0, 0);
354
-
355
- this.ctx.save();
356
- this.path([
357
- new Vector(bounds.left, bounds.top),
358
- new Vector(bounds.left + bounds.width, bounds.top),
359
- new Vector(bounds.left + bounds.width, bounds.top + bounds.height),
360
- new Vector(bounds.left, bounds.top + bounds.height)
361
- ]);
362
-
363
- this.ctx.clip();
364
-
365
- this.textRenderer.renderTextWithLetterSpacing(
366
- new TextBounds(container.value, textBounds),
367
- styles.letterSpacing,
368
- baseline,
369
- styles.writingMode
370
- );
371
- this.ctx.restore();
372
- this.ctx.textBaseline = 'alphabetic';
373
- this.ctx.textAlign = 'left';
374
- }
375
-
376
- if (contains(container.styles.display, DISPLAY.LIST_ITEM)) {
377
- if (container.styles.listStyleImage !== null) {
378
- const img = container.styles.listStyleImage;
379
- if (img.type === CSSImageType.URL) {
380
- let image;
381
- const url = (img as CSSURLImage).url;
382
- try {
383
- image = await this.context.cache.match(url);
384
- this.ctx.drawImage(image!, container.bounds.left - (image!.width + 10), container.bounds.top);
385
- } catch (e) {
386
- this.context.logger.error(`Error loading list-style-image ${url}`);
387
- }
388
- }
389
- } else if (paint.listValue && container.styles.listStyleType !== LIST_STYLE_TYPE.NONE) {
390
- const [font] = this.textRenderer.createFontStyle(styles);
391
-
392
- this.ctx.font = font;
393
- this.ctx.fillStyle = asString(styles.color);
394
-
395
- this.ctx.textBaseline = 'middle';
396
- this.ctx.textAlign = 'right';
397
- const bounds = new Bounds(
398
- container.bounds.left,
399
- container.bounds.top + getAbsoluteValue(container.styles.paddingTop, container.bounds.width),
400
- container.bounds.width,
401
- computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 1
402
- );
403
-
404
- this.textRenderer.renderTextWithLetterSpacing(
405
- new TextBounds(paint.listValue, bounds),
406
- styles.letterSpacing,
407
- computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 2,
408
- styles.writingMode
409
- );
410
- this.ctx.textBaseline = 'bottom';
411
- this.ctx.textAlign = 'left';
412
- }
413
- }
414
- }
415
-
416
- async renderStackContent(stack: StackingContext): Promise<void> {
417
- if (stack.element.container.debugRender) {
418
- debugger;
419
- }
420
- const signal = this.options.signal;
421
- // https://www.w3.org/TR/css-position-3/#painting-order
422
- // 1. the background and borders of the element forming the stacking context.
423
- await this.renderNodeBackgroundAndBorders(stack.element);
424
- // 2. the child stacking contexts with negative stack levels (most negative first).
425
- for (const child of stack.negativeZIndex) {
426
- if (signal?.aborted) throw new DOMException('The operation was aborted.', 'AbortError');
427
- await this.renderStack(child);
428
- }
429
- // 3. For all its in-flow, non-positioned, block-level descendants in tree order:
430
- if (signal?.aborted) throw new DOMException('The operation was aborted.', 'AbortError');
431
- await this.renderNodeContent(stack.element);
432
-
433
- for (const child of stack.nonInlineLevel) {
434
- await this.renderNode(child);
435
- }
436
- // 4. All non-positioned floating descendants, in tree order. For each one of these,
437
- // treat the element as if it created a new stacking context, but any positioned descendants and descendants
438
- // which actually create a new stacking context should be considered part of the parent stacking context,
439
- // not this new one.
440
- for (const child of stack.nonPositionedFloats) {
441
- if (signal?.aborted) throw new DOMException('The operation was aborted.', 'AbortError');
442
- await this.renderStack(child);
443
- }
444
- // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
445
- for (const child of stack.nonPositionedInlineLevel) {
446
- if (signal?.aborted) throw new DOMException('The operation was aborted.', 'AbortError');
447
- await this.renderStack(child);
448
- }
449
- for (const child of stack.inlineLevel) {
450
- await this.renderNode(child);
451
- }
452
- // 6. All positioned, opacity or transform descendants, in tree order that fall into the following categories:
453
- // All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order.
454
- // For those with 'z-index: auto', treat the element as if it created a new stacking context,
455
- // but any positioned descendants and descendants which actually create a new stacking context should be
456
- // considered part of the parent stacking context, not this new one. For those with 'z-index: 0',
457
- // treat the stacking context generated atomically.
458
- //
459
- // All opacity descendants with opacity less than 1
460
- //
461
- // All transform descendants with transform other than none
462
- for (const child of stack.zeroOrAutoZIndexOrTransformedOrOpacity) {
463
- if (signal?.aborted) throw new DOMException('The operation was aborted.', 'AbortError');
464
- await this.renderStack(child);
465
- }
466
- // 7. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index
467
- // order (smallest first) then tree order.
468
- for (const child of stack.positiveZIndex) {
469
- if (signal?.aborted) throw new DOMException('The operation was aborted.', 'AbortError');
470
- await this.renderStack(child);
471
- }
472
- }
473
-
474
- mask(paths: Path[]): void {
475
- this.ctx.beginPath();
476
- this.ctx.moveTo(0, 0);
477
- // Use logical dimensions (options.width/height) instead of canvas pixel dimensions
478
- // because context has already been scaled by this.options.scale
479
- // Fix for Issue #126: Using canvas pixel dimensions causes broken output
480
- this.ctx.lineTo(this.options.width, 0);
481
- this.ctx.lineTo(this.options.width, this.options.height);
482
- this.ctx.lineTo(0, this.options.height);
483
- this.ctx.lineTo(0, 0);
484
- this.formatPath(paths.slice(0).reverse());
485
- this.ctx.closePath();
486
- }
487
-
488
- path(paths: Path[]): void {
489
- createCanvasPath(this.ctx, paths);
490
- }
491
-
492
- formatPath(paths: Path[]): void {
493
- formatCanvasPath(this.ctx, paths);
494
- }
495
-
496
- async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise<void> {
497
- this.effectsRenderer.applyEffects(paint.getEffects(EffectTarget.BACKGROUND_BORDERS));
498
- const styles = paint.container.styles;
499
- const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length;
500
-
501
- const borders = [
502
- { style: styles.borderTopStyle, color: styles.borderTopColor, width: styles.borderTopWidth },
503
- { style: styles.borderRightStyle, color: styles.borderRightColor, width: styles.borderRightWidth },
504
- { style: styles.borderBottomStyle, color: styles.borderBottomColor, width: styles.borderBottomWidth },
505
- { style: styles.borderLeftStyle, color: styles.borderLeftColor, width: styles.borderLeftWidth }
506
- ];
507
-
508
- const backgroundPaintingArea = calculateBackgroundCurvedPaintingArea(
509
- getBackgroundValueForIndex(styles.backgroundClip, 0),
510
- paint.curves
511
- );
512
-
513
- if (hasBackground || styles.boxShadow.length) {
514
- this.ctx.save();
515
- this.path(backgroundPaintingArea);
516
- this.ctx.clip();
517
-
518
- if (!isTransparent(styles.backgroundColor)) {
519
- this.ctx.fillStyle = asString(styles.backgroundColor);
520
- this.ctx.fill();
521
- }
522
-
523
- await this.backgroundRenderer.renderBackgroundImage(paint.container);
524
-
525
- this.ctx.restore();
526
-
527
- styles.boxShadow
528
- .slice(0)
529
- .reverse()
530
- .forEach((shadow) => {
531
- this.ctx.save();
532
- const borderBoxArea = calculateBorderBoxPath(paint.curves);
533
- const maskOffset = shadow.inset ? 0 : MASK_OFFSET;
534
- const shadowPaintingArea = transformPath(
535
- borderBoxArea,
536
- -maskOffset + (shadow.inset ? 1 : -1) * shadow.spread.number,
537
- (shadow.inset ? 1 : -1) * shadow.spread.number,
538
- shadow.spread.number * (shadow.inset ? -2 : 2),
539
- shadow.spread.number * (shadow.inset ? -2 : 2)
540
- );
541
-
542
- if (shadow.inset) {
543
- this.path(borderBoxArea);
544
- this.ctx.clip();
545
- this.mask(shadowPaintingArea);
546
- } else {
547
- this.mask(borderBoxArea);
548
- this.ctx.clip();
549
- this.path(shadowPaintingArea);
550
- }
551
-
552
- this.ctx.shadowOffsetX = shadow.offsetX.number + maskOffset;
553
- this.ctx.shadowOffsetY = shadow.offsetY.number;
554
- this.ctx.shadowColor = asString(shadow.color);
555
- this.ctx.shadowBlur = shadow.blur.number;
556
- this.ctx.fillStyle = shadow.inset ? asString(shadow.color) : 'rgba(0,0,0,1)';
557
-
558
- this.ctx.fill();
559
- this.ctx.restore();
560
- });
561
- }
562
-
563
- let side = 0;
564
- for (const border of borders) {
565
- if (border.style !== BORDER_STYLE.NONE && !isTransparent(border.color) && border.width > 0) {
566
- if (border.style === BORDER_STYLE.DASHED) {
567
- await this.borderRenderer.renderDashedDottedBorder(
568
- border.color,
569
- border.width,
570
- side,
571
- paint.curves,
572
- BORDER_STYLE.DASHED
573
- );
574
- } else if (border.style === BORDER_STYLE.DOTTED) {
575
- await this.borderRenderer.renderDashedDottedBorder(
576
- border.color,
577
- border.width,
578
- side,
579
- paint.curves,
580
- BORDER_STYLE.DOTTED
581
- );
582
- } else if (border.style === BORDER_STYLE.DOUBLE) {
583
- await this.borderRenderer.renderDoubleBorder(border.color, border.width, side, paint.curves);
584
- } else {
585
- await this.borderRenderer.renderSolidBorder(border.color, side, paint.curves);
586
- }
587
- }
588
- side++;
589
- }
590
- }
591
-
592
- async render(element: ElementContainer): Promise<HTMLCanvasElement> {
593
- if (this.options.backgroundColor) {
594
- this.ctx.fillStyle = asString(this.options.backgroundColor);
595
- this.ctx.fillRect(this.options.x, this.options.y, this.options.width, this.options.height);
596
- }
597
-
598
- const stack = parseStackingContexts(element);
599
-
600
- await this.renderStack(stack);
601
- this.effectsRenderer.applyEffects([]);
602
- return this.canvas;
603
- }
604
- }
605
-
606
- const isTextInputElement = (
607
- container: ElementContainer
608
- ): container is InputElementContainer | TextareaElementContainer | SelectElementContainer => {
609
- if (container instanceof TextareaElementContainer) {
610
- return true;
611
- } else if (container instanceof SelectElementContainer) {
612
- return true;
613
- } else if (container instanceof InputElementContainer && container.type !== RADIO && container.type !== CHECKBOX) {
614
- return true;
615
- }
616
- return false;
617
- };
618
-
619
- const calculateBackgroundCurvedPaintingArea = (clip: BACKGROUND_CLIP, curves: BoundCurves): Path[] => {
620
- switch (clip) {
621
- case BACKGROUND_CLIP.BORDER_BOX:
622
- return calculateBorderBoxPath(curves);
623
- case BACKGROUND_CLIP.CONTENT_BOX:
624
- return calculateContentBoxPath(curves);
625
- case BACKGROUND_CLIP.PADDING_BOX:
626
- default:
627
- return calculatePaddingBoxPath(curves);
628
- }
629
- };
630
-
631
- const canvasTextAlign = (textAlign: TEXT_ALIGN): CanvasTextAlign => {
632
- switch (textAlign) {
633
- case TEXT_ALIGN.CENTER:
634
- return 'center';
635
- case TEXT_ALIGN.RIGHT:
636
- return 'right';
637
- case TEXT_ALIGN.LEFT:
638
- default:
639
- return 'left';
640
- }
641
- };