html2canvas-pro 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/dist/html2canvas-pro.esm.js +10226 -10526
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +10869 -11171
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +8 -8
  6. package/dist/lib/config.js +0 -22
  7. package/dist/lib/core/cache-storage.js +3 -40
  8. package/dist/lib/core/constants.js +25 -0
  9. package/dist/lib/core/context.js +1 -0
  10. package/dist/lib/core/features.js +3 -2
  11. package/dist/lib/core/validator.js +3 -3
  12. package/dist/lib/css/grouped/background-styles.js +36 -0
  13. package/dist/lib/css/grouped/border-styles.js +75 -0
  14. package/dist/lib/css/grouped/font-styles.js +93 -0
  15. package/dist/lib/css/grouped/layout-styles.js +127 -0
  16. package/dist/lib/css/index.js +74 -46
  17. package/dist/lib/css/layout/text.js +7 -6
  18. package/dist/lib/css/property-descriptors/background-blend-mode.js +41 -0
  19. package/dist/lib/css/property-descriptors/border-image-repeat.js +42 -0
  20. package/dist/lib/css/property-descriptors/border-image-slice.js +45 -0
  21. package/dist/lib/css/property-descriptors/border-image-source.js +21 -0
  22. package/dist/lib/css/property-descriptors/border-radius.js +1 -1
  23. package/dist/lib/css/property-descriptors/box-decoration-break.js +18 -0
  24. package/dist/lib/css/property-descriptors/counter-increment.js +17 -12
  25. package/dist/lib/css/property-descriptors/counter-reset.js +4 -12
  26. package/dist/lib/css/property-descriptors/filter.js +76 -0
  27. package/dist/lib/css/property-descriptors/font-variant-ligatures.js +34 -0
  28. package/dist/lib/css/property-descriptors/object-fit.js +1 -1
  29. package/dist/lib/css/property-descriptors/object-position.js +42 -0
  30. package/dist/lib/css/property-descriptors/visibility.js +1 -1
  31. package/dist/lib/css/property-descriptors/zoom.js +18 -0
  32. package/dist/lib/css/syntax/parser.js +0 -1
  33. package/dist/lib/css/types/color.js +5 -1
  34. package/dist/lib/css/types/functions/repeating-linear-gradient.js +9 -0
  35. package/dist/lib/css/types/image.js +12 -2
  36. package/dist/lib/css/types/length-percentage.js +6 -2
  37. package/dist/lib/css/types/safe-eval.js +80 -0
  38. package/dist/lib/dom/document-cloner.js +23 -163
  39. package/dist/lib/dom/slot-cloner.js +176 -0
  40. package/dist/lib/index.js +1 -17
  41. package/dist/lib/render/canvas/background-renderer.js +169 -30
  42. package/dist/lib/render/canvas/border-image-renderer.js +153 -0
  43. package/dist/lib/render/canvas/canvas-renderer.js +39 -190
  44. package/dist/lib/render/canvas/content-renderer.js +202 -0
  45. package/dist/lib/render/canvas/effects-renderer.js +3 -0
  46. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  47. package/dist/lib/render/canvas/text/text-decoration-renderer.js +99 -0
  48. package/dist/lib/render/canvas/text-renderer.js +100 -224
  49. package/dist/lib/render/effects.js +38 -3
  50. package/dist/lib/render/object-fit.js +19 -15
  51. package/dist/lib/render/stacking-context.js +11 -0
  52. package/dist/types/config.d.ts +0 -10
  53. package/dist/types/core/cache-storage.d.ts +0 -24
  54. package/dist/types/core/constants.d.ts +22 -0
  55. package/dist/types/core/context.d.ts +3 -0
  56. package/dist/types/core/performance-monitor.d.ts +4 -4
  57. package/dist/types/core/validator.d.ts +6 -8
  58. package/dist/types/css/grouped/background-styles.d.ts +16 -0
  59. package/dist/types/css/grouped/border-styles.d.ts +31 -0
  60. package/dist/types/css/grouped/font-styles.d.ts +35 -0
  61. package/dist/types/css/grouped/layout-styles.d.ts +46 -0
  62. package/dist/types/css/index.d.ts +30 -0
  63. package/dist/types/css/property-descriptors/background-blend-mode.d.ts +23 -0
  64. package/dist/types/css/property-descriptors/border-image-repeat.d.ts +12 -0
  65. package/dist/types/css/property-descriptors/border-image-slice.d.ts +10 -0
  66. package/dist/types/css/property-descriptors/border-image-source.d.ts +4 -0
  67. package/dist/types/css/property-descriptors/box-decoration-break.d.ts +6 -0
  68. package/dist/types/css/property-descriptors/counter-increment.d.ts +3 -0
  69. package/dist/types/css/property-descriptors/filter.d.ts +3 -0
  70. package/dist/types/css/property-descriptors/font-variant-ligatures.d.ts +14 -0
  71. package/dist/types/css/property-descriptors/object-position.d.ts +4 -0
  72. package/dist/types/css/property-descriptors/zoom.d.ts +3 -0
  73. package/dist/types/css/types/functions/repeating-linear-gradient.d.ts +4 -0
  74. package/dist/types/css/types/image.d.ts +4 -2
  75. package/dist/types/css/types/safe-eval.d.ts +8 -0
  76. package/dist/types/dom/document-cloner.d.ts +3 -44
  77. package/dist/types/dom/slot-cloner.d.ts +66 -0
  78. package/dist/types/index.d.ts +3 -7
  79. package/dist/types/options.d.ts +11 -0
  80. package/dist/types/render/canvas/background-renderer.d.ts +23 -0
  81. package/dist/types/render/canvas/border-image-renderer.d.ts +18 -0
  82. package/dist/types/render/canvas/canvas-renderer.d.ts +1 -0
  83. package/dist/types/render/canvas/content-renderer.d.ts +44 -0
  84. package/dist/types/render/canvas/text/text-decoration-renderer.d.ts +18 -0
  85. package/dist/types/render/canvas/text-renderer.d.ts +12 -1
  86. package/dist/types/render/effects.d.ts +12 -2
  87. package/dist/types/render/object-fit.d.ts +2 -1
  88. package/dist/types/render/renderer-interface.d.ts +11 -9
  89. package/package.json +7 -20
  90. package/dist/lib/dom/replaced-elements/pseudo-elements.js +0 -0
  91. package/dist/lib/invariant.js +0 -9
  92. package/dist/types/dom/replaced-elements/pseudo-elements.d.ts +0 -0
  93. package/dist/types/invariant.d.ts +0 -1
  94. package/src/__tests__/index.ts +0 -99
  95. package/src/config.ts +0 -107
  96. package/src/core/__mocks__/cache-storage.ts +0 -1
  97. package/src/core/__mocks__/context.ts +0 -19
  98. package/src/core/__mocks__/features.ts +0 -8
  99. package/src/core/__mocks__/logger.ts +0 -17
  100. package/src/core/__tests__/cache-storage.test.ts +0 -205
  101. package/src/core/__tests__/cache-storage.ts +0 -278
  102. package/src/core/__tests__/logger.ts +0 -29
  103. package/src/core/__tests__/validator.ts +0 -359
  104. package/src/core/bitwise.ts +0 -1
  105. package/src/core/cache-storage.ts +0 -315
  106. package/src/core/context.ts +0 -31
  107. package/src/core/debugger.ts +0 -32
  108. package/src/core/features.ts +0 -222
  109. package/src/core/logger.ts +0 -64
  110. package/src/core/origin-checker.ts +0 -57
  111. package/src/core/performance-monitor.ts +0 -241
  112. package/src/core/render-element.ts +0 -272
  113. package/src/core/util.ts +0 -1
  114. package/src/core/validator.ts +0 -593
  115. package/src/css/index.ts +0 -427
  116. package/src/css/layout/__mocks__/bounds.ts +0 -6
  117. package/src/css/layout/bounds.ts +0 -79
  118. package/src/css/layout/text.ts +0 -161
  119. package/src/css/property-descriptor.ts +0 -49
  120. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  121. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  122. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  123. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  124. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  125. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  126. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  127. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  128. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  129. package/src/css/property-descriptors/background-clip.ts +0 -30
  130. package/src/css/property-descriptors/background-color.ts +0 -9
  131. package/src/css/property-descriptors/background-image.ts +0 -27
  132. package/src/css/property-descriptors/background-origin.ts +0 -31
  133. package/src/css/property-descriptors/background-position.ts +0 -38
  134. package/src/css/property-descriptors/background-repeat.ts +0 -44
  135. package/src/css/property-descriptors/background-size.ts +0 -27
  136. package/src/css/property-descriptors/border-color.ts +0 -13
  137. package/src/css/property-descriptors/border-radius.ts +0 -19
  138. package/src/css/property-descriptors/border-style.ts +0 -34
  139. package/src/css/property-descriptors/border-width.ts +0 -20
  140. package/src/css/property-descriptors/box-shadow.ts +0 -60
  141. package/src/css/property-descriptors/clip-path.ts +0 -271
  142. package/src/css/property-descriptors/color.ts +0 -9
  143. package/src/css/property-descriptors/content.ts +0 -26
  144. package/src/css/property-descriptors/counter-increment.ts +0 -43
  145. package/src/css/property-descriptors/counter-reset.ts +0 -36
  146. package/src/css/property-descriptors/direction.ts +0 -23
  147. package/src/css/property-descriptors/display.ts +0 -117
  148. package/src/css/property-descriptors/duration.ts +0 -14
  149. package/src/css/property-descriptors/float.ts +0 -29
  150. package/src/css/property-descriptors/font-family.ts +0 -38
  151. package/src/css/property-descriptors/font-size.ts +0 -9
  152. package/src/css/property-descriptors/font-style.ts +0 -25
  153. package/src/css/property-descriptors/font-variant.ts +0 -12
  154. package/src/css/property-descriptors/font-weight.ts +0 -26
  155. package/src/css/property-descriptors/image-rendering.ts +0 -33
  156. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  157. package/src/css/property-descriptors/line-break.ts +0 -22
  158. package/src/css/property-descriptors/line-height.ts +0 -22
  159. package/src/css/property-descriptors/list-style-image.ts +0 -19
  160. package/src/css/property-descriptors/list-style-position.ts +0 -22
  161. package/src/css/property-descriptors/list-style-type.ts +0 -179
  162. package/src/css/property-descriptors/margin.ts +0 -13
  163. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  164. package/src/css/property-descriptors/object-fit.ts +0 -39
  165. package/src/css/property-descriptors/opacity.ts +0 -15
  166. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  167. package/src/css/property-descriptors/overflow.ts +0 -34
  168. package/src/css/property-descriptors/padding.ts +0 -14
  169. package/src/css/property-descriptors/paint-order.ts +0 -42
  170. package/src/css/property-descriptors/position.ts +0 -30
  171. package/src/css/property-descriptors/quotes.ts +0 -57
  172. package/src/css/property-descriptors/rotate.ts +0 -34
  173. package/src/css/property-descriptors/text-align.ts +0 -26
  174. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  175. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  176. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  177. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  178. package/src/css/property-descriptors/text-overflow.ts +0 -23
  179. package/src/css/property-descriptors/text-shadow.ts +0 -52
  180. package/src/css/property-descriptors/text-transform.ts +0 -27
  181. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  182. package/src/css/property-descriptors/transform-origin.ts +0 -29
  183. package/src/css/property-descriptors/transform.ts +0 -74
  184. package/src/css/property-descriptors/visibility.ts +0 -25
  185. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  186. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  187. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  188. package/src/css/property-descriptors/word-break.ts +0 -25
  189. package/src/css/property-descriptors/writing-mode.ts +0 -37
  190. package/src/css/property-descriptors/z-index.ts +0 -27
  191. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  192. package/src/css/syntax/parser.ts +0 -188
  193. package/src/css/syntax/tokenizer.ts +0 -822
  194. package/src/css/type-descriptor.ts +0 -7
  195. package/src/css/types/__tests__/color-tests.ts +0 -147
  196. package/src/css/types/__tests__/image-tests.ts +0 -239
  197. package/src/css/types/angle.ts +0 -86
  198. package/src/css/types/color-math.ts +0 -22
  199. package/src/css/types/color-spaces/a98.ts +0 -86
  200. package/src/css/types/color-spaces/p3.ts +0 -92
  201. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  202. package/src/css/types/color-spaces/rec2020.ts +0 -90
  203. package/src/css/types/color-spaces/srgb.ts +0 -87
  204. package/src/css/types/color-utilities.ts +0 -452
  205. package/src/css/types/color.ts +0 -485
  206. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  207. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  208. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  209. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  210. package/src/css/types/functions/counter.ts +0 -511
  211. package/src/css/types/functions/gradient.ts +0 -206
  212. package/src/css/types/functions/linear-gradient.ts +0 -28
  213. package/src/css/types/functions/radial-gradient.ts +0 -101
  214. package/src/css/types/image.ts +0 -120
  215. package/src/css/types/index.ts +0 -1
  216. package/src/css/types/length-percentage.ts +0 -137
  217. package/src/css/types/length.ts +0 -7
  218. package/src/css/types/time.ts +0 -20
  219. package/src/dom/__mocks__/document-cloner.ts +0 -22
  220. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  221. package/src/dom/__tests__/element-container.test.ts +0 -129
  222. package/src/dom/document-cloner.ts +0 -929
  223. package/src/dom/dom-normalizer.ts +0 -133
  224. package/src/dom/element-container.ts +0 -75
  225. package/src/dom/elements/li-element-container.ts +0 -10
  226. package/src/dom/elements/ol-element-container.ts +0 -12
  227. package/src/dom/elements/select-element-container.ts +0 -10
  228. package/src/dom/elements/textarea-element-container.ts +0 -9
  229. package/src/dom/node-parser.ts +0 -177
  230. package/src/dom/node-type-guards.ts +0 -70
  231. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  232. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  233. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  234. package/src/dom/replaced-elements/index.ts +0 -5
  235. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  236. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  237. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  238. package/src/dom/text-container.ts +0 -42
  239. package/src/global.d.ts +0 -19
  240. package/src/index.ts +0 -82
  241. package/src/invariant.ts +0 -5
  242. package/src/options.ts +0 -55
  243. package/src/render/__tests__/object-fit.test.ts +0 -85
  244. package/src/render/background.ts +0 -298
  245. package/src/render/bezier-curve.ts +0 -47
  246. package/src/render/border.ts +0 -165
  247. package/src/render/bound-curves.ts +0 -388
  248. package/src/render/box-sizing.ts +0 -31
  249. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  250. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  251. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  252. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  253. package/src/render/canvas/background-renderer.ts +0 -271
  254. package/src/render/canvas/border-renderer.ts +0 -224
  255. package/src/render/canvas/canvas-path.ts +0 -31
  256. package/src/render/canvas/canvas-renderer.ts +0 -641
  257. package/src/render/canvas/effects-renderer.ts +0 -130
  258. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  259. package/src/render/canvas/text-renderer.ts +0 -700
  260. package/src/render/effects.ts +0 -75
  261. package/src/render/font-metrics.ts +0 -72
  262. package/src/render/object-fit.ts +0 -100
  263. package/src/render/path.ts +0 -37
  264. package/src/render/renderer-interface.ts +0 -28
  265. package/src/render/stacking-context.ts +0 -386
  266. package/src/render/vector.ts +0 -19
@@ -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
- };