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
@@ -5,36 +5,28 @@ const stacking_context_1 = require("../stacking-context");
5
5
  const color_utilities_1 = require("../../css/types/color-utilities");
6
6
  const path_1 = require("../path");
7
7
  const bound_curves_1 = require("../bound-curves");
8
- const vector_1 = require("../vector");
9
8
  const background_1 = require("../background");
10
- const text_1 = require("../../css/layout/text");
11
- const image_element_container_1 = require("../../dom/replaced-elements/image-element-container");
12
9
  const box_sizing_1 = require("../box-sizing");
13
- const canvas_element_container_1 = require("../../dom/replaced-elements/canvas-element-container");
14
- const svg_element_container_1 = require("../../dom/replaced-elements/svg-element-container");
15
- const bitwise_1 = require("../../core/bitwise");
16
- const length_percentage_1 = require("../../css/types/length-percentage");
17
10
  const font_metrics_1 = require("../font-metrics");
18
- const bounds_1 = require("../../css/layout/bounds");
19
- const image_rendering_1 = require("../../css/property-descriptors/image-rendering");
20
- const line_height_1 = require("../../css/property-descriptors/line-height");
21
- const input_element_container_1 = require("../../dom/replaced-elements/input-element-container");
22
- const textarea_element_container_1 = require("../../dom/elements/textarea-element-container");
23
- const select_element_container_1 = require("../../dom/elements/select-element-container");
24
- const iframe_element_container_1 = require("../../dom/replaced-elements/iframe-element-container");
11
+ const text_renderer_1 = require("./text-renderer");
25
12
  const background_renderer_1 = require("./background-renderer");
26
13
  const border_renderer_1 = require("./border-renderer");
14
+ const border_image_renderer_1 = require("./border-image-renderer");
27
15
  const effects_renderer_1 = require("./effects-renderer");
28
- const text_renderer_1 = require("./text-renderer");
29
16
  const canvas_path_1 = require("./canvas-path");
30
17
  const object_fit_1 = require("../object-fit");
18
+ const content_renderer_1 = require("./content-renderer");
31
19
  const MASK_OFFSET = 10000;
32
20
  class CanvasRenderer {
33
21
  constructor(context, options) {
34
22
  this.context = context;
35
23
  this.options = options;
36
24
  this.canvas = options.canvas ? options.canvas : document.createElement('canvas');
37
- this.ctx = this.canvas.getContext('2d');
25
+ const ctx = this.canvas.getContext('2d');
26
+ if (!ctx) {
27
+ throw new Error('Failed to get 2D rendering context from canvas');
28
+ }
29
+ this.ctx = ctx;
38
30
  if (!options.canvas) {
39
31
  this.canvas.width = Math.floor(options.width * options.scale);
40
32
  this.canvas.height = Math.floor(options.height * options.scale);
@@ -67,6 +59,7 @@ class CanvasRenderer {
67
59
  path: (paths) => this.path(paths),
68
60
  formatPath: (paths) => this.formatPath(paths)
69
61
  });
62
+ this.borderImageRenderer = new border_image_renderer_1.BorderImageRenderer(this.ctx);
70
63
  this.effectsRenderer = new effects_renderer_1.EffectsRenderer({ ctx: this.ctx }, { path: (paths) => this.path(paths) });
71
64
  this.textRenderer = new text_renderer_1.TextRenderer({
72
65
  ctx: this.ctx,
@@ -104,7 +97,7 @@ class CanvasRenderer {
104
97
  this.path(path);
105
98
  this.ctx.save();
106
99
  this.ctx.clip();
107
- const { sx, sy, sw, sh, dx, dy, dw, dh } = (0, object_fit_1.calculateObjectFitRendering)(intrinsicWidth, intrinsicHeight, box, container.styles.objectFit);
100
+ const { sx, sy, sw, sh, dx, dy, dw, dh } = (0, object_fit_1.calculateObjectFitRendering)(intrinsicWidth, intrinsicHeight, box, container.styles.objectFit, container.styles.objectPosition);
108
101
  this.ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
109
102
  this.ctx.restore();
110
103
  }
@@ -115,160 +108,20 @@ class CanvasRenderer {
115
108
  const curves = paint.curves;
116
109
  const styles = container.styles;
117
110
  // Use content box for text overflow calculation (excludes padding and border)
118
- // This matches browser behavior where text-overflow uses the content width
119
111
  const textBounds = (0, box_sizing_1.contentBox)(container);
120
112
  for (const child of container.textNodes) {
121
113
  await this.textRenderer.renderTextNode(child, styles, textBounds);
122
114
  }
123
- if (container instanceof image_element_container_1.ImageElementContainer) {
124
- try {
125
- const image = await this.context.cache.match(container.src);
126
- // Apply image smoothing based on CSS image-rendering property and global options
127
- const prevSmoothing = this.ctx.imageSmoothingEnabled;
128
- // CSS image-rendering property overrides global settings
129
- if (styles.imageRendering === image_rendering_1.IMAGE_RENDERING.PIXELATED ||
130
- styles.imageRendering === image_rendering_1.IMAGE_RENDERING.CRISP_EDGES) {
131
- 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'}`);
132
- this.ctx.imageSmoothingEnabled = false;
133
- }
134
- else if (styles.imageRendering === image_rendering_1.IMAGE_RENDERING.SMOOTH) {
135
- this.context.logger.debug(`Enabling image smoothing for ${container.src} due to CSS image-rendering: smooth`);
136
- this.ctx.imageSmoothingEnabled = true;
137
- }
138
- // IMAGE_RENDERING.AUTO: keep current global setting
139
- this.renderReplacedElement(container, curves, image);
140
- // Restore previous smoothing state
141
- this.ctx.imageSmoothingEnabled = prevSmoothing;
142
- }
143
- catch (e) {
144
- this.context.logger.error(`Error loading image ${container.src}`);
145
- }
146
- }
147
- if (container instanceof canvas_element_container_1.CanvasElementContainer) {
148
- this.renderReplacedElement(container, curves, container.canvas);
149
- }
150
- if (container instanceof svg_element_container_1.SVGElementContainer) {
151
- try {
152
- const image = await this.context.cache.match(container.svg);
153
- this.renderReplacedElement(container, curves, image);
154
- }
155
- catch (e) {
156
- this.context.logger.error(`Error loading svg ${container.svg.substring(0, 255)}`);
157
- }
158
- }
159
- if (container instanceof iframe_element_container_1.IFrameElementContainer && container.tree) {
160
- const iframeRenderer = new CanvasRenderer(this.context, {
161
- scale: this.options.scale,
162
- backgroundColor: container.backgroundColor,
163
- x: 0,
164
- y: 0,
165
- width: container.width,
166
- height: container.height
167
- });
168
- const canvas = await iframeRenderer.render(container.tree);
169
- if (container.width && container.height) {
170
- this.ctx.drawImage(canvas, 0, 0, container.width, container.height, container.bounds.left, container.bounds.top, container.bounds.width, container.bounds.height);
171
- }
172
- }
173
- if (container instanceof input_element_container_1.InputElementContainer) {
174
- const size = Math.min(container.bounds.width, container.bounds.height);
175
- if (container.type === input_element_container_1.CHECKBOX) {
176
- if (container.checked) {
177
- this.ctx.save();
178
- this.path([
179
- new vector_1.Vector(container.bounds.left + size * 0.39363, container.bounds.top + size * 0.79),
180
- new vector_1.Vector(container.bounds.left + size * 0.16, container.bounds.top + size * 0.5549),
181
- new vector_1.Vector(container.bounds.left + size * 0.27347, container.bounds.top + size * 0.44071),
182
- new vector_1.Vector(container.bounds.left + size * 0.39694, container.bounds.top + size * 0.5649),
183
- new vector_1.Vector(container.bounds.left + size * 0.72983, container.bounds.top + size * 0.23),
184
- new vector_1.Vector(container.bounds.left + size * 0.84, container.bounds.top + size * 0.34085),
185
- new vector_1.Vector(container.bounds.left + size * 0.39363, container.bounds.top + size * 0.79)
186
- ]);
187
- this.ctx.fillStyle = (0, color_utilities_1.asString)(input_element_container_1.INPUT_COLOR);
188
- this.ctx.fill();
189
- this.ctx.restore();
190
- }
191
- }
192
- else if (container.type === input_element_container_1.RADIO) {
193
- if (container.checked) {
194
- this.ctx.save();
195
- this.ctx.beginPath();
196
- this.ctx.arc(container.bounds.left + size / 2, container.bounds.top + size / 2, size / 4, 0, Math.PI * 2, true);
197
- this.ctx.fillStyle = (0, color_utilities_1.asString)(input_element_container_1.INPUT_COLOR);
198
- this.ctx.fill();
199
- this.ctx.restore();
200
- }
201
- }
202
- }
203
- if (isTextInputElement(container) && container.value.length) {
204
- const [font, fontFamily, fontSize] = this.textRenderer.createFontStyle(styles);
205
- const { baseline } = this.fontMetrics.getMetrics(fontFamily, fontSize);
206
- this.ctx.font = font;
207
- // Fix for Issue #92: Use placeholder color when rendering placeholder text
208
- const isPlaceholder = container instanceof input_element_container_1.InputElementContainer && container.isPlaceholder;
209
- this.ctx.fillStyle = isPlaceholder ? (0, color_utilities_1.asString)(input_element_container_1.PLACEHOLDER_COLOR) : (0, color_utilities_1.asString)(styles.color);
210
- this.ctx.textBaseline = 'alphabetic';
211
- this.ctx.textAlign = canvasTextAlign(container.styles.textAlign);
212
- const bounds = (0, box_sizing_1.contentBox)(container);
213
- let x = 0;
214
- switch (container.styles.textAlign) {
215
- case 1 /* TEXT_ALIGN.CENTER */:
216
- x += bounds.width / 2;
217
- break;
218
- case 2 /* TEXT_ALIGN.RIGHT */:
219
- x += bounds.width;
220
- break;
221
- }
222
- // Fix for Issue #92: Position text vertically centered in single-line input
223
- // Only apply vertical centering for InputElementContainer, not for textarea or select
224
- let verticalOffset = 0;
225
- if (container instanceof input_element_container_1.InputElementContainer) {
226
- const fontSizeValue = (0, length_percentage_1.getAbsoluteValue)(styles.fontSize, 0);
227
- verticalOffset = (bounds.height - fontSizeValue) / 2;
228
- }
229
- // Create text bounds with horizontal and vertical offsets
230
- // Height is not modified as it doesn't affect text rendering position
231
- const textBounds = bounds.add(x, verticalOffset, 0, 0);
232
- this.ctx.save();
233
- this.path([
234
- new vector_1.Vector(bounds.left, bounds.top),
235
- new vector_1.Vector(bounds.left + bounds.width, bounds.top),
236
- new vector_1.Vector(bounds.left + bounds.width, bounds.top + bounds.height),
237
- new vector_1.Vector(bounds.left, bounds.top + bounds.height)
238
- ]);
239
- this.ctx.clip();
240
- this.textRenderer.renderTextWithLetterSpacing(new text_1.TextBounds(container.value, textBounds), styles.letterSpacing, baseline, styles.writingMode);
241
- this.ctx.restore();
242
- this.ctx.textBaseline = 'alphabetic';
243
- this.ctx.textAlign = 'left';
244
- }
245
- if ((0, bitwise_1.contains)(container.styles.display, 2048 /* DISPLAY.LIST_ITEM */)) {
246
- if (container.styles.listStyleImage !== null) {
247
- const img = container.styles.listStyleImage;
248
- if (img.type === 0 /* CSSImageType.URL */) {
249
- let image;
250
- const url = img.url;
251
- try {
252
- image = await this.context.cache.match(url);
253
- this.ctx.drawImage(image, container.bounds.left - (image.width + 10), container.bounds.top);
254
- }
255
- catch (e) {
256
- this.context.logger.error(`Error loading list-style-image ${url}`);
257
- }
258
- }
259
- }
260
- else if (paint.listValue && container.styles.listStyleType !== -1 /* LIST_STYLE_TYPE.NONE */) {
261
- const [font] = this.textRenderer.createFontStyle(styles);
262
- this.ctx.font = font;
263
- this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
264
- this.ctx.textBaseline = 'middle';
265
- this.ctx.textAlign = 'right';
266
- 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);
267
- this.textRenderer.renderTextWithLetterSpacing(new text_1.TextBounds(paint.listValue, bounds), styles.letterSpacing, (0, line_height_1.computeLineHeight)(styles.lineHeight, styles.fontSize.number) / 2 + 2, styles.writingMode);
268
- this.ctx.textBaseline = 'bottom';
269
- this.ctx.textAlign = 'left';
270
- }
271
- }
115
+ await (0, content_renderer_1.renderReplacedElements)(this.ctx, this.context, {
116
+ scale: this.options.scale,
117
+ backgroundColor: this.options.backgroundColor,
118
+ x: this.options.x,
119
+ y: this.options.y,
120
+ width: this.options.width,
121
+ height: this.options.height
122
+ }, (ctx, opts) => new CanvasRenderer(ctx, opts), container, curves, styles, (c, cv, img) => this.renderReplacedElement(c, cv, img));
123
+ (0, content_renderer_1.renderFormElements)(this.ctx, this.fontMetrics, this.textRenderer, this.path.bind(this), container, styles);
124
+ await (0, content_renderer_1.renderListMarker)(this.ctx, this.context, this.textRenderer, paint, container, styles);
272
125
  }
273
126
  async renderStackContent(stack) {
274
127
  if (stack.element.container.debugRender) {
@@ -399,6 +252,25 @@ class CanvasRenderer {
399
252
  this.ctx.restore();
400
253
  });
401
254
  }
255
+ // Render border-image if present (replaces traditional borders per CSS spec)
256
+ if (styles.borderImageSource) {
257
+ const source = styles.borderImageSource;
258
+ if (source.type === 0 /* CSSImageType.URL */) {
259
+ const url = source.url;
260
+ try {
261
+ const image = await this.context.cache.match(url);
262
+ if (image) {
263
+ const bounds = paint.container.bounds;
264
+ this.borderImageRenderer.renderBorderImage(bounds, image, styles.borderImageSlice, styles.borderImageRepeat, Math.max(0, styles.borderTopWidth), Math.max(0, styles.borderRightWidth), Math.max(0, styles.borderBottomWidth), Math.max(0, styles.borderLeftWidth));
265
+ }
266
+ }
267
+ catch (e) {
268
+ this.context.logger.error(`Error loading border-image ${url}`);
269
+ }
270
+ }
271
+ // When border-image is present, skip regular border rendering
272
+ return;
273
+ }
402
274
  let side = 0;
403
275
  for (const border of borders) {
404
276
  if (border.style !== 0 /* BORDER_STYLE.NONE */ && !(0, color_utilities_1.isTransparent)(border.color) && border.width > 0) {
@@ -430,18 +302,6 @@ class CanvasRenderer {
430
302
  }
431
303
  }
432
304
  exports.CanvasRenderer = CanvasRenderer;
433
- const isTextInputElement = (container) => {
434
- if (container instanceof textarea_element_container_1.TextareaElementContainer) {
435
- return true;
436
- }
437
- else if (container instanceof select_element_container_1.SelectElementContainer) {
438
- return true;
439
- }
440
- else if (container instanceof input_element_container_1.InputElementContainer && container.type !== input_element_container_1.RADIO && container.type !== input_element_container_1.CHECKBOX) {
441
- return true;
442
- }
443
- return false;
444
- };
445
305
  const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
446
306
  switch (clip) {
447
307
  case 0 /* BACKGROUND_CLIP.BORDER_BOX */:
@@ -453,14 +313,3 @@ const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
453
313
  return (0, bound_curves_1.calculatePaddingBoxPath)(curves);
454
314
  }
455
315
  };
456
- const canvasTextAlign = (textAlign) => {
457
- switch (textAlign) {
458
- case 1 /* TEXT_ALIGN.CENTER */:
459
- return 'center';
460
- case 2 /* TEXT_ALIGN.RIGHT */:
461
- return 'right';
462
- case 0 /* TEXT_ALIGN.LEFT */:
463
- default:
464
- return 'left';
465
- }
466
- };
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderReplacedElements = renderReplacedElements;
4
+ exports.renderFormElements = renderFormElements;
5
+ exports.renderListMarker = renderListMarker;
6
+ const color_utilities_1 = require("../../css/types/color-utilities");
7
+ const image_element_container_1 = require("../../dom/replaced-elements/image-element-container");
8
+ const canvas_element_container_1 = require("../../dom/replaced-elements/canvas-element-container");
9
+ const svg_element_container_1 = require("../../dom/replaced-elements/svg-element-container");
10
+ const iframe_element_container_1 = require("../../dom/replaced-elements/iframe-element-container");
11
+ const input_element_container_1 = require("../../dom/replaced-elements/input-element-container");
12
+ const textarea_element_container_1 = require("../../dom/elements/textarea-element-container");
13
+ const select_element_container_1 = require("../../dom/elements/select-element-container");
14
+ const bounds_1 = require("../../css/layout/bounds");
15
+ const text_1 = require("../../css/layout/text");
16
+ const vector_1 = require("../vector");
17
+ const box_sizing_1 = require("../box-sizing");
18
+ const image_rendering_1 = require("../../css/property-descriptors/image-rendering");
19
+ const length_percentage_1 = require("../../css/types/length-percentage");
20
+ const line_height_1 = require("../../css/property-descriptors/line-height");
21
+ const bitwise_1 = require("../../core/bitwise");
22
+ /**
23
+ * Render replaced elements: Image, Canvas, SVG, IFrame.
24
+ */
25
+ async function renderReplacedElements(ctx, context, options, iframeRendererFactory, container, curves, styles, renderReplacedElementFn) {
26
+ if (container instanceof image_element_container_1.ImageElementContainer) {
27
+ try {
28
+ const image = await context.cache.match(container.src);
29
+ const prevSmoothing = ctx.imageSmoothingEnabled;
30
+ if (styles.imageRendering === image_rendering_1.IMAGE_RENDERING.PIXELATED ||
31
+ styles.imageRendering === image_rendering_1.IMAGE_RENDERING.CRISP_EDGES) {
32
+ ctx.imageSmoothingEnabled = false;
33
+ }
34
+ else if (styles.imageRendering === image_rendering_1.IMAGE_RENDERING.SMOOTH) {
35
+ ctx.imageSmoothingEnabled = true;
36
+ }
37
+ renderReplacedElementFn(container, curves, image);
38
+ ctx.imageSmoothingEnabled = prevSmoothing;
39
+ }
40
+ catch (e) {
41
+ context.logger.error(`Error loading image ${container.src}`);
42
+ context.onError?.(e instanceof Error ? e : new Error(String(e)));
43
+ }
44
+ }
45
+ if (container instanceof canvas_element_container_1.CanvasElementContainer) {
46
+ renderReplacedElementFn(container, curves, container.canvas);
47
+ }
48
+ if (container instanceof svg_element_container_1.SVGElementContainer) {
49
+ try {
50
+ const image = await context.cache.match(container.svg);
51
+ renderReplacedElementFn(container, curves, image);
52
+ }
53
+ catch (e) {
54
+ context.logger.error(`Error loading svg ${container.svg.substring(0, 255)}`);
55
+ context.onError?.(e instanceof Error ? e : new Error(String(e)));
56
+ }
57
+ }
58
+ if (container instanceof iframe_element_container_1.IFrameElementContainer && container.tree) {
59
+ const iframeRenderer = iframeRendererFactory(context, {
60
+ scale: options.scale,
61
+ backgroundColor: container.backgroundColor,
62
+ x: 0,
63
+ y: 0,
64
+ width: container.width,
65
+ height: container.height
66
+ });
67
+ const canvas = await iframeRenderer.render(container.tree);
68
+ if (container.width && container.height) {
69
+ ctx.drawImage(canvas, 0, 0, container.width, container.height, container.bounds.left, container.bounds.top, container.bounds.width, container.bounds.height);
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Render form element content: checkbox, radio, text input.
75
+ */
76
+ function renderFormElements(ctx, fontMetrics, textRenderer, pathFn, container, styles) {
77
+ if (container instanceof input_element_container_1.InputElementContainer) {
78
+ const size = Math.min(container.bounds.width, container.bounds.height);
79
+ if (container.type === input_element_container_1.CHECKBOX && container.checked) {
80
+ ctx.save();
81
+ pathFn([
82
+ new vector_1.Vector(container.bounds.left + size * 0.39363, container.bounds.top + size * 0.79),
83
+ new vector_1.Vector(container.bounds.left + size * 0.16, container.bounds.top + size * 0.5549),
84
+ new vector_1.Vector(container.bounds.left + size * 0.27347, container.bounds.top + size * 0.44071),
85
+ new vector_1.Vector(container.bounds.left + size * 0.39694, container.bounds.top + size * 0.5649),
86
+ new vector_1.Vector(container.bounds.left + size * 0.72983, container.bounds.top + size * 0.23),
87
+ new vector_1.Vector(container.bounds.left + size * 0.84, container.bounds.top + size * 0.34085),
88
+ new vector_1.Vector(container.bounds.left + size * 0.39363, container.bounds.top + size * 0.79)
89
+ ]);
90
+ ctx.fillStyle = (0, color_utilities_1.asString)(input_element_container_1.INPUT_COLOR);
91
+ ctx.fill();
92
+ ctx.restore();
93
+ }
94
+ else if (container.type === input_element_container_1.RADIO && container.checked) {
95
+ ctx.save();
96
+ ctx.beginPath();
97
+ ctx.arc(container.bounds.left + size / 2, container.bounds.top + size / 2, size / 4, 0, Math.PI * 2, true);
98
+ ctx.fillStyle = (0, color_utilities_1.asString)(input_element_container_1.INPUT_COLOR);
99
+ ctx.fill();
100
+ ctx.restore();
101
+ }
102
+ }
103
+ if (isTextInputElement(container) && container.value.length) {
104
+ const [font, fontFamily, fontSize] = textRenderer.createFontStyle(styles);
105
+ const { baseline } = fontMetrics.getMetrics(fontFamily, fontSize);
106
+ ctx.font = font;
107
+ const isPlaceholder = container instanceof input_element_container_1.InputElementContainer && container.isPlaceholder;
108
+ ctx.fillStyle = isPlaceholder ? (0, color_utilities_1.asString)(input_element_container_1.PLACEHOLDER_COLOR) : (0, color_utilities_1.asString)(styles.color);
109
+ ctx.textBaseline = 'alphabetic';
110
+ ctx.textAlign = canvasTextAlign(container.styles.textAlign);
111
+ const bounds = (0, box_sizing_1.contentBox)(container);
112
+ let x = 0;
113
+ switch (container.styles.textAlign) {
114
+ case 1 /* TEXT_ALIGN.CENTER */:
115
+ x += bounds.width / 2;
116
+ break;
117
+ case 2 /* TEXT_ALIGN.RIGHT */:
118
+ x += bounds.width;
119
+ break;
120
+ }
121
+ let verticalOffset = 0;
122
+ if (container instanceof input_element_container_1.InputElementContainer) {
123
+ const fontSizeValue = (0, length_percentage_1.getAbsoluteValue)(styles.fontSize, 0);
124
+ verticalOffset = (bounds.height - fontSizeValue) / 2;
125
+ }
126
+ const textBounds = bounds.add(x, verticalOffset, 0, 0);
127
+ ctx.save();
128
+ pathFn([
129
+ new vector_1.Vector(bounds.left, bounds.top),
130
+ new vector_1.Vector(bounds.left + bounds.width, bounds.top),
131
+ new vector_1.Vector(bounds.left + bounds.width, bounds.top + bounds.height),
132
+ new vector_1.Vector(bounds.left, bounds.top + bounds.height)
133
+ ]);
134
+ ctx.clip();
135
+ textRenderer.renderTextWithLetterSpacing(new text_1.TextBounds(container.value, textBounds), styles.letterSpacing, baseline, styles.writingMode);
136
+ ctx.restore();
137
+ ctx.textBaseline = 'alphabetic';
138
+ ctx.textAlign = 'left';
139
+ }
140
+ }
141
+ /**
142
+ * Render list-item marker (image or text).
143
+ */
144
+ async function renderListMarker(ctx, context, textRenderer, paint, container, styles) {
145
+ if (!(0, bitwise_1.contains)(container.styles.display, 2048 /* DISPLAY.LIST_ITEM */)) {
146
+ return;
147
+ }
148
+ if (container.styles.listStyleImage !== null) {
149
+ const img = container.styles.listStyleImage;
150
+ if (img.type === 0 /* CSSImageType.URL */) {
151
+ const url = img.url;
152
+ try {
153
+ const image = await context.cache.match(url);
154
+ ctx.drawImage(image, container.bounds.left - (image.width + 10), container.bounds.top);
155
+ }
156
+ catch (e) {
157
+ context.logger.error(`Error loading list-style-image ${url}`);
158
+ context.onError?.(e instanceof Error ? e : new Error(String(e)));
159
+ }
160
+ }
161
+ }
162
+ else if (paint.listValue && container.styles.listStyleType !== -1 /* LIST_STYLE_TYPE.NONE */) {
163
+ const [font] = textRenderer.createFontStyle(styles);
164
+ ctx.font = font;
165
+ ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
166
+ ctx.textBaseline = 'middle';
167
+ ctx.textAlign = 'right';
168
+ 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);
169
+ textRenderer.renderTextWithLetterSpacing(new text_1.TextBounds(paint.listValue, bounds), styles.letterSpacing, (0, line_height_1.computeLineHeight)(styles.lineHeight, styles.fontSize.number) / 2 + 2, styles.writingMode);
170
+ ctx.textBaseline = 'bottom';
171
+ ctx.textAlign = 'left';
172
+ }
173
+ }
174
+ /**
175
+ * Type guard for text input containers.
176
+ */
177
+ const isTextInputElement = (container) => {
178
+ if (container instanceof textarea_element_container_1.TextareaElementContainer) {
179
+ return true;
180
+ }
181
+ else if (container instanceof select_element_container_1.SelectElementContainer) {
182
+ return true;
183
+ }
184
+ else if (container instanceof input_element_container_1.InputElementContainer && container.type !== input_element_container_1.RADIO && container.type !== input_element_container_1.CHECKBOX) {
185
+ return true;
186
+ }
187
+ return false;
188
+ };
189
+ /**
190
+ * Map CSS text-align to Canvas textAlign.
191
+ */
192
+ const canvasTextAlign = (textAlign) => {
193
+ switch (textAlign) {
194
+ case 1 /* TEXT_ALIGN.CENTER */:
195
+ return 'center';
196
+ case 2 /* TEXT_ALIGN.RIGHT */:
197
+ return 'right';
198
+ case 0 /* TEXT_ALIGN.LEFT */:
199
+ default:
200
+ return 'left';
201
+ }
202
+ };
@@ -66,6 +66,9 @@ class EffectsRenderer {
66
66
  else if ((0, effects_1.isBlendEffect)(effect)) {
67
67
  this.ctx.globalCompositeOperation = effect.compositeOperation;
68
68
  }
69
+ else if ((0, effects_1.isFilterEffect)(effect)) {
70
+ this.ctx.filter = effect.filterString;
71
+ }
69
72
  this.activeEffects.push(effect);
70
73
  }
71
74
  /**
@@ -8,7 +8,11 @@ class ForeignObjectRenderer {
8
8
  this.context = context;
9
9
  this.options = options;
10
10
  this.canvas = options.canvas ? options.canvas : document.createElement('canvas');
11
- this.ctx = this.canvas.getContext('2d');
11
+ const ctx = this.canvas.getContext('2d');
12
+ if (!ctx) {
13
+ throw new Error('Failed to get 2D rendering context from canvas');
14
+ }
15
+ this.ctx = ctx;
12
16
  this.canvas.width = Math.floor(options.width * options.scale);
13
17
  this.canvas.height = Math.floor(options.height * options.scale);
14
18
  this.canvas.style.width = `${options.width}px`;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TextDecorationRenderer = void 0;
4
+ const color_utilities_1 = require("../../../css/types/color-utilities");
5
+ class TextDecorationRenderer {
6
+ constructor(ctx) {
7
+ this.ctx = ctx;
8
+ }
9
+ render(bounds, styles) {
10
+ this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.textDecorationColor || styles.color);
11
+ let thickness = 1;
12
+ if (typeof styles.textDecorationThickness === 'number') {
13
+ thickness = styles.textDecorationThickness;
14
+ }
15
+ else if (styles.textDecorationThickness === 'from-font') {
16
+ thickness = Math.max(1, Math.floor(styles.fontSize.number * 0.05));
17
+ }
18
+ let underlineOffset = 0;
19
+ if (typeof styles.textUnderlineOffset === 'number') {
20
+ underlineOffset = styles.textUnderlineOffset;
21
+ }
22
+ const decorationStyle = styles.textDecorationStyle;
23
+ styles.textDecorationLine.forEach((line) => {
24
+ let y = 0;
25
+ switch (line) {
26
+ case 1 /* TEXT_DECORATION_LINE.UNDERLINE */:
27
+ y = bounds.top + bounds.height - thickness + underlineOffset;
28
+ break;
29
+ case 2 /* TEXT_DECORATION_LINE.OVERLINE */:
30
+ y = bounds.top;
31
+ break;
32
+ case 3 /* TEXT_DECORATION_LINE.LINE_THROUGH */:
33
+ y = bounds.top + (bounds.height / 2 - thickness / 2);
34
+ break;
35
+ default:
36
+ return;
37
+ }
38
+ this.draw(bounds.left, y, bounds.width, thickness, decorationStyle);
39
+ });
40
+ }
41
+ draw(x, y, width, thickness, style) {
42
+ switch (style) {
43
+ case 0 /* TEXT_DECORATION_STYLE.SOLID */:
44
+ this.ctx.fillRect(x, y, width, thickness);
45
+ break;
46
+ case 1 /* TEXT_DECORATION_STYLE.DOUBLE */: {
47
+ const gap = Math.max(1, thickness);
48
+ this.ctx.fillRect(x, y, width, thickness);
49
+ this.ctx.fillRect(x, y + thickness + gap, width, thickness);
50
+ break;
51
+ }
52
+ case 2 /* TEXT_DECORATION_STYLE.DOTTED */:
53
+ this.drawPattern(x, y, width, thickness, [thickness, thickness * 2]);
54
+ break;
55
+ case 3 /* TEXT_DECORATION_STYLE.DASHED */:
56
+ this.drawPattern(x, y, width, thickness, [thickness * 3, thickness * 2]);
57
+ break;
58
+ case 4 /* TEXT_DECORATION_STYLE.WAVY */:
59
+ this.drawWavy(x, y, width, thickness);
60
+ break;
61
+ default:
62
+ this.ctx.fillRect(x, y, width, thickness);
63
+ }
64
+ }
65
+ drawPattern(x, y, width, thickness, dash) {
66
+ this.ctx.save();
67
+ this.ctx.beginPath();
68
+ this.ctx.setLineDash(dash);
69
+ this.ctx.lineWidth = thickness;
70
+ this.ctx.strokeStyle = this.ctx.fillStyle;
71
+ this.ctx.moveTo(x, y + thickness / 2);
72
+ this.ctx.lineTo(x + width, y + thickness / 2);
73
+ this.ctx.stroke();
74
+ this.ctx.restore();
75
+ }
76
+ drawWavy(x, y, width, thickness) {
77
+ this.ctx.save();
78
+ this.ctx.beginPath();
79
+ this.ctx.lineWidth = thickness;
80
+ this.ctx.strokeStyle = this.ctx.fillStyle;
81
+ const amplitude = thickness * 2;
82
+ const wavelength = thickness * 4;
83
+ let currentX = x;
84
+ this.ctx.moveTo(currentX, y + thickness / 2);
85
+ while (currentX < x + width) {
86
+ const nextX = Math.min(currentX + wavelength / 2, x + width);
87
+ this.ctx.quadraticCurveTo(currentX + wavelength / 4, y + thickness / 2 - amplitude, nextX, y + thickness / 2);
88
+ currentX = nextX;
89
+ if (currentX < x + width) {
90
+ const nextX2 = Math.min(currentX + wavelength / 2, x + width);
91
+ this.ctx.quadraticCurveTo(currentX + wavelength / 4, y + thickness / 2 + amplitude, nextX2, y + thickness / 2);
92
+ currentX = nextX2;
93
+ }
94
+ }
95
+ this.ctx.stroke();
96
+ this.ctx.restore();
97
+ }
98
+ }
99
+ exports.TextDecorationRenderer = TextDecorationRenderer;