html2canvas-pro 2.4.4 → 2.5.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.
- package/README.md +115 -13
- package/dist/html2canvas-pro.cjs +3246 -1601
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +3246 -1601
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +3242 -1597
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +5 -5
- package/dist/types/core/cache-storage.d.ts +4 -1
- package/dist/types/core/context.d.ts +10 -1
- package/dist/types/core/render-element.d.ts +11 -0
- package/dist/types/core/util.d.ts +6 -0
- package/dist/types/css/index.d.ts +77 -46
- package/dist/types/css/parse.d.ts +15 -0
- package/dist/types/css/property-descriptors/accent-color.d.ts +5 -0
- package/dist/types/css/property-descriptors/backdrop-filter.d.ts +9 -0
- package/dist/types/css/property-descriptors/background-blend-mode.d.ts +16 -16
- package/dist/types/css/property-descriptors/border-image-width.d.ts +36 -0
- package/dist/types/css/property-descriptors/clip-path.d.ts +16 -2
- package/dist/types/css/property-descriptors/isolation.d.ts +6 -0
- package/dist/types/css/property-descriptors/mask.d.ts +25 -0
- package/dist/types/css/property-descriptors/mix-blend-mode.d.ts +16 -16
- package/dist/types/css/property-descriptors/outline.d.ts +15 -0
- package/dist/types/css/property-descriptors/text-emphasis.d.ts +22 -0
- package/dist/types/css/property-descriptors/text-shadow.d.ts +1 -1
- package/dist/types/css/property-descriptors/webkit-box-reflect.d.ts +19 -0
- package/dist/types/css/property-descriptors/webkit-text-fill-color.d.ts +9 -0
- package/dist/types/css/syntax/token-constants.d.ts +1 -1
- package/dist/types/css/types/color-math.d.ts +7 -1
- package/dist/types/css/types/color-spaces/a98.d.ts +2 -1
- package/dist/types/css/types/color-spaces/p3.d.ts +5 -4
- package/dist/types/css/types/color-spaces/pro-photo.d.ts +5 -4
- package/dist/types/css/types/color-spaces/rec2020.d.ts +5 -4
- package/dist/types/css/types/color-spaces/srgb.d.ts +5 -4
- package/dist/types/css/types/color-utilities.d.ts +11 -11
- package/dist/types/css/types/color.d.ts +1 -0
- package/dist/types/css/types/functions/conic-gradient.d.ts +11 -0
- package/dist/types/css/types/functions/image-set.d.ts +11 -0
- package/dist/types/css/types/functions/repeating-radial-gradient.d.ts +8 -0
- package/dist/types/css/types/image.d.ts +15 -2
- package/dist/types/css/types/safe-eval.d.ts +0 -6
- package/dist/types/dom/copy-css-styles.d.ts +8 -0
- package/dist/types/dom/document-cloner.d.ts +3 -13
- package/dist/types/dom/iframe-mount.d.ts +37 -0
- package/dist/types/dom/pseudo-content.d.ts +33 -0
- package/dist/types/dom/replaced-elements/iframe-element-container.d.ts +2 -2
- package/dist/types/options.d.ts +8 -2
- package/dist/types/render/canvas/background-renderer.d.ts +19 -0
- package/dist/types/render/canvas/border-image-renderer.d.ts +50 -1
- package/dist/types/render/canvas/box-shadow-geometry.d.ts +3 -0
- package/dist/types/render/canvas/box-shadow-painter.d.ts +13 -0
- package/dist/types/render/canvas/box-shadow-transform.d.ts +16 -0
- package/dist/types/render/canvas/canvas-renderer.d.ts +21 -15
- package/dist/types/render/canvas/mask-renderer.d.ts +36 -0
- package/dist/types/render/canvas/text-clip-renderer.d.ts +36 -0
- package/dist/types/render/canvas/text-renderer.d.ts +22 -0
- package/dist/types/render/stacking-context.d.ts +4 -1
- package/package.json +39 -40
- package/dist/lib/config.js +0 -49
- package/dist/lib/core/abort-helper.js +0 -20
- package/dist/lib/core/background-parser.js +0 -41
- package/dist/lib/core/bitwise.js +0 -5
- package/dist/lib/core/cache-storage.js +0 -283
- package/dist/lib/core/config-assembler.js +0 -88
- package/dist/lib/core/constants.js +0 -25
- package/dist/lib/core/context.js +0 -19
- package/dist/lib/core/debugger.js +0 -27
- package/dist/lib/core/features.js +0 -190
- package/dist/lib/core/logger.js +0 -56
- package/dist/lib/core/lru-map.js +0 -66
- package/dist/lib/core/origin-checker.js +0 -53
- package/dist/lib/core/performance-monitor.js +0 -185
- package/dist/lib/core/render-element.js +0 -131
- package/dist/lib/core/util.js +0 -4
- package/dist/lib/core/validator.js +0 -500
- package/dist/lib/css/grouped/background-styles.js +0 -36
- package/dist/lib/css/grouped/border-styles.js +0 -75
- package/dist/lib/css/grouped/font-styles.js +0 -93
- package/dist/lib/css/grouped/layout-styles.js +0 -127
- package/dist/lib/css/index.js +0 -339
- package/dist/lib/css/layout/bounds.js +0 -52
- package/dist/lib/css/layout/text.js +0 -133
- package/dist/lib/css/property-descriptor.js +0 -2
- package/dist/lib/css/property-descriptors/background-blend-mode.js +0 -41
- package/dist/lib/css/property-descriptors/background-clip.js +0 -25
- package/dist/lib/css/property-descriptors/background-color.js +0 -10
- package/dist/lib/css/property-descriptors/background-image.js +0 -24
- package/dist/lib/css/property-descriptors/background-origin.js +0 -23
- package/dist/lib/css/property-descriptors/background-position.js +0 -19
- package/dist/lib/css/property-descriptors/background-repeat.js +0 -33
- package/dist/lib/css/property-descriptors/background-size.js +0 -23
- package/dist/lib/css/property-descriptors/border-color.js +0 -14
- package/dist/lib/css/property-descriptors/border-image-repeat.js +0 -42
- package/dist/lib/css/property-descriptors/border-image-slice.js +0 -45
- package/dist/lib/css/property-descriptors/border-image-source.js +0 -22
- package/dist/lib/css/property-descriptors/border-radius.js +0 -15
- package/dist/lib/css/property-descriptors/border-style.js +0 -26
- package/dist/lib/css/property-descriptors/border-width.js +0 -20
- package/dist/lib/css/property-descriptors/box-decoration-break.js +0 -18
- package/dist/lib/css/property-descriptors/box-shadow.js +0 -54
- package/dist/lib/css/property-descriptors/clip-path.js +0 -189
- package/dist/lib/css/property-descriptors/color.js +0 -10
- package/dist/lib/css/property-descriptors/content.js +0 -19
- package/dist/lib/css/property-descriptors/counter-increment.js +0 -35
- package/dist/lib/css/property-descriptors/counter-reset.js +0 -18
- package/dist/lib/css/property-descriptors/direction.js +0 -18
- package/dist/lib/css/property-descriptors/display.js +0 -81
- package/dist/lib/css/property-descriptors/duration.js +0 -14
- package/dist/lib/css/property-descriptors/filter.js +0 -86
- package/dist/lib/css/property-descriptors/float.js +0 -22
- package/dist/lib/css/property-descriptors/font-family.js +0 -47
- package/dist/lib/css/property-descriptors/font-size.js +0 -10
- package/dist/lib/css/property-descriptors/font-style.js +0 -20
- package/dist/lib/css/property-descriptors/font-variant-ligatures.js +0 -34
- package/dist/lib/css/property-descriptors/font-variant.js +0 -13
- package/dist/lib/css/property-descriptors/font-weight.js +0 -25
- package/dist/lib/css/property-descriptors/image-rendering.js +0 -33
- package/dist/lib/css/property-descriptors/letter-spacing.js +0 -21
- package/dist/lib/css/property-descriptors/line-break.js +0 -23
- package/dist/lib/css/property-descriptors/line-height.js +0 -24
- package/dist/lib/css/property-descriptors/list-style-image.js +0 -17
- package/dist/lib/css/property-descriptors/list-style-position.js +0 -18
- package/dist/lib/css/property-descriptors/list-style-type.js +0 -122
- package/dist/lib/css/property-descriptors/margin.js +0 -13
- package/dist/lib/css/property-descriptors/mix-blend-mode.js +0 -31
- package/dist/lib/css/property-descriptors/object-fit.js +0 -28
- package/dist/lib/css/property-descriptors/object-position.js +0 -42
- package/dist/lib/css/property-descriptors/opacity.js +0 -16
- package/dist/lib/css/property-descriptors/overflow-wrap.js +0 -18
- package/dist/lib/css/property-descriptors/overflow.js +0 -27
- package/dist/lib/css/property-descriptors/padding.js +0 -14
- package/dist/lib/css/property-descriptors/paint-order.js +0 -33
- package/dist/lib/css/property-descriptors/position.js +0 -22
- package/dist/lib/css/property-descriptors/quotes.js +0 -41
- package/dist/lib/css/property-descriptors/rotate.js +0 -27
- package/dist/lib/css/property-descriptors/text-align.js +0 -21
- package/dist/lib/css/property-descriptors/text-decoration-color.js +0 -10
- package/dist/lib/css/property-descriptors/text-decoration-line.js +0 -28
- package/dist/lib/css/property-descriptors/text-decoration-style.js +0 -24
- package/dist/lib/css/property-descriptors/text-decoration-thickness.js +0 -26
- package/dist/lib/css/property-descriptors/text-overflow.js +0 -18
- package/dist/lib/css/property-descriptors/text-shadow.js +0 -46
- package/dist/lib/css/property-descriptors/text-transform.js +0 -20
- package/dist/lib/css/property-descriptors/text-underline-offset.js +0 -23
- package/dist/lib/css/property-descriptors/transform-origin.js +0 -24
- package/dist/lib/css/property-descriptors/transform.js +0 -57
- package/dist/lib/css/property-descriptors/visibility.js +0 -20
- package/dist/lib/css/property-descriptors/webkit-line-clamp.js +0 -26
- package/dist/lib/css/property-descriptors/webkit-text-stroke-color.js +0 -10
- package/dist/lib/css/property-descriptors/webkit-text-stroke-width.js +0 -16
- package/dist/lib/css/property-descriptors/white-space.js +0 -32
- package/dist/lib/css/property-descriptors/word-break.js +0 -26
- package/dist/lib/css/property-descriptors/writing-mode.js +0 -28
- package/dist/lib/css/property-descriptors/z-index.js +0 -19
- package/dist/lib/css/property-descriptors/zoom.js +0 -18
- package/dist/lib/css/syntax/parser.js +0 -141
- package/dist/lib/css/syntax/token-constants.js +0 -214
- package/dist/lib/css/syntax/token-singletons.js +0 -36
- package/dist/lib/css/syntax/token-types.js +0 -10
- package/dist/lib/css/syntax/tokenizer.js +0 -502
- package/dist/lib/css/type-descriptor.js +0 -2
- package/dist/lib/css/types/angle.js +0 -80
- package/dist/lib/css/types/color-math.js +0 -25
- package/dist/lib/css/types/color-spaces/a98.js +0 -78
- package/dist/lib/css/types/color-spaces/p3.js +0 -86
- package/dist/lib/css/types/color-spaces/pro-photo.js +0 -83
- package/dist/lib/css/types/color-spaces/rec2020.js +0 -85
- package/dist/lib/css/types/color-spaces/srgb.js +0 -79
- package/dist/lib/css/types/color-utilities.js +0 -342
- package/dist/lib/css/types/color.js +0 -400
- package/dist/lib/css/types/functions/-prefix-linear-gradient.js +0 -32
- package/dist/lib/css/types/functions/-prefix-radial-gradient.js +0 -83
- package/dist/lib/css/types/functions/-webkit-gradient.js +0 -59
- package/dist/lib/css/types/functions/counter.js +0 -367
- package/dist/lib/css/types/functions/gradient.js +0 -163
- package/dist/lib/css/types/functions/linear-gradient.js +0 -27
- package/dist/lib/css/types/functions/radial-gradient.js +0 -90
- package/dist/lib/css/types/functions/repeating-linear-gradient.js +0 -9
- package/dist/lib/css/types/image.js +0 -62
- package/dist/lib/css/types/index.js +0 -2
- package/dist/lib/css/types/length-percentage.js +0 -197
- package/dist/lib/css/types/length.js +0 -5
- package/dist/lib/css/types/safe-eval.js +0 -80
- package/dist/lib/css/types/time.js +0 -17
- package/dist/lib/dom/document-cloner.js +0 -618
- package/dist/lib/dom/dom-normalizer.js +0 -115
- package/dist/lib/dom/element-container.js +0 -71
- package/dist/lib/dom/elements/li-element-container.js +0 -11
- package/dist/lib/dom/elements/ol-element-container.js +0 -12
- package/dist/lib/dom/elements/select-element-container.js +0 -12
- package/dist/lib/dom/elements/textarea-element-container.js +0 -11
- package/dist/lib/dom/node-parser.js +0 -125
- package/dist/lib/dom/node-type-guards.js +0 -81
- package/dist/lib/dom/replaced-elements/canvas-element-container.js +0 -13
- package/dist/lib/dom/replaced-elements/iframe-element-container.js +0 -38
- package/dist/lib/dom/replaced-elements/image-element-container.js +0 -14
- package/dist/lib/dom/replaced-elements/index.js +0 -2
- package/dist/lib/dom/replaced-elements/input-element-container.js +0 -89
- package/dist/lib/dom/replaced-elements/svg-element-container.js +0 -57
- package/dist/lib/dom/slot-cloner.js +0 -176
- package/dist/lib/dom/text-container.js +0 -35
- package/dist/lib/index.js +0 -49
- package/dist/lib/options.js +0 -2
- package/dist/lib/render/background.js +0 -188
- package/dist/lib/render/bezier-curve.js +0 -34
- package/dist/lib/render/border.js +0 -104
- package/dist/lib/render/bound-curves.js +0 -188
- package/dist/lib/render/box-sizing.js +0 -20
- package/dist/lib/render/canvas/background-renderer.js +0 -338
- package/dist/lib/render/canvas/border-image-renderer.js +0 -153
- package/dist/lib/render/canvas/border-renderer.js +0 -184
- package/dist/lib/render/canvas/canvas-path.js +0 -25
- package/dist/lib/render/canvas/canvas-renderer.js +0 -633
- package/dist/lib/render/canvas/content-renderer.js +0 -270
- package/dist/lib/render/canvas/effects-renderer.js +0 -171
- package/dist/lib/render/canvas/filter-surface.js +0 -270
- package/dist/lib/render/canvas/font-utils.js +0 -46
- package/dist/lib/render/canvas/foreignobject-renderer.js +0 -38
- package/dist/lib/render/canvas/surface-bounds.js +0 -64
- package/dist/lib/render/canvas/text/text-decoration-renderer.js +0 -99
- package/dist/lib/render/canvas/text-renderer.js +0 -486
- package/dist/lib/render/effects.js +0 -231
- package/dist/lib/render/font-metrics.js +0 -53
- package/dist/lib/render/object-fit.js +0 -91
- package/dist/lib/render/path.js +0 -26
- package/dist/lib/render/renderer-interface.js +0 -2
- package/dist/lib/render/stacking-context.js +0 -341
- package/dist/lib/render/vector.js +0 -16
|
@@ -1,633 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CanvasRenderer = void 0;
|
|
4
|
-
const stacking_context_1 = require("../stacking-context");
|
|
5
|
-
const color_utilities_1 = require("../../css/types/color-utilities");
|
|
6
|
-
const path_1 = require("../path");
|
|
7
|
-
const bound_curves_1 = require("../bound-curves");
|
|
8
|
-
const background_1 = require("../background");
|
|
9
|
-
const box_sizing_1 = require("../box-sizing");
|
|
10
|
-
const effects_1 = require("../effects");
|
|
11
|
-
const mix_blend_mode_1 = require("../../css/property-descriptors/mix-blend-mode");
|
|
12
|
-
const filter_surface_1 = require("./filter-surface");
|
|
13
|
-
const surface_bounds_1 = require("./surface-bounds");
|
|
14
|
-
const bounds_1 = require("../../css/layout/bounds");
|
|
15
|
-
const bitwise_1 = require("../../core/bitwise");
|
|
16
|
-
const text_renderer_1 = require("./text-renderer");
|
|
17
|
-
const background_renderer_1 = require("./background-renderer");
|
|
18
|
-
const border_renderer_1 = require("./border-renderer");
|
|
19
|
-
const border_image_renderer_1 = require("./border-image-renderer");
|
|
20
|
-
const effects_renderer_1 = require("./effects-renderer");
|
|
21
|
-
const canvas_path_1 = require("./canvas-path");
|
|
22
|
-
const object_fit_1 = require("../object-fit");
|
|
23
|
-
const content_renderer_1 = require("./content-renderer");
|
|
24
|
-
const constants_1 = require("../../core/constants");
|
|
25
|
-
const writing_mode_1 = require("../../css/property-descriptors/writing-mode");
|
|
26
|
-
const font_utils_1 = require("./font-utils");
|
|
27
|
-
const text_1 = require("../../css/layout/text");
|
|
28
|
-
class CanvasRenderer {
|
|
29
|
-
constructor(context, options, surfaceRoot, surfaceBudget = { pixels: 0 }) {
|
|
30
|
-
this.surfaceRoot = surfaceRoot;
|
|
31
|
-
this.surfaceBudget = surfaceBudget;
|
|
32
|
-
this.legacySubtree = false;
|
|
33
|
-
this.context = context;
|
|
34
|
-
this.options = options;
|
|
35
|
-
this.canvas = options.canvas ? options.canvas : document.createElement('canvas');
|
|
36
|
-
const ctx = this.canvas.getContext('2d');
|
|
37
|
-
if (!ctx) {
|
|
38
|
-
if (!options.canvas)
|
|
39
|
-
(0, filter_surface_1.releaseSurface)(this.canvas);
|
|
40
|
-
if (surfaceRoot)
|
|
41
|
-
throw new filter_surface_1.FilterSurfaceError('Filter surface canvas is unavailable');
|
|
42
|
-
throw new Error('Failed to get 2D rendering context from canvas');
|
|
43
|
-
}
|
|
44
|
-
this.ctx = ctx;
|
|
45
|
-
if (!options.canvas) {
|
|
46
|
-
this.canvas.width = Math.floor(options.width * options.scale);
|
|
47
|
-
this.canvas.height = Math.floor(options.height * options.scale);
|
|
48
|
-
this.canvas.style.width = `${options.width}px`;
|
|
49
|
-
this.canvas.style.height = `${options.height}px`;
|
|
50
|
-
}
|
|
51
|
-
this.ctx.scale(this.options.scale, this.options.scale);
|
|
52
|
-
this.ctx.translate(-options.x, -options.y);
|
|
53
|
-
this.ctx.textBaseline = 'bottom';
|
|
54
|
-
// Set image smoothing options
|
|
55
|
-
if (options.imageSmoothing !== undefined) {
|
|
56
|
-
this.ctx.imageSmoothingEnabled = options.imageSmoothing;
|
|
57
|
-
}
|
|
58
|
-
if (options.imageSmoothingQuality) {
|
|
59
|
-
this.ctx.imageSmoothingQuality = options.imageSmoothingQuality;
|
|
60
|
-
}
|
|
61
|
-
// Initialize specialized renderers
|
|
62
|
-
this.backgroundRenderer = new background_renderer_1.BackgroundRenderer({
|
|
63
|
-
ctx: this.ctx,
|
|
64
|
-
context: this.context,
|
|
65
|
-
canvas: this.canvas,
|
|
66
|
-
options: {
|
|
67
|
-
width: options.width,
|
|
68
|
-
height: options.height,
|
|
69
|
-
scale: options.scale
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
this.borderRenderer = new border_renderer_1.BorderRenderer({ ctx: this.ctx }, {
|
|
73
|
-
path: (paths) => this.path(paths),
|
|
74
|
-
formatPath: (paths) => this.formatPath(paths)
|
|
75
|
-
});
|
|
76
|
-
this.borderImageRenderer = new border_image_renderer_1.BorderImageRenderer(this.ctx);
|
|
77
|
-
this.effectsRenderer = new effects_renderer_1.EffectsRenderer({ ctx: this.ctx }, { path: (paths) => this.path(paths) });
|
|
78
|
-
this.textRenderer = new text_renderer_1.TextRenderer({
|
|
79
|
-
ctx: this.ctx,
|
|
80
|
-
options: { scale: options.scale }
|
|
81
|
-
});
|
|
82
|
-
this.context.logger.debug(`Canvas renderer initialized (${options.width}x${options.height}) with scale ${options.scale}`);
|
|
83
|
-
}
|
|
84
|
-
async renderStack(stack) {
|
|
85
|
-
const styles = stack.element.container.styles;
|
|
86
|
-
if (styles.isVisible()) {
|
|
87
|
-
const filter = (0, filter_surface_1.parseSimpleFilter)(styles.filter);
|
|
88
|
-
if (stack.element !== this.surfaceRoot &&
|
|
89
|
-
!this.legacySubtree &&
|
|
90
|
-
filter &&
|
|
91
|
-
(styles.filter || styles.opacity < 1) &&
|
|
92
|
-
this.canComposite(stack.element)) {
|
|
93
|
-
if (!(await this.renderCompositedStack(stack, filter))) {
|
|
94
|
-
// A rejected optional surface must preserve the previous subtree path.
|
|
95
|
-
this.legacySubtree = true;
|
|
96
|
-
try {
|
|
97
|
-
await this.renderStackContent(stack);
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
this.legacySubtree = false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
await this.renderStackContent(stack);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Draft scope: transformed, blended and clip-path subtrees keep the old path.
|
|
109
|
-
// The surface root owns its filter/opacity; ancestors are applied at composition.
|
|
110
|
-
canComposite(paint) {
|
|
111
|
-
const supported = (container) => {
|
|
112
|
-
const styles = container.styles;
|
|
113
|
-
return (!styles.isTransformed() &&
|
|
114
|
-
styles.zoom === 1 &&
|
|
115
|
-
styles.mixBlendMode === mix_blend_mode_1.MIX_BLEND_MODE.NORMAL &&
|
|
116
|
-
styles.clipPath.type === 0 /* CLIP_PATH_TYPE.NONE */ &&
|
|
117
|
-
!(0, bitwise_1.contains)(styles.display, 2048 /* DISPLAY.LIST_ITEM */) &&
|
|
118
|
-
(0, filter_surface_1.parseSimpleFilter)(styles.filter) !== null);
|
|
119
|
-
};
|
|
120
|
-
const subtree = (container) => supported(container) && container.elements.every(subtree);
|
|
121
|
-
let ancestor = paint.parent;
|
|
122
|
-
while (ancestor && ancestor !== this.surfaceRoot) {
|
|
123
|
-
if (!supported(ancestor.container))
|
|
124
|
-
return false;
|
|
125
|
-
ancestor = ancestor.parent;
|
|
126
|
-
}
|
|
127
|
-
return subtree(paint.container);
|
|
128
|
-
}
|
|
129
|
-
async renderCompositedStack(stack, filter) {
|
|
130
|
-
const signal = this.options.signal;
|
|
131
|
-
if (signal?.aborted)
|
|
132
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
133
|
-
const bounds = (0, surface_bounds_1.cropSurface)((0, surface_bounds_1.surfaceBounds)(stack.element.container), new bounds_1.Bounds(this.options.x, this.options.y, this.options.width, this.options.height), (0, filter_surface_1.filterOutset)(filter), this.options.scale);
|
|
134
|
-
if (!bounds.width || !bounds.height)
|
|
135
|
-
return true;
|
|
136
|
-
const reserved = (0, surface_bounds_1.reserveSurface)(this.surfaceBudget, Math.ceil(bounds.width * this.options.scale), Math.ceil(bounds.height * this.options.scale));
|
|
137
|
-
if (!reserved) {
|
|
138
|
-
this.context.logger.info('Filter surface budget exceeded; using the existing renderer');
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
const options = {
|
|
142
|
-
...this.options,
|
|
143
|
-
canvas: undefined,
|
|
144
|
-
backgroundColor: null,
|
|
145
|
-
x: bounds.left,
|
|
146
|
-
y: bounds.top,
|
|
147
|
-
width: bounds.width,
|
|
148
|
-
height: bounds.height
|
|
149
|
-
};
|
|
150
|
-
let source;
|
|
151
|
-
let filtered;
|
|
152
|
-
try {
|
|
153
|
-
source = new CanvasRenderer(this.context, options, stack.element, this.surfaceBudget);
|
|
154
|
-
await source.renderStackContent(stack);
|
|
155
|
-
source.effectsRenderer.applyEffects([]);
|
|
156
|
-
filtered = await (0, filter_surface_1.renderFilterSurface)(source.canvas, filter, stack.element.container.styles.opacity, options.scale, signal);
|
|
157
|
-
if (signal?.aborted)
|
|
158
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
159
|
-
const own = stack.element.effects;
|
|
160
|
-
const effects = stack.element
|
|
161
|
-
.getEffects(4 /* EffectTarget.CONTENT */, this.surfaceRoot)
|
|
162
|
-
.filter((effect) => !own.includes(effect) ||
|
|
163
|
-
(!(0, effects_1.isFilterEffect)(effect) && !(0, effects_1.isOpacityEffect)(effect) && !(0, effects_1.isClipEffect)(effect)));
|
|
164
|
-
this.effectsRenderer.applyEffects(effects);
|
|
165
|
-
this.ctx.drawImage(filtered, options.x, options.y, filtered.width / options.scale, filtered.height / options.scale);
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
if (!(error instanceof filter_surface_1.FilterSurfaceError))
|
|
170
|
-
throw error;
|
|
171
|
-
this.context.logger.info(`${error.message}; using the existing renderer`);
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
finally {
|
|
175
|
-
if (source)
|
|
176
|
-
(0, filter_surface_1.releaseSurface)(source.canvas);
|
|
177
|
-
if (filtered)
|
|
178
|
-
(0, filter_surface_1.releaseSurface)(filtered);
|
|
179
|
-
this.surfaceBudget.pixels -= reserved;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
async renderNode(paint) {
|
|
183
|
-
if (paint.container.debugRender) {
|
|
184
|
-
debugger;
|
|
185
|
-
}
|
|
186
|
-
if (paint.container.styles.isVisible()) {
|
|
187
|
-
await this.renderNodeBackgroundAndBorders(paint);
|
|
188
|
-
await this.renderNodeContent(paint);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Helper method to render text with paint order support
|
|
193
|
-
* Reduces code duplication in line-clamp and normal rendering
|
|
194
|
-
*/
|
|
195
|
-
// Helper method to truncate text and add ellipsis if needed
|
|
196
|
-
renderReplacedElement(container, curves, image) {
|
|
197
|
-
const intrinsicWidth = image.naturalWidth || container.intrinsicWidth;
|
|
198
|
-
const intrinsicHeight = image.naturalHeight || container.intrinsicHeight;
|
|
199
|
-
if (image && intrinsicWidth > 0 && intrinsicHeight > 0) {
|
|
200
|
-
const box = (0, box_sizing_1.contentBox)(container);
|
|
201
|
-
const path = (0, bound_curves_1.calculatePaddingBoxPath)(curves);
|
|
202
|
-
this.path(path);
|
|
203
|
-
this.ctx.save();
|
|
204
|
-
this.ctx.clip();
|
|
205
|
-
const { sx, sy, sw, sh, dx, dy, dw, dh } = (0, object_fit_1.calculateObjectFitRendering)(intrinsicWidth, intrinsicHeight, box, container.styles.objectFit, container.styles.objectPosition);
|
|
206
|
-
this.ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
207
|
-
this.ctx.restore();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
async renderNodeContent(paint) {
|
|
211
|
-
this.effectsRenderer.applyEffects(paint.getEffects(4 /* EffectTarget.CONTENT */, this.surfaceRoot));
|
|
212
|
-
const container = paint.container;
|
|
213
|
-
const curves = paint.curves;
|
|
214
|
-
const styles = container.styles;
|
|
215
|
-
// Use content box for text overflow calculation (excludes padding and border)
|
|
216
|
-
const textBounds = (0, box_sizing_1.contentBox)(container);
|
|
217
|
-
for (const child of container.textNodes) {
|
|
218
|
-
await this.textRenderer.renderTextNode(child, styles, textBounds);
|
|
219
|
-
}
|
|
220
|
-
await (0, content_renderer_1.renderReplacedElements)(this.ctx, this.context, {
|
|
221
|
-
scale: this.options.scale,
|
|
222
|
-
backgroundColor: this.options.backgroundColor,
|
|
223
|
-
x: this.options.x,
|
|
224
|
-
y: this.options.y,
|
|
225
|
-
width: this.options.width,
|
|
226
|
-
height: this.options.height
|
|
227
|
-
}, (ctx, opts) => new CanvasRenderer(ctx, opts), container, curves, styles, (c, cv, img) => this.renderReplacedElement(c, cv, img));
|
|
228
|
-
(0, content_renderer_1.renderFormElements)(this.ctx, this.textRenderer, this.path.bind(this), container, styles);
|
|
229
|
-
await (0, content_renderer_1.renderListMarker)(this.ctx, this.context, this.textRenderer, paint, container, styles);
|
|
230
|
-
}
|
|
231
|
-
async renderStackContent(stack) {
|
|
232
|
-
if (stack.element.container.debugRender) {
|
|
233
|
-
debugger;
|
|
234
|
-
}
|
|
235
|
-
const signal = this.options.signal;
|
|
236
|
-
// https://www.w3.org/TR/css-position-3/#painting-order
|
|
237
|
-
// 1. the background and borders of the element forming the stacking context.
|
|
238
|
-
await this.renderNodeBackgroundAndBorders(stack.element);
|
|
239
|
-
// 2. the child stacking contexts with negative stack levels (most negative first).
|
|
240
|
-
for (const child of stack.negativeZIndex) {
|
|
241
|
-
if (signal?.aborted)
|
|
242
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
243
|
-
await this.renderStack(child);
|
|
244
|
-
}
|
|
245
|
-
// 3. For all its in-flow, non-positioned, block-level descendants in tree order:
|
|
246
|
-
if (signal?.aborted)
|
|
247
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
248
|
-
await this.renderNodeContent(stack.element);
|
|
249
|
-
for (const child of stack.nonInlineLevel) {
|
|
250
|
-
await this.renderNode(child);
|
|
251
|
-
}
|
|
252
|
-
// 4. All non-positioned floating descendants, in tree order. For each one of these,
|
|
253
|
-
// treat the element as if it created a new stacking context, but any positioned descendants and descendants
|
|
254
|
-
// which actually create a new stacking context should be considered part of the parent stacking context,
|
|
255
|
-
// not this new one.
|
|
256
|
-
for (const child of stack.nonPositionedFloats) {
|
|
257
|
-
if (signal?.aborted)
|
|
258
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
259
|
-
await this.renderStack(child);
|
|
260
|
-
}
|
|
261
|
-
// 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
|
|
262
|
-
for (const child of stack.nonPositionedInlineLevel) {
|
|
263
|
-
if (signal?.aborted)
|
|
264
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
265
|
-
await this.renderStack(child);
|
|
266
|
-
}
|
|
267
|
-
for (const child of stack.inlineLevel) {
|
|
268
|
-
await this.renderNode(child);
|
|
269
|
-
}
|
|
270
|
-
// 6. All positioned, opacity or transform descendants, in tree order that fall into the following categories:
|
|
271
|
-
// All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order.
|
|
272
|
-
// For those with 'z-index: auto', treat the element as if it created a new stacking context,
|
|
273
|
-
// but any positioned descendants and descendants which actually create a new stacking context should be
|
|
274
|
-
// considered part of the parent stacking context, not this new one. For those with 'z-index: 0',
|
|
275
|
-
// treat the stacking context generated atomically.
|
|
276
|
-
//
|
|
277
|
-
// All opacity descendants with opacity less than 1
|
|
278
|
-
//
|
|
279
|
-
// All transform descendants with transform other than none
|
|
280
|
-
for (const child of stack.zeroOrAutoZIndexOrTransformedOrOpacity) {
|
|
281
|
-
if (signal?.aborted)
|
|
282
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
283
|
-
await this.renderStack(child);
|
|
284
|
-
}
|
|
285
|
-
// 7. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index
|
|
286
|
-
// order (smallest first) then tree order.
|
|
287
|
-
for (const child of stack.positiveZIndex) {
|
|
288
|
-
if (signal?.aborted)
|
|
289
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
290
|
-
await this.renderStack(child);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
mask(paths) {
|
|
294
|
-
this.ctx.beginPath();
|
|
295
|
-
// Anchor the mask rectangle to (options.x, options.y) instead of (0, 0)
|
|
296
|
-
// because ctx.translate(-options.x, -options.y) shifts the coordinate system.
|
|
297
|
-
// Fix for Issue #230: inset box-shadow rendering was inverted for elements
|
|
298
|
-
// not at document origin due to the mask covering the wrong region.
|
|
299
|
-
const x = this.options.x;
|
|
300
|
-
const y = this.options.y;
|
|
301
|
-
this.ctx.moveTo(x, y);
|
|
302
|
-
this.ctx.lineTo(x + this.options.width, y);
|
|
303
|
-
this.ctx.lineTo(x + this.options.width, y + this.options.height);
|
|
304
|
-
this.ctx.lineTo(x, y + this.options.height);
|
|
305
|
-
this.ctx.lineTo(x, y);
|
|
306
|
-
this.formatPath(paths.slice(0).reverse());
|
|
307
|
-
this.ctx.closePath();
|
|
308
|
-
}
|
|
309
|
-
path(paths) {
|
|
310
|
-
(0, canvas_path_1.createCanvasPath)(this.ctx, paths);
|
|
311
|
-
}
|
|
312
|
-
formatPath(paths) {
|
|
313
|
-
(0, canvas_path_1.formatCanvasPath)(this.ctx, paths);
|
|
314
|
-
}
|
|
315
|
-
async renderSurfaceBoxShadow(paint, shadow) {
|
|
316
|
-
// Rasterize a visible silhouette, then blur it. Linux WebKit can drop the
|
|
317
|
-
// interior of Canvas shadows whose solid mask is outside a cropped canvas.
|
|
318
|
-
const spread = shadow.spread.number;
|
|
319
|
-
const blur = shadow.blur.number / 2;
|
|
320
|
-
const margin = Math.ceil(3 * blur) + 1;
|
|
321
|
-
const box = paint.container.bounds.add(-spread, -spread, 2 * spread, 2 * spread);
|
|
322
|
-
if (box.width <= 0 || box.height <= 0)
|
|
323
|
-
return;
|
|
324
|
-
const scale = this.options.scale;
|
|
325
|
-
const bounds = (0, surface_bounds_1.cropSurface)(box.add(-margin, -margin, 2 * margin, 2 * margin), new bounds_1.Bounds(this.options.x - shadow.offsetX.number, this.options.y - shadow.offsetY.number, this.options.width, this.options.height), margin, scale);
|
|
326
|
-
if (!bounds.width || !bounds.height)
|
|
327
|
-
return;
|
|
328
|
-
const width = Math.ceil(bounds.width * scale), height = Math.ceil(bounds.height * scale);
|
|
329
|
-
const reserved = (0, surface_bounds_1.reserveSurface)(this.surfaceBudget, width, height);
|
|
330
|
-
if (!reserved)
|
|
331
|
-
throw new filter_surface_1.FilterSurfaceError('Box shadow surface budget exceeded');
|
|
332
|
-
let source;
|
|
333
|
-
let filtered;
|
|
334
|
-
try {
|
|
335
|
-
source = document.createElement('canvas');
|
|
336
|
-
source.width = width;
|
|
337
|
-
source.height = height;
|
|
338
|
-
const ctx = source.getContext('2d');
|
|
339
|
-
if (!ctx)
|
|
340
|
-
throw new filter_surface_1.FilterSurfaceError('Box shadow surface canvas is unavailable');
|
|
341
|
-
ctx.scale(scale, scale);
|
|
342
|
-
ctx.translate(-bounds.left, -bounds.top);
|
|
343
|
-
(0, canvas_path_1.createCanvasPath)(ctx, (0, path_1.transformPath)((0, bound_curves_1.calculateBorderBoxPath)(paint.curves), -spread, -spread, 2 * spread, 2 * spread));
|
|
344
|
-
ctx.fillStyle = (0, color_utilities_1.asString)(shadow.color);
|
|
345
|
-
ctx.fill();
|
|
346
|
-
filtered = await (0, filter_surface_1.renderFilterSurface)(source, { blur }, 1, scale, this.options.signal);
|
|
347
|
-
this.ctx.save();
|
|
348
|
-
try {
|
|
349
|
-
this.ctx.beginPath();
|
|
350
|
-
this.ctx.rect(this.options.x, this.options.y, this.options.width, this.options.height);
|
|
351
|
-
(0, canvas_path_1.formatCanvasPath)(this.ctx, (0, bound_curves_1.calculateBorderBoxPath)(paint.curves));
|
|
352
|
-
this.ctx.closePath();
|
|
353
|
-
this.ctx.clip('evenodd');
|
|
354
|
-
this.ctx.drawImage(filtered, bounds.left + shadow.offsetX.number, bounds.top + shadow.offsetY.number, filtered.width / scale, filtered.height / scale);
|
|
355
|
-
}
|
|
356
|
-
finally {
|
|
357
|
-
this.ctx.restore();
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
finally {
|
|
361
|
-
if (source)
|
|
362
|
-
(0, filter_surface_1.releaseSurface)(source);
|
|
363
|
-
if (filtered)
|
|
364
|
-
(0, filter_surface_1.releaseSurface)(filtered);
|
|
365
|
-
this.surfaceBudget.pixels -= reserved;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
async renderNodeBackgroundAndBorders(paint) {
|
|
369
|
-
this.effectsRenderer.applyEffects(paint.getEffects(2 /* EffectTarget.BACKGROUND_BORDERS */, this.surfaceRoot));
|
|
370
|
-
const styles = paint.container.styles;
|
|
371
|
-
const hasBackground = !(0, color_utilities_1.isTransparent)(styles.backgroundColor) || styles.backgroundImage.length;
|
|
372
|
-
const hasTextClip = hasTextBackgroundClip(styles);
|
|
373
|
-
const borders = [
|
|
374
|
-
{ style: styles.borderTopStyle, color: styles.borderTopColor, width: styles.borderTopWidth },
|
|
375
|
-
{ style: styles.borderRightStyle, color: styles.borderRightColor, width: styles.borderRightWidth },
|
|
376
|
-
{ style: styles.borderBottomStyle, color: styles.borderBottomColor, width: styles.borderBottomWidth },
|
|
377
|
-
{ style: styles.borderLeftStyle, color: styles.borderLeftColor, width: styles.borderLeftWidth }
|
|
378
|
-
];
|
|
379
|
-
const backgroundClipValue = (0, background_1.getBackgroundValueForIndex)(styles.backgroundClip, 0);
|
|
380
|
-
const backgroundPaintingArea = calculateBackgroundCurvedPaintingArea(backgroundClipValue, paint.curves);
|
|
381
|
-
if (hasBackground || styles.boxShadow.length) {
|
|
382
|
-
// Handle background-clip: text
|
|
383
|
-
if (hasTextClip && paint.container.textNodes.length > 0) {
|
|
384
|
-
await this.renderTextClippedBackground(paint);
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
this.ctx.save();
|
|
388
|
-
this.path(backgroundPaintingArea);
|
|
389
|
-
this.ctx.clip();
|
|
390
|
-
if (!(0, color_utilities_1.isTransparent)(styles.backgroundColor)) {
|
|
391
|
-
this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.backgroundColor);
|
|
392
|
-
this.ctx.fill();
|
|
393
|
-
}
|
|
394
|
-
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
395
|
-
this.ctx.restore();
|
|
396
|
-
}
|
|
397
|
-
for (const shadow of styles.boxShadow.slice(0).reverse()) {
|
|
398
|
-
if (this.surfaceRoot && !shadow.inset) {
|
|
399
|
-
await this.renderSurfaceBoxShadow(paint, shadow);
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
this.ctx.save();
|
|
403
|
-
const borderBoxArea = (0, bound_curves_1.calculateBorderBoxPath)(paint.curves);
|
|
404
|
-
const maskOffset = shadow.inset ? 0 : constants_1.SHADOW_MASK_OFFSET;
|
|
405
|
-
const shadowPaintingArea = (0, path_1.transformPath)(borderBoxArea, -maskOffset + (shadow.inset ? 1 : -1) * shadow.spread.number, (shadow.inset ? 1 : -1) * shadow.spread.number, shadow.spread.number * (shadow.inset ? -2 : 2), shadow.spread.number * (shadow.inset ? -2 : 2));
|
|
406
|
-
if (shadow.inset) {
|
|
407
|
-
this.path(borderBoxArea);
|
|
408
|
-
this.ctx.clip();
|
|
409
|
-
this.mask(shadowPaintingArea);
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
this.mask(borderBoxArea);
|
|
413
|
-
this.ctx.clip();
|
|
414
|
-
this.path(shadowPaintingArea);
|
|
415
|
-
}
|
|
416
|
-
// Canvas shadow metrics ignore the transform. Surface sources
|
|
417
|
-
// need capture-pixel offsets, including the displaced mask.
|
|
418
|
-
const shadowScale = this.surfaceRoot ? this.options.scale : 1;
|
|
419
|
-
this.ctx.shadowOffsetX = (shadow.offsetX.number + maskOffset) * shadowScale;
|
|
420
|
-
this.ctx.shadowOffsetY = shadow.offsetY.number * shadowScale;
|
|
421
|
-
this.ctx.shadowColor = (0, color_utilities_1.asString)(shadow.color);
|
|
422
|
-
this.ctx.shadowBlur = shadow.blur.number * shadowScale;
|
|
423
|
-
this.ctx.fillStyle = shadow.inset ? (0, color_utilities_1.asString)(shadow.color) : 'rgba(0,0,0,1)';
|
|
424
|
-
this.ctx.fill();
|
|
425
|
-
this.ctx.restore();
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
// Render border-image if present (replaces traditional borders per CSS spec)
|
|
429
|
-
if (styles.borderImageSource) {
|
|
430
|
-
const source = styles.borderImageSource;
|
|
431
|
-
if (source.type === 0 /* CSSImageType.URL */) {
|
|
432
|
-
const url = source.url;
|
|
433
|
-
try {
|
|
434
|
-
const image = await this.context.cache.match(url);
|
|
435
|
-
if (image) {
|
|
436
|
-
const bounds = paint.container.bounds;
|
|
437
|
-
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));
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
catch (e) {
|
|
441
|
-
this.context.logger.error(`Error loading border-image ${url}`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
// When border-image is present, skip regular border rendering
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
let side = 0;
|
|
448
|
-
for (const border of borders) {
|
|
449
|
-
if (border.style !== 0 /* BORDER_STYLE.NONE */ && !(0, color_utilities_1.isTransparent)(border.color) && border.width > 0) {
|
|
450
|
-
const renderBorderSide = async () => {
|
|
451
|
-
if (border.style === 2 /* BORDER_STYLE.DASHED */) {
|
|
452
|
-
await this.borderRenderer.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 2 /* BORDER_STYLE.DASHED */);
|
|
453
|
-
}
|
|
454
|
-
else if (border.style === 3 /* BORDER_STYLE.DOTTED */) {
|
|
455
|
-
await this.borderRenderer.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 3 /* BORDER_STYLE.DOTTED */);
|
|
456
|
-
}
|
|
457
|
-
else if (border.style === 4 /* BORDER_STYLE.DOUBLE */) {
|
|
458
|
-
await this.borderRenderer.renderDoubleBorder(border.color, border.width, side, paint.curves);
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
await this.borderRenderer.renderSolidBorder(border.color, side, paint.curves);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
// Browsers break a <fieldset>'s top border where its <legend>
|
|
465
|
-
// sits, instead of drawing the border through the legend (issue
|
|
466
|
-
// #227). Render the top border in two clipped segments around
|
|
467
|
-
// the legend's horizontal span.
|
|
468
|
-
const legend = paint.container.legendBounds;
|
|
469
|
-
if (side === 0 && legend) {
|
|
470
|
-
const bounds = paint.container.bounds;
|
|
471
|
-
const legendLeft = legend.left - bounds.left;
|
|
472
|
-
const legendRight = legend.left + legend.width - bounds.left;
|
|
473
|
-
if (legendLeft > 0) {
|
|
474
|
-
this.ctx.save();
|
|
475
|
-
this.ctx.beginPath();
|
|
476
|
-
this.ctx.rect(bounds.left, bounds.top, legendLeft, bounds.height);
|
|
477
|
-
this.ctx.clip();
|
|
478
|
-
await renderBorderSide();
|
|
479
|
-
this.ctx.restore();
|
|
480
|
-
}
|
|
481
|
-
const rightStart = bounds.left + legendRight;
|
|
482
|
-
const rightWidth = bounds.width - legendRight;
|
|
483
|
-
if (rightWidth > 0) {
|
|
484
|
-
this.ctx.save();
|
|
485
|
-
this.ctx.beginPath();
|
|
486
|
-
this.ctx.rect(rightStart, bounds.top, rightWidth, bounds.height);
|
|
487
|
-
this.ctx.clip();
|
|
488
|
-
await renderBorderSide();
|
|
489
|
-
this.ctx.restore();
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
await renderBorderSide();
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
side++;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
async render(element) {
|
|
500
|
-
try {
|
|
501
|
-
if (this.options.backgroundColor) {
|
|
502
|
-
this.ctx.fillStyle = (0, color_utilities_1.asString)(this.options.backgroundColor);
|
|
503
|
-
this.ctx.fillRect(this.options.x, this.options.y, this.options.width, this.options.height);
|
|
504
|
-
}
|
|
505
|
-
const stack = (0, stacking_context_1.parseStackingContexts)(element);
|
|
506
|
-
await this.renderStack(stack);
|
|
507
|
-
this.effectsRenderer.applyEffects([]);
|
|
508
|
-
return this.canvas;
|
|
509
|
-
}
|
|
510
|
-
catch (error) {
|
|
511
|
-
// Failed captures have no returned canvas. Release only our backing
|
|
512
|
-
// store; a canvas supplied by the caller remains caller-owned.
|
|
513
|
-
if (!this.options.canvas)
|
|
514
|
-
(0, filter_surface_1.releaseSurface)(this.canvas);
|
|
515
|
-
throw error;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Render background clipped to text shape using offscreen canvas with destination-in compositing.
|
|
520
|
-
* This implements background-clip: text support.
|
|
521
|
-
*
|
|
522
|
-
* Algorithm:
|
|
523
|
-
* 1. Draw background (color + images) onto an offscreen canvas
|
|
524
|
-
* 2. Use destination-in compositing to clip the background to the text glyph shapes
|
|
525
|
-
* 3. Composite the result back onto the main canvas
|
|
526
|
-
*
|
|
527
|
-
* The offscreen canvas uses CSS-pixel dimensions (not device-pixel) because the
|
|
528
|
-
* background renderer handles scaling internally via BackgroundRenderer options.
|
|
529
|
-
*/
|
|
530
|
-
async renderTextClippedBackground(paint) {
|
|
531
|
-
const container = paint.container;
|
|
532
|
-
const styles = container.styles;
|
|
533
|
-
const bounds = container.bounds;
|
|
534
|
-
if (bounds.width <= 0 || bounds.height <= 0) {
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
const ownerDocument = this.canvas.ownerDocument ?? document;
|
|
538
|
-
const offscreen = ownerDocument.createElement('canvas');
|
|
539
|
-
const width = Math.ceil(bounds.width);
|
|
540
|
-
const height = Math.ceil(bounds.height);
|
|
541
|
-
offscreen.width = width;
|
|
542
|
-
offscreen.height = height;
|
|
543
|
-
const offCtx = offscreen.getContext('2d');
|
|
544
|
-
if (!offCtx) {
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
// ── Set up font matching the main context ──
|
|
548
|
-
const [fontString] = this.textRenderer.createFontStyle(styles);
|
|
549
|
-
offCtx.font = fontString;
|
|
550
|
-
offCtx.textBaseline = 'alphabetic';
|
|
551
|
-
offCtx.textAlign = 'left';
|
|
552
|
-
offCtx.direction = styles.direction === 1 /* DIRECTION.RTL */ ? 'rtl' : 'ltr';
|
|
553
|
-
// Measure baseline from the actual rendered font
|
|
554
|
-
const baseline = (0, font_utils_1.measureBaseline)(offCtx, styles.fontSize.number);
|
|
555
|
-
// ── Draw background onto offscreen canvas ──
|
|
556
|
-
const bgRenderer = new background_renderer_1.BackgroundRenderer({
|
|
557
|
-
ctx: offCtx,
|
|
558
|
-
context: this.context,
|
|
559
|
-
canvas: offscreen,
|
|
560
|
-
options: { width, height, scale: 1 }
|
|
561
|
-
});
|
|
562
|
-
if (!(0, color_utilities_1.isTransparent)(styles.backgroundColor)) {
|
|
563
|
-
offCtx.fillStyle = (0, color_utilities_1.asString)(styles.backgroundColor);
|
|
564
|
-
offCtx.fillRect(0, 0, width, height);
|
|
565
|
-
}
|
|
566
|
-
// Background images are positioned relative to the element bounds,
|
|
567
|
-
// so we need the BackgroundRenderer to compute offsets in the same
|
|
568
|
-
// coordinate space as the main renderer. We do that by translating
|
|
569
|
-
// the offscreen context so that the element origin (bounds.left, bounds.top)
|
|
570
|
-
// falls at (0, 0).
|
|
571
|
-
offCtx.save();
|
|
572
|
-
offCtx.translate(-bounds.left, -bounds.top);
|
|
573
|
-
await bgRenderer.renderBackgroundImage(container);
|
|
574
|
-
offCtx.restore();
|
|
575
|
-
// ── Clip background to text glyphs ──
|
|
576
|
-
// destination-in: keep background pixels only where the source (text) is non-transparent.
|
|
577
|
-
offCtx.globalCompositeOperation = 'destination-in';
|
|
578
|
-
offCtx.fillStyle = '#000';
|
|
579
|
-
const writingMode = styles.writingMode;
|
|
580
|
-
const letterSpacing = styles.letterSpacing;
|
|
581
|
-
for (const textNode of container.textNodes) {
|
|
582
|
-
for (const textBound of textNode.textBounds) {
|
|
583
|
-
// Offset from element bounds to canvas-local coordinates
|
|
584
|
-
const localLeft = textBound.bounds.left - bounds.left;
|
|
585
|
-
const localTop = textBound.bounds.top - bounds.top + baseline;
|
|
586
|
-
if (letterSpacing > 0) {
|
|
587
|
-
this.renderTextMaskWithLetterSpacing(offCtx, textBound, letterSpacing, localLeft, localTop, writingMode);
|
|
588
|
-
}
|
|
589
|
-
else {
|
|
590
|
-
offCtx.fillText(textBound.text, localLeft, localTop);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
// ── Composite back to main canvas ──
|
|
595
|
-
this.ctx.drawImage(offscreen, bounds.left, bounds.top);
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* Render text glyphs one-by-one as a mask, applying letter-spacing between characters.
|
|
599
|
-
* This ensures the mask matches how the text renderer lays out the characters.
|
|
600
|
-
*/
|
|
601
|
-
renderTextMaskWithLetterSpacing(ctx, text, letterSpacing, x, y, writingMode) {
|
|
602
|
-
const letters = (0, text_1.segmentGraphemes)(text.text);
|
|
603
|
-
let offset = x;
|
|
604
|
-
for (const letter of letters) {
|
|
605
|
-
if ((0, writing_mode_1.isVerticalWritingMode)(writingMode)) {
|
|
606
|
-
// Vertical writing mode: not yet supported for text-clip;
|
|
607
|
-
// fall back to single fillText call.
|
|
608
|
-
ctx.fillText(text.text, x, y);
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
ctx.fillText(letter, offset, y);
|
|
612
|
-
offset += ctx.measureText(letter).width + letterSpacing;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
exports.CanvasRenderer = CanvasRenderer;
|
|
617
|
-
const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
|
|
618
|
-
switch (clip) {
|
|
619
|
-
case 0 /* BACKGROUND_CLIP.BORDER_BOX */:
|
|
620
|
-
return (0, bound_curves_1.calculateBorderBoxPath)(curves);
|
|
621
|
-
case 2 /* BACKGROUND_CLIP.CONTENT_BOX */:
|
|
622
|
-
return (0, bound_curves_1.calculateContentBoxPath)(curves);
|
|
623
|
-
case 1 /* BACKGROUND_CLIP.PADDING_BOX */:
|
|
624
|
-
default:
|
|
625
|
-
return (0, bound_curves_1.calculatePaddingBoxPath)(curves);
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
/**
|
|
629
|
-
* Check if any background layer uses background-clip: text
|
|
630
|
-
*/
|
|
631
|
-
const hasTextBackgroundClip = (styles) => {
|
|
632
|
-
return styles.backgroundClip.some((clip) => clip === 3 /* BACKGROUND_CLIP.TEXT */);
|
|
633
|
-
};
|