html2canvas-pro 2.2.4 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html2canvas-pro.cjs +11221 -0
- package/dist/html2canvas-pro.cjs.map +1 -0
- package/dist/html2canvas-pro.esm.js +2812 -2587
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +2812 -2587
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +4 -5
- package/dist/lib/core/abort-helper.js +20 -0
- package/dist/lib/core/background-parser.js +41 -0
- package/dist/lib/core/cache-storage.js +63 -7
- package/dist/lib/core/config-assembler.js +88 -0
- package/dist/lib/core/lru-map.js +66 -0
- package/dist/lib/core/render-element.js +24 -103
- package/dist/lib/css/property-descriptors/background-position.js +1 -11
- package/dist/lib/css/property-descriptors/background-size.js +3 -1
- package/dist/lib/css/syntax/token-constants.js +214 -0
- package/dist/lib/css/syntax/token-singletons.js +36 -0
- package/dist/lib/css/syntax/token-types.js +10 -0
- package/dist/lib/css/syntax/tokenizer.js +175 -307
- package/dist/lib/css/types/length-percentage.js +63 -3
- package/dist/lib/dom/document-cloner.js +23 -22
- package/dist/lib/dom/node-parser.js +1 -21
- package/dist/lib/dom/slot-cloner.js +8 -8
- package/dist/lib/render/background.js +4 -1
- package/dist/lib/render/canvas/background-renderer.js +27 -39
- package/dist/lib/render/canvas/canvas-renderer.js +3 -3
- package/dist/lib/render/canvas/effects-renderer.js +94 -32
- package/dist/lib/render/canvas/text-renderer.js +94 -32
- package/dist/lib/render/stacking-context.js +12 -6
- package/dist/types/core/abort-helper.d.ts +12 -0
- package/dist/types/core/background-parser.d.ts +16 -0
- package/dist/types/core/cache-storage.d.ts +25 -0
- package/dist/types/core/config-assembler.d.ts +28 -0
- package/dist/types/core/lru-map.d.ts +37 -0
- package/dist/types/css/syntax/token-constants.d.ts +74 -0
- package/dist/types/css/syntax/token-singletons.d.ts +22 -0
- package/dist/types/css/syntax/token-types.d.ts +72 -0
- package/dist/types/css/syntax/tokenizer.d.ts +6 -73
- package/dist/types/css/types/length-percentage.d.ts +42 -1
- package/dist/types/dom/document-cloner.d.ts +5 -0
- package/dist/types/dom/node-parser.d.ts +0 -2
- package/dist/types/render/canvas/background-renderer.d.ts +0 -10
- package/dist/types/render/canvas/effects-renderer.d.ts +42 -17
- package/dist/types/render/canvas/text-renderer.d.ts +15 -2
- package/dist/types/render/stacking-context.d.ts +26 -1
- package/package.json +5 -7
|
@@ -3,18 +3,71 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getAbsoluteValue = exports.getAbsoluteValueForTuple = exports.HUNDRED_PERCENT = exports.FIFTY_PERCENT = exports.ZERO_LENGTH = exports.parseLengthPercentageTuple = exports.evaluateCalcToLengthPercentage = exports.isCalcFunction = exports.isLengthPercentage = void 0;
|
|
6
|
+
exports.getAbsoluteValue = exports.getAbsoluteValueForTuple = exports.HUNDRED_PERCENT = exports.FIFTY_PERCENT = exports.ZERO_LENGTH = exports.parseLengthPercentageTuple = exports.evaluateCalcToLengthPercentage = exports.parseOptionalCalcOrLength = exports.parseCalcForLengthPercentage = exports.isCalcFunction = exports.isCalcWithPercentage = exports.isLengthPercentage = void 0;
|
|
7
7
|
const tokenizer_1 = require("../syntax/tokenizer");
|
|
8
8
|
const parser_1 = require("../syntax/parser");
|
|
9
9
|
const length_1 = require("./length");
|
|
10
10
|
const safe_eval_1 = __importDefault(require("./safe-eval"));
|
|
11
|
-
const isLengthPercentage = (token) => token.type === 16 /* TokenType.PERCENTAGE_TOKEN */ || (0, length_1.isLength)(token);
|
|
11
|
+
const isLengthPercentage = (token) => token.type === 16 /* TokenType.PERCENTAGE_TOKEN */ || (0, length_1.isLength)(token) || (0, exports.isCalcWithPercentage)(token);
|
|
12
12
|
exports.isLengthPercentage = isLengthPercentage;
|
|
13
|
+
/**
|
|
14
|
+
* Check if a token is a CalcWithPercentage deferred token.
|
|
15
|
+
* Accepts CSSValue (structural duck-type check) for use in type guards.
|
|
16
|
+
*/
|
|
17
|
+
const isCalcWithPercentage = (token) => token.type === 17 /* TokenType.NUMBER_TOKEN */ && token._calcPercentage !== undefined;
|
|
18
|
+
exports.isCalcWithPercentage = isCalcWithPercentage;
|
|
13
19
|
/**
|
|
14
20
|
* Check if a token is a calc() function
|
|
15
21
|
*/
|
|
16
22
|
const isCalcFunction = (token) => token.type === 18 /* TokenType.FUNCTION */ && token.name === 'calc';
|
|
17
23
|
exports.isCalcFunction = isCalcFunction;
|
|
24
|
+
/**
|
|
25
|
+
* Parse a calc() token and produce a LengthPercentage.
|
|
26
|
+
*
|
|
27
|
+
* Uses two-point evaluation to extract the percentage coefficient and pixel
|
|
28
|
+
* offset from a calc() expression so it can be resolved later at render time
|
|
29
|
+
* when the container size is known.
|
|
30
|
+
*
|
|
31
|
+
* evaluate(0) → pixelOffset
|
|
32
|
+
* evaluate(100) → percentage + pixelOffset → percentage = evaluate(100) - pixelOffset
|
|
33
|
+
*
|
|
34
|
+
* If the expression has no percentage component, returns a plain NUMBER_TOKEN
|
|
35
|
+
* for backwards compatibility.
|
|
36
|
+
*/
|
|
37
|
+
const parseCalcForLengthPercentage = (calcToken) => {
|
|
38
|
+
const withZero = (0, exports.evaluateCalcToLengthPercentage)(calcToken, 0);
|
|
39
|
+
if (!withZero)
|
|
40
|
+
return null;
|
|
41
|
+
const withHundred = (0, exports.evaluateCalcToLengthPercentage)(calcToken, 100);
|
|
42
|
+
if (!withHundred)
|
|
43
|
+
return null;
|
|
44
|
+
const pixelOffset = withZero.number;
|
|
45
|
+
const percentage = withHundred.number - pixelOffset;
|
|
46
|
+
// If there's no percentage component, return a plain NUMBER_TOKEN
|
|
47
|
+
if (percentage === 0) {
|
|
48
|
+
return withZero;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
type: 17 /* TokenType.NUMBER_TOKEN */,
|
|
52
|
+
number: pixelOffset,
|
|
53
|
+
flags: tokenizer_1.FLAG_INTEGER,
|
|
54
|
+
_calcPercentage: percentage,
|
|
55
|
+
_calcPixelOffset: pixelOffset
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
exports.parseCalcForLengthPercentage = parseCalcForLengthPercentage;
|
|
59
|
+
/**
|
|
60
|
+
* Convenience helper for parsing an optional calc() or length-percentage value.
|
|
61
|
+
* Used by property descriptors that accept both calc() and regular length-percentage tokens.
|
|
62
|
+
* Returns null if the value is neither a calc() function nor a length-percentage token.
|
|
63
|
+
*/
|
|
64
|
+
const parseOptionalCalcOrLength = (value) => {
|
|
65
|
+
if ((0, exports.isCalcFunction)(value)) {
|
|
66
|
+
return (0, exports.parseCalcForLengthPercentage)(value);
|
|
67
|
+
}
|
|
68
|
+
return (0, exports.isLengthPercentage)(value) ? value : null;
|
|
69
|
+
};
|
|
70
|
+
exports.parseOptionalCalcOrLength = parseOptionalCalcOrLength;
|
|
18
71
|
/**
|
|
19
72
|
* Evaluate a calc() expression and convert to LengthPercentage token
|
|
20
73
|
* Supports basic arithmetic: +, -, *, /
|
|
@@ -119,6 +172,9 @@ const getAbsoluteValueForTuple = (tuple, width, height) => {
|
|
|
119
172
|
};
|
|
120
173
|
exports.getAbsoluteValueForTuple = getAbsoluteValueForTuple;
|
|
121
174
|
const getAbsoluteValue = (token, parent) => {
|
|
175
|
+
if ((0, exports.isCalcWithPercentage)(token)) {
|
|
176
|
+
return (token._calcPercentage / 100) * parent + token._calcPixelOffset;
|
|
177
|
+
}
|
|
122
178
|
if (token.type === 16 /* TokenType.PERCENTAGE_TOKEN */) {
|
|
123
179
|
return (token.number / 100) * parent;
|
|
124
180
|
}
|
|
@@ -126,7 +182,11 @@ const getAbsoluteValue = (token, parent) => {
|
|
|
126
182
|
switch (token.unit) {
|
|
127
183
|
case 'rem':
|
|
128
184
|
case 'em':
|
|
129
|
-
|
|
185
|
+
// NOTE: Assumes a 16px root font-size for rem/em units. A fully
|
|
186
|
+
// accurate implementation would require access to the element's
|
|
187
|
+
// computed font-size (em) or the root element's font-size (rem),
|
|
188
|
+
// which are not available in this pure-resolution function.
|
|
189
|
+
return 16 * token.number;
|
|
130
190
|
case 'px':
|
|
131
191
|
default:
|
|
132
192
|
return token.number;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.copyCSSStyles = exports.DocumentCloner = void 0;
|
|
4
|
-
const node_parser_1 = require("./node-parser");
|
|
3
|
+
exports.serializeDoctype = exports.copyCSSStyles = exports.DocumentCloner = void 0;
|
|
5
4
|
const node_type_guards_1 = require("./node-type-guards");
|
|
6
5
|
const parser_1 = require("../css/syntax/parser");
|
|
7
6
|
const counter_1 = require("../css/types/functions/counter");
|
|
@@ -102,7 +101,7 @@ class DocumentCloner {
|
|
|
102
101
|
// rawHTML is always a static, internally-generated string:
|
|
103
102
|
// serializeDoctype(document.doctype) + '<html></html>'
|
|
104
103
|
// No user-controlled input — safe for document.write in the sandbox iframe.
|
|
105
|
-
const rawHTML = serializeDoctype(document.doctype) + '<html></html>';
|
|
104
|
+
const rawHTML = (0, exports.serializeDoctype)(document.doctype) + '<html></html>';
|
|
106
105
|
try {
|
|
107
106
|
const ownerWindow = this.referenceElement.ownerDocument?.defaultView;
|
|
108
107
|
const trustedTypesFactory = ownerWindow && ownerWindow.trustedTypes;
|
|
@@ -149,18 +148,18 @@ class DocumentCloner {
|
|
|
149
148
|
if ((0, debugger_1.isDebugging)(node, 2 /* DebuggerType.CLONE */)) {
|
|
150
149
|
debugger;
|
|
151
150
|
}
|
|
152
|
-
if ((0,
|
|
151
|
+
if ((0, node_type_guards_1.isCanvasElement)(node)) {
|
|
153
152
|
return this.createCanvasClone(node);
|
|
154
153
|
}
|
|
155
|
-
if ((0,
|
|
154
|
+
if ((0, node_type_guards_1.isVideoElement)(node)) {
|
|
156
155
|
return this.createVideoClone(node);
|
|
157
156
|
}
|
|
158
|
-
if ((0,
|
|
157
|
+
if ((0, node_type_guards_1.isStyleElement)(node)) {
|
|
159
158
|
return this.createStyleClone(node);
|
|
160
159
|
}
|
|
161
160
|
const clone = node.cloneNode(false);
|
|
162
|
-
if ((0,
|
|
163
|
-
if ((0,
|
|
161
|
+
if ((0, node_type_guards_1.isImageElement)(clone)) {
|
|
162
|
+
if ((0, node_type_guards_1.isImageElement)(node) && node.currentSrc && node.currentSrc !== node.src) {
|
|
164
163
|
clone.src = node.currentSrc;
|
|
165
164
|
clone.srcset = '';
|
|
166
165
|
}
|
|
@@ -168,7 +167,7 @@ class DocumentCloner {
|
|
|
168
167
|
clone.loading = 'eager';
|
|
169
168
|
}
|
|
170
169
|
}
|
|
171
|
-
if ((0,
|
|
170
|
+
if ((0, node_type_guards_1.isCustomElement)(clone) && !(0, node_type_guards_1.isSVGElementNode)(clone)) {
|
|
172
171
|
return this.createCustomElementClone(clone);
|
|
173
172
|
}
|
|
174
173
|
return clone;
|
|
@@ -295,24 +294,24 @@ class DocumentCloner {
|
|
|
295
294
|
this.slotCloner.cloneChildNodes(node, clone, copyStyles);
|
|
296
295
|
}
|
|
297
296
|
cloneNode(node, copyStyles) {
|
|
298
|
-
if ((0,
|
|
297
|
+
if ((0, node_type_guards_1.isTextNode)(node)) {
|
|
299
298
|
return document.createTextNode(node.data);
|
|
300
299
|
}
|
|
301
300
|
if (!node.ownerDocument) {
|
|
302
301
|
return node.cloneNode(false);
|
|
303
302
|
}
|
|
304
303
|
const window = node.ownerDocument.defaultView;
|
|
305
|
-
if (window && (0,
|
|
304
|
+
if (window && (0, node_type_guards_1.isElementNode)(node) && ((0, node_type_guards_1.isHTMLElementNode)(node) || (0, node_type_guards_1.isSVGElementNode)(node))) {
|
|
306
305
|
const clone = this.createElementClone(node);
|
|
307
306
|
clone.style.transitionProperty = 'none';
|
|
308
307
|
const style = window.getComputedStyle(node);
|
|
309
308
|
// Per CSS spec, replaced elements, void elements, and SVG elements
|
|
310
309
|
// cannot have ::before / ::after pseudo-elements — skip these queries.
|
|
311
310
|
const checkPseudoElements = (0, node_type_guards_1.canHavePseudoElements)(node);
|
|
312
|
-
if (this.referenceElement === node && (0,
|
|
311
|
+
if (this.referenceElement === node && (0, node_type_guards_1.isHTMLElementNode)(clone)) {
|
|
313
312
|
this.clonedReferenceElement = clone;
|
|
314
313
|
}
|
|
315
|
-
if ((0,
|
|
314
|
+
if ((0, node_type_guards_1.isBodyElement)(clone)) {
|
|
316
315
|
createPseudoHideStyles(clone, this.options.cspNonce);
|
|
317
316
|
}
|
|
318
317
|
const counters = this.counters.parse(new index_1.CSSParsedCounterDeclaration(this.context, style));
|
|
@@ -328,22 +327,22 @@ class DocumentCloner {
|
|
|
328
327
|
clone.appendChild(after);
|
|
329
328
|
}
|
|
330
329
|
}
|
|
331
|
-
if ((0,
|
|
330
|
+
if ((0, node_type_guards_1.isCustomElement)(node)) {
|
|
332
331
|
copyStyles = true;
|
|
333
332
|
}
|
|
334
|
-
if (!(0,
|
|
333
|
+
if (!(0, node_type_guards_1.isVideoElement)(node)) {
|
|
335
334
|
this.cloneChildNodes(node, clone, copyStyles);
|
|
336
335
|
}
|
|
337
336
|
this.counters.pop(counters);
|
|
338
|
-
if ((style && (this.options.copyStyles || (0,
|
|
337
|
+
if ((style && (this.options.copyStyles || (0, node_type_guards_1.isSVGElementNode)(node)) && !(0, node_type_guards_1.isIFrameElement)(node)) ||
|
|
339
338
|
copyStyles) {
|
|
340
339
|
(0, exports.copyCSSStyles)(style, clone);
|
|
341
340
|
}
|
|
342
341
|
if (node.scrollTop !== 0 || node.scrollLeft !== 0) {
|
|
343
342
|
this.scrolledElements.push([clone, node.scrollLeft, node.scrollTop]);
|
|
344
343
|
}
|
|
345
|
-
if (((0,
|
|
346
|
-
((0,
|
|
344
|
+
if (((0, node_type_guards_1.isTextareaElement)(node) || (0, node_type_guards_1.isSelectElement)(node)) &&
|
|
345
|
+
((0, node_type_guards_1.isTextareaElement)(clone) || (0, node_type_guards_1.isSelectElement)(clone))) {
|
|
347
346
|
clone.value = node.value;
|
|
348
347
|
}
|
|
349
348
|
return clone;
|
|
@@ -404,9 +403,6 @@ class DocumentCloner {
|
|
|
404
403
|
anonymousReplacedElement.appendChild(document.createTextNode(text));
|
|
405
404
|
}
|
|
406
405
|
}
|
|
407
|
-
else {
|
|
408
|
-
// console.log('FUNCTION_TOKEN', token);
|
|
409
|
-
}
|
|
410
406
|
}
|
|
411
407
|
else if (token.type === 20 /* TokenType.IDENT_TOKEN */) {
|
|
412
408
|
switch (token.value) {
|
|
@@ -426,7 +422,7 @@ class DocumentCloner {
|
|
|
426
422
|
const newClassName = pseudoElt === PseudoElementType.BEFORE
|
|
427
423
|
? ` ${PSEUDO_HIDE_ELEMENT_CLASS_BEFORE}`
|
|
428
424
|
: ` ${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}`;
|
|
429
|
-
if ((0,
|
|
425
|
+
if ((0, node_type_guards_1.isSVGElementNode)(clone)) {
|
|
430
426
|
clone.className.baseValue += newClassName;
|
|
431
427
|
}
|
|
432
428
|
else {
|
|
@@ -531,6 +527,10 @@ const copyCSSStyles = (style, target) => {
|
|
|
531
527
|
return target;
|
|
532
528
|
};
|
|
533
529
|
exports.copyCSSStyles = copyCSSStyles;
|
|
530
|
+
/**
|
|
531
|
+
* Serialise a document type declaration to an HTML string.
|
|
532
|
+
* @internal – exported for testing only, not part of the public API.
|
|
533
|
+
*/
|
|
534
534
|
const serializeDoctype = (doctype) => {
|
|
535
535
|
let str = '';
|
|
536
536
|
if (doctype) {
|
|
@@ -554,6 +554,7 @@ const serializeDoctype = (doctype) => {
|
|
|
554
554
|
}
|
|
555
555
|
return str;
|
|
556
556
|
};
|
|
557
|
+
exports.serializeDoctype = serializeDoctype;
|
|
557
558
|
const restoreOwnerScroll = (ownerDocument, x, y) => {
|
|
558
559
|
if (ownerDocument &&
|
|
559
560
|
ownerDocument.defaultView &&
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseTree =
|
|
3
|
+
exports.parseTree = void 0;
|
|
4
4
|
const element_container_1 = require("./element-container");
|
|
5
5
|
const text_container_1 = require("./text-container");
|
|
6
6
|
const image_element_container_1 = require("./replaced-elements/image-element-container");
|
|
@@ -13,26 +13,6 @@ const select_element_container_1 = require("./elements/select-element-container"
|
|
|
13
13
|
const textarea_element_container_1 = require("./elements/textarea-element-container");
|
|
14
14
|
const iframe_element_container_1 = require("./replaced-elements/iframe-element-container");
|
|
15
15
|
const node_type_guards_1 = require("./node-type-guards");
|
|
16
|
-
Object.defineProperty(exports, "isBodyElement", { enumerable: true, get: function () { return node_type_guards_1.isBodyElement; } });
|
|
17
|
-
Object.defineProperty(exports, "isCanvasElement", { enumerable: true, get: function () { return node_type_guards_1.isCanvasElement; } });
|
|
18
|
-
Object.defineProperty(exports, "isCustomElement", { enumerable: true, get: function () { return node_type_guards_1.isCustomElement; } });
|
|
19
|
-
Object.defineProperty(exports, "isElementNode", { enumerable: true, get: function () { return node_type_guards_1.isElementNode; } });
|
|
20
|
-
Object.defineProperty(exports, "isHTMLElement", { enumerable: true, get: function () { return node_type_guards_1.isHTMLElement; } });
|
|
21
|
-
Object.defineProperty(exports, "isHTMLElementNode", { enumerable: true, get: function () { return node_type_guards_1.isHTMLElementNode; } });
|
|
22
|
-
Object.defineProperty(exports, "isIFrameElement", { enumerable: true, get: function () { return node_type_guards_1.isIFrameElement; } });
|
|
23
|
-
Object.defineProperty(exports, "isImageElement", { enumerable: true, get: function () { return node_type_guards_1.isImageElement; } });
|
|
24
|
-
Object.defineProperty(exports, "isInputElement", { enumerable: true, get: function () { return node_type_guards_1.isInputElement; } });
|
|
25
|
-
Object.defineProperty(exports, "isLIElement", { enumerable: true, get: function () { return node_type_guards_1.isLIElement; } });
|
|
26
|
-
Object.defineProperty(exports, "isOLElement", { enumerable: true, get: function () { return node_type_guards_1.isOLElement; } });
|
|
27
|
-
Object.defineProperty(exports, "isScriptElement", { enumerable: true, get: function () { return node_type_guards_1.isScriptElement; } });
|
|
28
|
-
Object.defineProperty(exports, "isSelectElement", { enumerable: true, get: function () { return node_type_guards_1.isSelectElement; } });
|
|
29
|
-
Object.defineProperty(exports, "isSlotElement", { enumerable: true, get: function () { return node_type_guards_1.isSlotElement; } });
|
|
30
|
-
Object.defineProperty(exports, "isStyleElement", { enumerable: true, get: function () { return node_type_guards_1.isStyleElement; } });
|
|
31
|
-
Object.defineProperty(exports, "isSVGElement", { enumerable: true, get: function () { return node_type_guards_1.isSVGElement; } });
|
|
32
|
-
Object.defineProperty(exports, "isSVGElementNode", { enumerable: true, get: function () { return node_type_guards_1.isSVGElementNode; } });
|
|
33
|
-
Object.defineProperty(exports, "isTextareaElement", { enumerable: true, get: function () { return node_type_guards_1.isTextareaElement; } });
|
|
34
|
-
Object.defineProperty(exports, "isTextNode", { enumerable: true, get: function () { return node_type_guards_1.isTextNode; } });
|
|
35
|
-
Object.defineProperty(exports, "isVideoElement", { enumerable: true, get: function () { return node_type_guards_1.isVideoElement; } });
|
|
36
16
|
const bitwise_1 = require("../core/bitwise");
|
|
37
17
|
const LIST_OWNERS = ['OL', 'UL', 'MENU'];
|
|
38
18
|
const parseNodeTree = (context, node, parent, root) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SlotCloner = exports.IGNORE_ATTRIBUTE = void 0;
|
|
4
|
-
const
|
|
4
|
+
const node_type_guards_1 = require("./node-type-guards");
|
|
5
5
|
/** Exported for reuse in document-cloner.ts */
|
|
6
6
|
exports.IGNORE_ATTRIBUTE = 'data-html2canvas-ignore';
|
|
7
7
|
/**
|
|
@@ -43,8 +43,8 @@ class SlotCloner {
|
|
|
43
43
|
* Filters out: scripts, ignored elements, and optionally styles.
|
|
44
44
|
*/
|
|
45
45
|
shouldCloneChild(child) {
|
|
46
|
-
return (!(0,
|
|
47
|
-
(!(0,
|
|
46
|
+
return (!(0, node_type_guards_1.isElementNode)(child) ||
|
|
47
|
+
(!(0, node_type_guards_1.isScriptElement)(child) &&
|
|
48
48
|
!child.hasAttribute(exports.IGNORE_ATTRIBUTE) &&
|
|
49
49
|
(typeof this.options.ignoreElements !== 'function' || !this.options.ignoreElements(child))));
|
|
50
50
|
}
|
|
@@ -52,7 +52,7 @@ class SlotCloner {
|
|
|
52
52
|
* Check if a style element should be cloned based on copyStyles option.
|
|
53
53
|
*/
|
|
54
54
|
shouldCloneStyleElement(child) {
|
|
55
|
-
return !this.options.copyStyles || !(0,
|
|
55
|
+
return !this.options.copyStyles || !(0, node_type_guards_1.isElementNode)(child) || !(0, node_type_guards_1.isStyleElement)(child);
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* Safely append a cloned child to a target, applying all filtering rules.
|
|
@@ -82,7 +82,7 @@ class SlotCloner {
|
|
|
82
82
|
* Handle cloning of a slot element, including assigned nodes or fallback content.
|
|
83
83
|
*/
|
|
84
84
|
cloneSlotElement(slot, targetShadowRoot, copyStyles) {
|
|
85
|
-
if (!(0,
|
|
85
|
+
if (!(0, node_type_guards_1.isSlotElement)(slot)) {
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
const slotElement = slot;
|
|
@@ -113,7 +113,7 @@ class SlotCloner {
|
|
|
113
113
|
*/
|
|
114
114
|
cloneShadowDOMChildren(shadowRoot, targetShadowRoot, copyStyles) {
|
|
115
115
|
for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {
|
|
116
|
-
if ((0,
|
|
116
|
+
if ((0, node_type_guards_1.isElementNode)(child) && (0, node_type_guards_1.isSlotElement)(child)) {
|
|
117
117
|
// Handle slot elements specially
|
|
118
118
|
this.cloneSlotElement(child, targetShadowRoot, copyStyles);
|
|
119
119
|
}
|
|
@@ -135,7 +135,7 @@ class SlotCloner {
|
|
|
135
135
|
* Clone slot element as light DOM when shadow root creation failed.
|
|
136
136
|
*/
|
|
137
137
|
cloneSlotElementAsLightDOM(slot, clone, copyStyles) {
|
|
138
|
-
if (!(0,
|
|
138
|
+
if (!(0, node_type_guards_1.isSlotElement)(slot)) {
|
|
139
139
|
return;
|
|
140
140
|
}
|
|
141
141
|
const slotElement = slot;
|
|
@@ -164,7 +164,7 @@ class SlotCloner {
|
|
|
164
164
|
*/
|
|
165
165
|
cloneShadowDOMAsLightDOM(shadowRoot, clone, copyStyles) {
|
|
166
166
|
for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {
|
|
167
|
-
if ((0,
|
|
167
|
+
if ((0, node_type_guards_1.isElementNode)(child) && (0, node_type_guards_1.isSlotElement)(child)) {
|
|
168
168
|
this.cloneSlotElementAsLightDOM(child, clone, copyStyles);
|
|
169
169
|
}
|
|
170
170
|
else {
|
|
@@ -75,7 +75,10 @@ const calculateBackgroundSize = (size, [intrinsicWidth, intrinsicHeight, intrins
|
|
|
75
75
|
if (!hasIntrinsicProportion && !hasIntrinsicDimensions) {
|
|
76
76
|
return [bounds.width, bounds.height];
|
|
77
77
|
}
|
|
78
|
-
//
|
|
78
|
+
// NOTE (known gap): Per the CSS spec, images with intrinsic proportions
|
|
79
|
+
// but no intrinsic dimensions should be rendered as if 'contain' had been
|
|
80
|
+
// specified. This branch is not yet implemented — the image will render
|
|
81
|
+
// at the size of the background positioning area instead.
|
|
79
82
|
// If the image has only one intrinsic dimension and has intrinsic proportions, it's rendered at the size corresponding to that one dimension.
|
|
80
83
|
// The other dimension is computed using the specified dimension and the intrinsic proportions.
|
|
81
84
|
if (hasIntrinsicDimensions && hasIntrinsicProportion) {
|
|
@@ -18,6 +18,7 @@ const length_percentage_1 = require("../../css/types/length-percentage");
|
|
|
18
18
|
const color_utilities_1 = require("../../css/types/color-utilities");
|
|
19
19
|
const image_rendering_1 = require("../../css/property-descriptors/image-rendering");
|
|
20
20
|
const canvas_path_1 = require("./canvas-path");
|
|
21
|
+
const lru_map_1 = require("../../core/lru-map");
|
|
21
22
|
/**
|
|
22
23
|
* Background Renderer
|
|
23
24
|
*
|
|
@@ -31,11 +32,8 @@ class BackgroundRenderer {
|
|
|
31
32
|
* CanvasPatterns are tied to the rendering context and must not be
|
|
32
33
|
* shared across different render passes. This cache lives for the
|
|
33
34
|
* duration of one html2canvas() call.
|
|
34
|
-
*
|
|
35
|
-
* Also reused for linear-gradient and repeating-linear-gradient
|
|
36
|
-
* pattern canvases to avoid redundant offscreen canvas allocation.
|
|
37
35
|
*/
|
|
38
|
-
this.patternCache = new
|
|
36
|
+
this.patternCache = new lru_map_1.LRUMap(50);
|
|
39
37
|
this.ctx = deps.ctx;
|
|
40
38
|
this.context = deps.context;
|
|
41
39
|
this.canvas = deps.canvas;
|
|
@@ -102,13 +100,22 @@ class BackgroundRenderer {
|
|
|
102
100
|
imageHeight,
|
|
103
101
|
imageWidth / imageHeight
|
|
104
102
|
]);
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
|
|
103
|
+
// Determine repetition mode based on CSS background-repeat
|
|
104
|
+
const repeat = (0, background_1.getBackgroundValueForIndex)(container.styles.backgroundRepeat, index);
|
|
105
|
+
const repeatMode = repeat === 1 /* BACKGROUND_REPEAT.NO_REPEAT */
|
|
106
|
+
? 'no-repeat'
|
|
107
|
+
: repeat === 2 /* BACKGROUND_REPEAT.REPEAT_X */
|
|
108
|
+
? 'repeat-x'
|
|
109
|
+
: repeat === 3 /* BACKGROUND_REPEAT.REPEAT_Y */
|
|
110
|
+
? 'repeat-y'
|
|
111
|
+
: 'repeat';
|
|
112
|
+
// Cache key: URL + resized dimensions + imageRendering + repeat mode (pattern is dependent on all four)
|
|
113
|
+
const cacheKey = `${url}|${Math.round(width)}x${Math.round(height)}|${container.styles.imageRendering}|${repeatMode}`;
|
|
114
|
+
let pattern = this.patternCache.get(cacheKey);
|
|
108
115
|
if (!pattern) {
|
|
109
116
|
const resized = this.resizeImage(image, width, height, container.styles.imageRendering);
|
|
110
|
-
pattern = this.ctx.createPattern(resized,
|
|
111
|
-
this.
|
|
117
|
+
pattern = this.ctx.createPattern(resized, repeatMode);
|
|
118
|
+
this.patternCache.set(cacheKey, pattern);
|
|
112
119
|
}
|
|
113
120
|
this.renderRepeat(path, pattern, x, y);
|
|
114
121
|
}
|
|
@@ -143,7 +150,7 @@ class BackgroundRenderer {
|
|
|
143
150
|
const ownerDocument = this.canvas.ownerDocument ?? document;
|
|
144
151
|
// Cache key for this repeating gradient pattern
|
|
145
152
|
const cacheKey = `rlg|${backgroundImage.angle}|${Math.round(patternLength)}|${JSON.stringify(backgroundImage.stops)}`;
|
|
146
|
-
let pattern = this.
|
|
153
|
+
let pattern = this.patternCache.get(cacheKey);
|
|
147
154
|
if (!pattern) {
|
|
148
155
|
const canvas = ownerDocument.createElement('canvas');
|
|
149
156
|
// Create a canvas large enough to hold one full repeating unit
|
|
@@ -163,7 +170,7 @@ class BackgroundRenderer {
|
|
|
163
170
|
ctx.fillRect(0, 0, canvasSize, canvasSize);
|
|
164
171
|
if (canvasSize > 0) {
|
|
165
172
|
pattern = this.ctx.createPattern(canvas, 'repeat');
|
|
166
|
-
this.
|
|
173
|
+
this.patternCache.set(cacheKey, pattern);
|
|
167
174
|
}
|
|
168
175
|
}
|
|
169
176
|
if (pattern) {
|
|
@@ -178,7 +185,7 @@ class BackgroundRenderer {
|
|
|
178
185
|
const [lineLength, x0, x1, y0, y1] = (0, gradient_1.calculateGradientDirection)(backgroundImage.angle, width, height);
|
|
179
186
|
// Cache key: angle + dimensions + serialised colour stops
|
|
180
187
|
const cacheKey = `lg|${backgroundImage.angle}|${Math.round(width)}x${Math.round(height)}|${JSON.stringify(backgroundImage.stops)}`;
|
|
181
|
-
let pattern = this.
|
|
188
|
+
let pattern = this.patternCache.get(cacheKey);
|
|
182
189
|
if (!pattern) {
|
|
183
190
|
const ownerDocument = this.canvas.ownerDocument ?? document;
|
|
184
191
|
const canvas = ownerDocument.createElement('canvas');
|
|
@@ -194,7 +201,7 @@ class BackgroundRenderer {
|
|
|
194
201
|
ctx.fillRect(0, 0, width, height);
|
|
195
202
|
if (width > 0 && height > 0) {
|
|
196
203
|
pattern = this.ctx.createPattern(canvas, 'repeat');
|
|
197
|
-
this.
|
|
204
|
+
this.patternCache.set(cacheKey, pattern);
|
|
198
205
|
}
|
|
199
206
|
}
|
|
200
207
|
if (pattern) {
|
|
@@ -219,7 +226,7 @@ class BackgroundRenderer {
|
|
|
219
226
|
if (rx > 0 && ry > 0) {
|
|
220
227
|
// Cache key for radial gradient: position + radii + colour stops
|
|
221
228
|
const cacheKey = `rg|${Math.round(x)}x${Math.round(y)}|${Math.round(rx)}x${Math.round(ry)}|${JSON.stringify(backgroundImage.stops)}`;
|
|
222
|
-
let pattern = this.
|
|
229
|
+
let pattern = this.patternCache.get(cacheKey);
|
|
223
230
|
if (!pattern) {
|
|
224
231
|
const ownerDocument = this.canvas.ownerDocument ?? document;
|
|
225
232
|
const size = Math.ceil(Math.max(rx, ry) * 2);
|
|
@@ -236,7 +243,7 @@ class BackgroundRenderer {
|
|
|
236
243
|
offCtx.scale(1, ry / rx);
|
|
237
244
|
offCtx.fillRect(0, 0, offRadius * 2, offRadius * 2);
|
|
238
245
|
pattern = this.ctx.createPattern(offscreen, 'no-repeat');
|
|
239
|
-
this.
|
|
246
|
+
this.patternCache.set(cacheKey, pattern);
|
|
240
247
|
}
|
|
241
248
|
}
|
|
242
249
|
if (pattern) {
|
|
@@ -275,10 +282,11 @@ class BackgroundRenderer {
|
|
|
275
282
|
* @returns Resized canvas or original image
|
|
276
283
|
*/
|
|
277
284
|
resizeImage(image, width, height, imageRendering) {
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
285
|
+
// NOTE: Early-return optimisation disabled per upstream investigation
|
|
286
|
+
// (https://github.com/niklasvh/html2canvas/pull/2911).
|
|
287
|
+
// Returning the original image when dimensions match caused subtle
|
|
288
|
+
// rendering issues — a resized copy through a fresh offscreen canvas
|
|
289
|
+
// is always safer, even when width/height are unchanged.
|
|
282
290
|
const ownerDocument = this.canvas.ownerDocument ?? document;
|
|
283
291
|
const canvas = ownerDocument.createElement('canvas');
|
|
284
292
|
canvas.width = Math.max(1, width);
|
|
@@ -315,25 +323,5 @@ class BackgroundRenderer {
|
|
|
315
323
|
path(paths) {
|
|
316
324
|
(0, canvas_path_1.createCanvasPath)(this.ctx, paths);
|
|
317
325
|
}
|
|
318
|
-
/**
|
|
319
|
-
* LRU-aware get: returns value and promotes the entry to end of Map.
|
|
320
|
-
*/
|
|
321
|
-
lruGet(cache, key) {
|
|
322
|
-
const value = cache.get(key);
|
|
323
|
-
if (value !== undefined) {
|
|
324
|
-
cache.delete(key);
|
|
325
|
-
cache.set(key, value);
|
|
326
|
-
}
|
|
327
|
-
return value;
|
|
328
|
-
}
|
|
329
|
-
/** LRU-aware set for CanvasPattern caches. Evicts oldest entry on overflow. */
|
|
330
|
-
lruSet(cache, key, value) {
|
|
331
|
-
if (cache.size >= BackgroundRenderer.PATTERN_CACHE_MAX) {
|
|
332
|
-
const oldestKey = cache.keys().next().value;
|
|
333
|
-
cache.delete(oldestKey);
|
|
334
|
-
}
|
|
335
|
-
cache.set(key, value);
|
|
336
|
-
}
|
|
337
326
|
}
|
|
338
327
|
exports.BackgroundRenderer = BackgroundRenderer;
|
|
339
|
-
BackgroundRenderer.PATTERN_CACHE_MAX = 50;
|
|
@@ -16,7 +16,7 @@ const effects_renderer_1 = require("./effects-renderer");
|
|
|
16
16
|
const canvas_path_1 = require("./canvas-path");
|
|
17
17
|
const object_fit_1 = require("../object-fit");
|
|
18
18
|
const content_renderer_1 = require("./content-renderer");
|
|
19
|
-
const
|
|
19
|
+
const constants_1 = require("../../core/constants");
|
|
20
20
|
class CanvasRenderer {
|
|
21
21
|
constructor(context, options) {
|
|
22
22
|
this.context = context;
|
|
@@ -63,7 +63,7 @@ class CanvasRenderer {
|
|
|
63
63
|
this.effectsRenderer = new effects_renderer_1.EffectsRenderer({ ctx: this.ctx }, { path: (paths) => this.path(paths) });
|
|
64
64
|
this.textRenderer = new text_renderer_1.TextRenderer({
|
|
65
65
|
ctx: this.ctx,
|
|
66
|
-
|
|
66
|
+
fontMetrics: this.fontMetrics,
|
|
67
67
|
options: { scale: options.scale }
|
|
68
68
|
});
|
|
69
69
|
this.context.logger.debug(`Canvas renderer initialized (${options.width}x${options.height}) with scale ${options.scale}`);
|
|
@@ -231,7 +231,7 @@ class CanvasRenderer {
|
|
|
231
231
|
.forEach((shadow) => {
|
|
232
232
|
this.ctx.save();
|
|
233
233
|
const borderBoxArea = (0, bound_curves_1.calculateBorderBoxPath)(paint.curves);
|
|
234
|
-
const maskOffset = shadow.inset ? 0 :
|
|
234
|
+
const maskOffset = shadow.inset ? 0 : constants_1.SHADOW_MASK_OFFSET;
|
|
235
235
|
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));
|
|
236
236
|
if (shadow.inset) {
|
|
237
237
|
this.path(borderBoxArea);
|