html2canvas-pro 1.6.4 → 1.6.6
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.esm.js +508 -43
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +508 -42
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +4 -4
- package/dist/lib/css/index.js +10 -0
- package/dist/lib/css/index.js.map +1 -1
- package/dist/lib/css/property-descriptors/text-decoration-style.js +25 -0
- package/dist/lib/css/property-descriptors/text-decoration-style.js.map +1 -0
- package/dist/lib/css/property-descriptors/text-decoration-thickness.js +27 -0
- package/dist/lib/css/property-descriptors/text-decoration-thickness.js.map +1 -0
- package/dist/lib/css/property-descriptors/text-overflow.js +19 -0
- package/dist/lib/css/property-descriptors/text-overflow.js.map +1 -0
- package/dist/lib/css/property-descriptors/text-underline-offset.js +24 -0
- package/dist/lib/css/property-descriptors/text-underline-offset.js.map +1 -0
- package/dist/lib/css/property-descriptors/webkit-line-clamp.js +27 -0
- package/dist/lib/css/property-descriptors/webkit-line-clamp.js.map +1 -0
- package/dist/lib/dom/document-cloner.js +50 -22
- package/dist/lib/dom/document-cloner.js.map +1 -1
- package/dist/lib/index.js +10 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/render/canvas/canvas-renderer.js +338 -18
- package/dist/lib/render/canvas/canvas-renderer.js.map +1 -1
- package/dist/types/css/index.d.ts +10 -0
- package/dist/types/css/property-descriptors/text-decoration-style.d.ts +9 -0
- package/dist/types/css/property-descriptors/text-decoration-thickness.d.ts +3 -0
- package/dist/types/css/property-descriptors/text-overflow.d.ts +6 -0
- package/dist/types/css/property-descriptors/text-underline-offset.d.ts +3 -0
- package/dist/types/css/property-descriptors/webkit-line-clamp.d.ts +7 -0
- package/dist/types/dom/document-cloner.d.ts +2 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/render/canvas/canvas-renderer.d.ts +10 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 1.6.
|
|
2
|
+
* html2canvas-pro 1.6.6 <https://yorickshan.github.io/html2canvas-pro/>
|
|
3
3
|
* Copyright (c) 2024-present yorickshan and html2canvas-pro contributors
|
|
4
4
|
* Released under MIT License
|
|
5
5
|
*/
|
|
@@ -4120,6 +4120,71 @@ const textDecorationLine = {
|
|
|
4120
4120
|
}
|
|
4121
4121
|
};
|
|
4122
4122
|
|
|
4123
|
+
const textDecorationStyle = {
|
|
4124
|
+
name: 'text-decoration-style',
|
|
4125
|
+
initialValue: 'solid',
|
|
4126
|
+
prefix: false,
|
|
4127
|
+
type: 2 /* PropertyDescriptorParsingType.IDENT_VALUE */,
|
|
4128
|
+
parse: (_context, style) => {
|
|
4129
|
+
switch (style) {
|
|
4130
|
+
case 'double':
|
|
4131
|
+
return 1 /* TEXT_DECORATION_STYLE.DOUBLE */;
|
|
4132
|
+
case 'dotted':
|
|
4133
|
+
return 2 /* TEXT_DECORATION_STYLE.DOTTED */;
|
|
4134
|
+
case 'dashed':
|
|
4135
|
+
return 3 /* TEXT_DECORATION_STYLE.DASHED */;
|
|
4136
|
+
case 'wavy':
|
|
4137
|
+
return 4 /* TEXT_DECORATION_STYLE.WAVY */;
|
|
4138
|
+
case 'solid':
|
|
4139
|
+
default:
|
|
4140
|
+
return 0 /* TEXT_DECORATION_STYLE.SOLID */;
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
|
|
4145
|
+
const textDecorationThickness = {
|
|
4146
|
+
name: 'text-decoration-thickness',
|
|
4147
|
+
initialValue: 'auto',
|
|
4148
|
+
prefix: false,
|
|
4149
|
+
type: 0 /* PropertyDescriptorParsingType.VALUE */,
|
|
4150
|
+
parse: (_context, token) => {
|
|
4151
|
+
if (isIdentToken(token)) {
|
|
4152
|
+
switch (token.value) {
|
|
4153
|
+
case 'auto':
|
|
4154
|
+
return 'auto';
|
|
4155
|
+
case 'from-font':
|
|
4156
|
+
return 'from-font';
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
if (isDimensionToken(token)) {
|
|
4160
|
+
// Convert to pixels
|
|
4161
|
+
return token.number;
|
|
4162
|
+
}
|
|
4163
|
+
// Default to auto
|
|
4164
|
+
return 'auto';
|
|
4165
|
+
}
|
|
4166
|
+
};
|
|
4167
|
+
|
|
4168
|
+
const textUnderlineOffset = {
|
|
4169
|
+
name: 'text-underline-offset',
|
|
4170
|
+
initialValue: 'auto',
|
|
4171
|
+
prefix: false,
|
|
4172
|
+
type: 0 /* PropertyDescriptorParsingType.VALUE */,
|
|
4173
|
+
parse: (_context, token) => {
|
|
4174
|
+
if (isIdentToken(token)) {
|
|
4175
|
+
if (token.value === 'auto') {
|
|
4176
|
+
return 'auto';
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
if (isDimensionToken(token)) {
|
|
4180
|
+
// Return pixel value
|
|
4181
|
+
return token.number;
|
|
4182
|
+
}
|
|
4183
|
+
// Default to auto
|
|
4184
|
+
return 'auto';
|
|
4185
|
+
}
|
|
4186
|
+
};
|
|
4187
|
+
|
|
4123
4188
|
const fontFamily = {
|
|
4124
4189
|
name: `font-family`,
|
|
4125
4190
|
initialValue: '',
|
|
@@ -4423,6 +4488,30 @@ const webkitTextStrokeWidth = {
|
|
|
4423
4488
|
}
|
|
4424
4489
|
};
|
|
4425
4490
|
|
|
4491
|
+
/**
|
|
4492
|
+
* -webkit-line-clamp property descriptor
|
|
4493
|
+
* Used with display: -webkit-box and -webkit-box-orient: vertical
|
|
4494
|
+
* to limit text to a specific number of lines
|
|
4495
|
+
*/
|
|
4496
|
+
const webkitLineClamp = {
|
|
4497
|
+
name: '-webkit-line-clamp',
|
|
4498
|
+
initialValue: 'none',
|
|
4499
|
+
prefix: true,
|
|
4500
|
+
type: 0 /* PropertyDescriptorParsingType.VALUE */,
|
|
4501
|
+
parse: (_context, token) => {
|
|
4502
|
+
// 'none' means no line clamping
|
|
4503
|
+
if (token.type === 20 /* TokenType.IDENT_TOKEN */ && token.value === 'none') {
|
|
4504
|
+
return 0;
|
|
4505
|
+
}
|
|
4506
|
+
// Number value specifies the number of lines
|
|
4507
|
+
if (token.type === 17 /* TokenType.NUMBER_TOKEN */) {
|
|
4508
|
+
return Math.max(0, Math.floor(token.number));
|
|
4509
|
+
}
|
|
4510
|
+
// Default to 0 (no clamping)
|
|
4511
|
+
return 0;
|
|
4512
|
+
}
|
|
4513
|
+
};
|
|
4514
|
+
|
|
4426
4515
|
const objectFit = {
|
|
4427
4516
|
name: 'objectFit',
|
|
4428
4517
|
initialValue: 'fill',
|
|
@@ -4448,6 +4537,22 @@ const parseDisplayValue = (display) => {
|
|
|
4448
4537
|
return 0 /* OBJECT_FIT.FILL */;
|
|
4449
4538
|
};
|
|
4450
4539
|
|
|
4540
|
+
const textOverflow = {
|
|
4541
|
+
name: 'text-overflow',
|
|
4542
|
+
initialValue: 'clip',
|
|
4543
|
+
prefix: false,
|
|
4544
|
+
type: 2 /* PropertyDescriptorParsingType.IDENT_VALUE */,
|
|
4545
|
+
parse: (_context, textOverflow) => {
|
|
4546
|
+
switch (textOverflow) {
|
|
4547
|
+
case 'ellipsis':
|
|
4548
|
+
return 1 /* TEXT_OVERFLOW.ELLIPSIS */;
|
|
4549
|
+
case 'clip':
|
|
4550
|
+
default:
|
|
4551
|
+
return 0 /* TEXT_OVERFLOW.CLIP */;
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
};
|
|
4555
|
+
|
|
4451
4556
|
class CSSParsedDeclaration {
|
|
4452
4557
|
constructor(context, declaration) {
|
|
4453
4558
|
this.animationDuration = parse(context, duration, declaration.animationDuration);
|
|
@@ -4508,14 +4613,19 @@ class CSSParsedDeclaration {
|
|
|
4508
4613
|
this.textAlign = parse(context, textAlign, declaration.textAlign);
|
|
4509
4614
|
this.textDecorationColor = parse(context, textDecorationColor, declaration.textDecorationColor ?? declaration.color);
|
|
4510
4615
|
this.textDecorationLine = parse(context, textDecorationLine, declaration.textDecorationLine ?? declaration.textDecoration);
|
|
4616
|
+
this.textDecorationStyle = parse(context, textDecorationStyle, declaration.textDecorationStyle);
|
|
4617
|
+
this.textDecorationThickness = parse(context, textDecorationThickness, declaration.textDecorationThickness);
|
|
4618
|
+
this.textUnderlineOffset = parse(context, textUnderlineOffset, declaration.textUnderlineOffset);
|
|
4511
4619
|
this.textShadow = parse(context, textShadow, declaration.textShadow);
|
|
4512
4620
|
this.textTransform = parse(context, textTransform, declaration.textTransform);
|
|
4621
|
+
this.textOverflow = parse(context, textOverflow, declaration.textOverflow);
|
|
4513
4622
|
this.transform = parse(context, transform$1, declaration.transform);
|
|
4514
4623
|
this.transformOrigin = parse(context, transformOrigin, declaration.transformOrigin);
|
|
4515
4624
|
this.rotate = parse(context, rotate, declaration.rotate);
|
|
4516
4625
|
this.visibility = parse(context, visibility, declaration.visibility);
|
|
4517
4626
|
this.webkitTextStrokeColor = parse(context, webkitTextStrokeColor, declaration.webkitTextStrokeColor);
|
|
4518
4627
|
this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);
|
|
4628
|
+
this.webkitLineClamp = parse(context, webkitLineClamp, declaration.webkitLineClamp);
|
|
4519
4629
|
this.wordBreak = parse(context, wordBreak, declaration.wordBreak);
|
|
4520
4630
|
this.zIndex = parse(context, zIndex, declaration.zIndex);
|
|
4521
4631
|
this.objectFit = parse(context, objectFit, declaration.objectFit);
|
|
@@ -6004,6 +6114,27 @@ const createCounterText = (value, type, appendSuffix) => {
|
|
|
6004
6114
|
};
|
|
6005
6115
|
|
|
6006
6116
|
const IGNORE_ATTRIBUTE = 'data-html2canvas-ignore';
|
|
6117
|
+
/**
|
|
6118
|
+
* Find the parent ShadowRoot of an element, if any
|
|
6119
|
+
* @param element - The element to check
|
|
6120
|
+
* @returns The parent ShadowRoot or null
|
|
6121
|
+
*/
|
|
6122
|
+
const findParentShadowRoot = (element) => {
|
|
6123
|
+
let current = element;
|
|
6124
|
+
while (current) {
|
|
6125
|
+
// Check if we've reached a shadow root boundary
|
|
6126
|
+
if (current.parentNode && current.parentNode.host) {
|
|
6127
|
+
return current.parentNode;
|
|
6128
|
+
}
|
|
6129
|
+
// Use getRootNode to check if we're in a shadow root
|
|
6130
|
+
const root = current.getRootNode();
|
|
6131
|
+
if (root && root !== current.ownerDocument && root.host) {
|
|
6132
|
+
return root;
|
|
6133
|
+
}
|
|
6134
|
+
current = current.parentNode;
|
|
6135
|
+
}
|
|
6136
|
+
return null;
|
|
6137
|
+
};
|
|
6007
6138
|
class DocumentCloner {
|
|
6008
6139
|
constructor(context, element, options) {
|
|
6009
6140
|
this.context = context;
|
|
@@ -6015,10 +6146,17 @@ class DocumentCloner {
|
|
|
6015
6146
|
if (!element.ownerDocument) {
|
|
6016
6147
|
throw new Error('Cloned element does not have an owner document');
|
|
6017
6148
|
}
|
|
6149
|
+
// Auto-detect Shadow Root if not explicitly provided
|
|
6150
|
+
if (!this.options.iframeContainer) {
|
|
6151
|
+
const shadowRoot = findParentShadowRoot(element);
|
|
6152
|
+
if (shadowRoot) {
|
|
6153
|
+
this.options.iframeContainer = shadowRoot;
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6018
6156
|
this.documentElement = this.cloneNode(element.ownerDocument.documentElement, false);
|
|
6019
6157
|
}
|
|
6020
6158
|
toIFrame(ownerDocument, windowSize) {
|
|
6021
|
-
const iframe = createIFrameContainer(ownerDocument, windowSize);
|
|
6159
|
+
const iframe = createIFrameContainer(ownerDocument, windowSize, this.options.iframeContainer);
|
|
6022
6160
|
if (!iframe.contentWindow) {
|
|
6023
6161
|
return Promise.reject(`Unable to find iframe window`);
|
|
6024
6162
|
}
|
|
@@ -6124,20 +6262,8 @@ class DocumentCloner {
|
|
|
6124
6262
|
return clone;
|
|
6125
6263
|
}
|
|
6126
6264
|
createCustomElementClone(node) {
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
try {
|
|
6130
|
-
customElements.define('html2canvascustomelement', class extends HTMLElement {
|
|
6131
|
-
constructor() {
|
|
6132
|
-
super();
|
|
6133
|
-
}
|
|
6134
|
-
});
|
|
6135
|
-
}
|
|
6136
|
-
catch (e) {
|
|
6137
|
-
// Already defined or cannot define
|
|
6138
|
-
}
|
|
6139
|
-
}
|
|
6140
|
-
const clone = document.createElement('html2canvascustomelement');
|
|
6265
|
+
const clone = document.createElement('div');
|
|
6266
|
+
clone.className = node.className;
|
|
6141
6267
|
copyCSSStyles(node.style, clone);
|
|
6142
6268
|
// Clone shadow DOM if it exists
|
|
6143
6269
|
// Fix for Issue #108: This is critical for Web Components with slots to work correctly
|
|
@@ -6166,6 +6292,9 @@ class DocumentCloner {
|
|
|
6166
6292
|
}, '');
|
|
6167
6293
|
const style = node.cloneNode(false);
|
|
6168
6294
|
style.textContent = css;
|
|
6295
|
+
if (this.options.cspNonce) {
|
|
6296
|
+
style.nonce = this.options.cspNonce;
|
|
6297
|
+
}
|
|
6169
6298
|
return style;
|
|
6170
6299
|
}
|
|
6171
6300
|
}
|
|
@@ -6176,7 +6305,11 @@ class DocumentCloner {
|
|
|
6176
6305
|
throw e;
|
|
6177
6306
|
}
|
|
6178
6307
|
}
|
|
6179
|
-
|
|
6308
|
+
const cloned = node.cloneNode(false);
|
|
6309
|
+
if (this.options.cspNonce) {
|
|
6310
|
+
cloned.nonce = this.options.cspNonce;
|
|
6311
|
+
}
|
|
6312
|
+
return cloned;
|
|
6180
6313
|
}
|
|
6181
6314
|
createCanvasClone(canvas) {
|
|
6182
6315
|
if (this.options.inlineImages && canvas.ownerDocument) {
|
|
@@ -6281,7 +6414,7 @@ class DocumentCloner {
|
|
|
6281
6414
|
this.clonedReferenceElement = clone;
|
|
6282
6415
|
}
|
|
6283
6416
|
if (isBodyElement(clone)) {
|
|
6284
|
-
createPseudoHideStyles(clone);
|
|
6417
|
+
createPseudoHideStyles(clone, this.options.cspNonce);
|
|
6285
6418
|
}
|
|
6286
6419
|
const counters = this.counters.parse(new CSSParsedCounterDeclaration(this.context, style));
|
|
6287
6420
|
const before = this.resolvePseudoContent(node, clone, styleBefore, PseudoElementType.BEFORE);
|
|
@@ -6409,7 +6542,7 @@ var PseudoElementType;
|
|
|
6409
6542
|
PseudoElementType[PseudoElementType["BEFORE"] = 0] = "BEFORE";
|
|
6410
6543
|
PseudoElementType[PseudoElementType["AFTER"] = 1] = "AFTER";
|
|
6411
6544
|
})(PseudoElementType || (PseudoElementType = {}));
|
|
6412
|
-
const createIFrameContainer = (ownerDocument, bounds) => {
|
|
6545
|
+
const createIFrameContainer = (ownerDocument, bounds, customContainer) => {
|
|
6413
6546
|
const cloneIframeContainer = ownerDocument.createElement('iframe');
|
|
6414
6547
|
cloneIframeContainer.className = 'html2canvas-container';
|
|
6415
6548
|
cloneIframeContainer.style.visibility = 'hidden';
|
|
@@ -6421,7 +6554,9 @@ const createIFrameContainer = (ownerDocument, bounds) => {
|
|
|
6421
6554
|
cloneIframeContainer.height = bounds.height.toString();
|
|
6422
6555
|
cloneIframeContainer.scrolling = 'no'; // ios won't scroll without it
|
|
6423
6556
|
cloneIframeContainer.setAttribute(IGNORE_ATTRIBUTE, 'true');
|
|
6424
|
-
|
|
6557
|
+
// Use custom container if provided, otherwise use body
|
|
6558
|
+
const container = customContainer || ownerDocument.body;
|
|
6559
|
+
container.appendChild(cloneIframeContainer);
|
|
6425
6560
|
return cloneIframeContainer;
|
|
6426
6561
|
};
|
|
6427
6562
|
const imageReady = (img) => {
|
|
@@ -6514,15 +6649,18 @@ const PSEUDO_HIDE_ELEMENT_STYLE = `{
|
|
|
6514
6649
|
content: "" !important;
|
|
6515
6650
|
display: none !important;
|
|
6516
6651
|
}`;
|
|
6517
|
-
const createPseudoHideStyles = (body) => {
|
|
6652
|
+
const createPseudoHideStyles = (body, cspNonce) => {
|
|
6518
6653
|
createStyles(body, `.${PSEUDO_HIDE_ELEMENT_CLASS_BEFORE}${PSEUDO_BEFORE}${PSEUDO_HIDE_ELEMENT_STYLE}
|
|
6519
|
-
.${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}${PSEUDO_AFTER}${PSEUDO_HIDE_ELEMENT_STYLE}
|
|
6654
|
+
.${PSEUDO_HIDE_ELEMENT_CLASS_AFTER}${PSEUDO_AFTER}${PSEUDO_HIDE_ELEMENT_STYLE}`, cspNonce);
|
|
6520
6655
|
};
|
|
6521
|
-
const createStyles = (body, styles) => {
|
|
6656
|
+
const createStyles = (body, styles, cspNonce) => {
|
|
6522
6657
|
const document = body.ownerDocument;
|
|
6523
6658
|
if (document) {
|
|
6524
6659
|
const style = document.createElement('style');
|
|
6525
6660
|
style.textContent = styles;
|
|
6661
|
+
if (cspNonce) {
|
|
6662
|
+
style.nonce = cspNonce;
|
|
6663
|
+
}
|
|
6526
6664
|
body.appendChild(style);
|
|
6527
6665
|
}
|
|
6528
6666
|
};
|
|
@@ -7525,6 +7663,156 @@ class CanvasRenderer extends Renderer {
|
|
|
7525
7663
|
}, text.bounds.left);
|
|
7526
7664
|
}
|
|
7527
7665
|
}
|
|
7666
|
+
/**
|
|
7667
|
+
* Helper method to render text with paint order support
|
|
7668
|
+
* Reduces code duplication in line-clamp and normal rendering
|
|
7669
|
+
*/
|
|
7670
|
+
renderTextBoundWithPaintOrder(textBound, styles, paintOrderLayers) {
|
|
7671
|
+
paintOrderLayers.forEach((paintOrderLayer) => {
|
|
7672
|
+
switch (paintOrderLayer) {
|
|
7673
|
+
case 0 /* PAINT_ORDER_LAYER.FILL */:
|
|
7674
|
+
this.ctx.fillStyle = asString(styles.color);
|
|
7675
|
+
this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number);
|
|
7676
|
+
break;
|
|
7677
|
+
case 1 /* PAINT_ORDER_LAYER.STROKE */:
|
|
7678
|
+
if (styles.webkitTextStrokeWidth && textBound.text.trim().length) {
|
|
7679
|
+
this.ctx.strokeStyle = asString(styles.webkitTextStrokeColor);
|
|
7680
|
+
this.ctx.lineWidth = styles.webkitTextStrokeWidth;
|
|
7681
|
+
this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
|
|
7682
|
+
this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number);
|
|
7683
|
+
}
|
|
7684
|
+
break;
|
|
7685
|
+
}
|
|
7686
|
+
});
|
|
7687
|
+
}
|
|
7688
|
+
renderTextDecoration(bounds, styles) {
|
|
7689
|
+
this.ctx.fillStyle = asString(styles.textDecorationColor || styles.color);
|
|
7690
|
+
// Calculate decoration line thickness
|
|
7691
|
+
let thickness = 1; // default
|
|
7692
|
+
if (typeof styles.textDecorationThickness === 'number') {
|
|
7693
|
+
thickness = styles.textDecorationThickness;
|
|
7694
|
+
}
|
|
7695
|
+
else if (styles.textDecorationThickness === 'from-font') {
|
|
7696
|
+
// Use a reasonable default based on font size
|
|
7697
|
+
thickness = Math.max(1, Math.floor(styles.fontSize.number * 0.05));
|
|
7698
|
+
}
|
|
7699
|
+
// 'auto' uses default thickness of 1
|
|
7700
|
+
// Calculate underline offset
|
|
7701
|
+
let underlineOffset = 0;
|
|
7702
|
+
if (typeof styles.textUnderlineOffset === 'number') {
|
|
7703
|
+
// It's a pixel value
|
|
7704
|
+
underlineOffset = styles.textUnderlineOffset;
|
|
7705
|
+
}
|
|
7706
|
+
// 'auto' uses default offset of 0
|
|
7707
|
+
const decorationStyle = styles.textDecorationStyle;
|
|
7708
|
+
styles.textDecorationLine.forEach((textDecorationLine) => {
|
|
7709
|
+
let y = 0;
|
|
7710
|
+
switch (textDecorationLine) {
|
|
7711
|
+
case 1 /* TEXT_DECORATION_LINE.UNDERLINE */:
|
|
7712
|
+
y = bounds.top + bounds.height - thickness + underlineOffset;
|
|
7713
|
+
break;
|
|
7714
|
+
case 2 /* TEXT_DECORATION_LINE.OVERLINE */:
|
|
7715
|
+
y = bounds.top;
|
|
7716
|
+
break;
|
|
7717
|
+
case 3 /* TEXT_DECORATION_LINE.LINE_THROUGH */:
|
|
7718
|
+
y = bounds.top + (bounds.height / 2 - thickness / 2);
|
|
7719
|
+
break;
|
|
7720
|
+
default:
|
|
7721
|
+
return;
|
|
7722
|
+
}
|
|
7723
|
+
this.drawDecorationLine(bounds.left, y, bounds.width, thickness, decorationStyle);
|
|
7724
|
+
});
|
|
7725
|
+
}
|
|
7726
|
+
drawDecorationLine(x, y, width, thickness, style) {
|
|
7727
|
+
switch (style) {
|
|
7728
|
+
case 0 /* TEXT_DECORATION_STYLE.SOLID */:
|
|
7729
|
+
// Solid line (default)
|
|
7730
|
+
this.ctx.fillRect(x, y, width, thickness);
|
|
7731
|
+
break;
|
|
7732
|
+
case 1 /* TEXT_DECORATION_STYLE.DOUBLE */:
|
|
7733
|
+
// Double line
|
|
7734
|
+
const gap = Math.max(1, thickness);
|
|
7735
|
+
this.ctx.fillRect(x, y, width, thickness);
|
|
7736
|
+
this.ctx.fillRect(x, y + thickness + gap, width, thickness);
|
|
7737
|
+
break;
|
|
7738
|
+
case 2 /* TEXT_DECORATION_STYLE.DOTTED */:
|
|
7739
|
+
// Dotted line
|
|
7740
|
+
this.ctx.save();
|
|
7741
|
+
this.ctx.beginPath();
|
|
7742
|
+
this.ctx.setLineDash([thickness, thickness * 2]);
|
|
7743
|
+
this.ctx.lineWidth = thickness;
|
|
7744
|
+
this.ctx.strokeStyle = this.ctx.fillStyle;
|
|
7745
|
+
this.ctx.moveTo(x, y + thickness / 2);
|
|
7746
|
+
this.ctx.lineTo(x + width, y + thickness / 2);
|
|
7747
|
+
this.ctx.stroke();
|
|
7748
|
+
this.ctx.restore();
|
|
7749
|
+
break;
|
|
7750
|
+
case 3 /* TEXT_DECORATION_STYLE.DASHED */:
|
|
7751
|
+
// Dashed line
|
|
7752
|
+
this.ctx.save();
|
|
7753
|
+
this.ctx.beginPath();
|
|
7754
|
+
this.ctx.setLineDash([thickness * 3, thickness * 2]);
|
|
7755
|
+
this.ctx.lineWidth = thickness;
|
|
7756
|
+
this.ctx.strokeStyle = this.ctx.fillStyle;
|
|
7757
|
+
this.ctx.moveTo(x, y + thickness / 2);
|
|
7758
|
+
this.ctx.lineTo(x + width, y + thickness / 2);
|
|
7759
|
+
this.ctx.stroke();
|
|
7760
|
+
this.ctx.restore();
|
|
7761
|
+
break;
|
|
7762
|
+
case 4 /* TEXT_DECORATION_STYLE.WAVY */:
|
|
7763
|
+
// Wavy line (approximation using quadratic curves)
|
|
7764
|
+
this.ctx.save();
|
|
7765
|
+
this.ctx.beginPath();
|
|
7766
|
+
this.ctx.lineWidth = thickness;
|
|
7767
|
+
this.ctx.strokeStyle = this.ctx.fillStyle;
|
|
7768
|
+
const amplitude = thickness * 2;
|
|
7769
|
+
const wavelength = thickness * 4;
|
|
7770
|
+
let currentX = x;
|
|
7771
|
+
this.ctx.moveTo(currentX, y + thickness / 2);
|
|
7772
|
+
while (currentX < x + width) {
|
|
7773
|
+
const nextX = Math.min(currentX + wavelength / 2, x + width);
|
|
7774
|
+
this.ctx.quadraticCurveTo(currentX + wavelength / 4, y + thickness / 2 - amplitude, nextX, y + thickness / 2);
|
|
7775
|
+
currentX = nextX;
|
|
7776
|
+
if (currentX < x + width) {
|
|
7777
|
+
const nextX2 = Math.min(currentX + wavelength / 2, x + width);
|
|
7778
|
+
this.ctx.quadraticCurveTo(currentX + wavelength / 4, y + thickness / 2 + amplitude, nextX2, y + thickness / 2);
|
|
7779
|
+
currentX = nextX2;
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
this.ctx.stroke();
|
|
7783
|
+
this.ctx.restore();
|
|
7784
|
+
break;
|
|
7785
|
+
default:
|
|
7786
|
+
// Fallback to solid
|
|
7787
|
+
this.ctx.fillRect(x, y, width, thickness);
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7790
|
+
// Helper method to truncate text and add ellipsis if needed
|
|
7791
|
+
truncateTextWithEllipsis(text, maxWidth, letterSpacing) {
|
|
7792
|
+
const ellipsis = '...';
|
|
7793
|
+
const ellipsisWidth = this.ctx.measureText(ellipsis).width;
|
|
7794
|
+
if (letterSpacing === 0) {
|
|
7795
|
+
let truncated = text;
|
|
7796
|
+
while (this.ctx.measureText(truncated).width + ellipsisWidth > maxWidth && truncated.length > 0) {
|
|
7797
|
+
truncated = truncated.slice(0, -1);
|
|
7798
|
+
}
|
|
7799
|
+
return truncated + ellipsis;
|
|
7800
|
+
}
|
|
7801
|
+
else {
|
|
7802
|
+
const letters = segmentGraphemes(text);
|
|
7803
|
+
let width = ellipsisWidth;
|
|
7804
|
+
let result = [];
|
|
7805
|
+
for (const letter of letters) {
|
|
7806
|
+
const letterWidth = this.ctx.measureText(letter).width + letterSpacing;
|
|
7807
|
+
if (width + letterWidth > maxWidth) {
|
|
7808
|
+
break;
|
|
7809
|
+
}
|
|
7810
|
+
result.push(letter);
|
|
7811
|
+
width += letterWidth;
|
|
7812
|
+
}
|
|
7813
|
+
return result.join('') + ellipsis;
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7528
7816
|
createFontStyle(styles) {
|
|
7529
7817
|
const fontVariant = styles.fontVariant
|
|
7530
7818
|
.filter((variant) => variant === 'normal' || variant === 'small-caps')
|
|
@@ -7539,13 +7827,195 @@ class CanvasRenderer extends Renderer {
|
|
|
7539
7827
|
fontSize
|
|
7540
7828
|
];
|
|
7541
7829
|
}
|
|
7542
|
-
async renderTextNode(text, styles) {
|
|
7830
|
+
async renderTextNode(text, styles, containerBounds) {
|
|
7543
7831
|
const [font] = this.createFontStyle(styles);
|
|
7544
7832
|
this.ctx.font = font;
|
|
7545
7833
|
this.ctx.direction = styles.direction === 1 /* DIRECTION.RTL */ ? 'rtl' : 'ltr';
|
|
7546
7834
|
this.ctx.textAlign = 'left';
|
|
7547
7835
|
this.ctx.textBaseline = 'alphabetic';
|
|
7548
7836
|
const paintOrder = styles.paintOrder;
|
|
7837
|
+
// Calculate line height for text layout detection (used by both line-clamp and ellipsis)
|
|
7838
|
+
const lineHeight = styles.fontSize.number * 1.5;
|
|
7839
|
+
// Check if we need to apply -webkit-line-clamp
|
|
7840
|
+
// This limits text to a specific number of lines with ellipsis
|
|
7841
|
+
const shouldApplyLineClamp = styles.webkitLineClamp > 0 &&
|
|
7842
|
+
(styles.display & 2 /* DISPLAY.BLOCK */) !== 0 &&
|
|
7843
|
+
styles.overflowY === 1 /* OVERFLOW.HIDDEN */ &&
|
|
7844
|
+
text.textBounds.length > 0;
|
|
7845
|
+
if (shouldApplyLineClamp) {
|
|
7846
|
+
// Group text bounds by lines based on their Y position
|
|
7847
|
+
const lines = [];
|
|
7848
|
+
let currentLine = [];
|
|
7849
|
+
let currentLineTop = text.textBounds[0].bounds.top;
|
|
7850
|
+
text.textBounds.forEach((tb) => {
|
|
7851
|
+
// If this text bound is on a different line, start a new line
|
|
7852
|
+
if (Math.abs(tb.bounds.top - currentLineTop) >= lineHeight * 0.5) {
|
|
7853
|
+
if (currentLine.length > 0) {
|
|
7854
|
+
lines.push(currentLine);
|
|
7855
|
+
}
|
|
7856
|
+
currentLine = [tb];
|
|
7857
|
+
currentLineTop = tb.bounds.top;
|
|
7858
|
+
}
|
|
7859
|
+
else {
|
|
7860
|
+
currentLine.push(tb);
|
|
7861
|
+
}
|
|
7862
|
+
});
|
|
7863
|
+
// Don't forget the last line
|
|
7864
|
+
if (currentLine.length > 0) {
|
|
7865
|
+
lines.push(currentLine);
|
|
7866
|
+
}
|
|
7867
|
+
// Only render up to webkitLineClamp lines
|
|
7868
|
+
const maxLines = styles.webkitLineClamp;
|
|
7869
|
+
if (lines.length > maxLines) {
|
|
7870
|
+
// Render only the first (maxLines - 1) complete lines
|
|
7871
|
+
for (let i = 0; i < maxLines - 1; i++) {
|
|
7872
|
+
lines[i].forEach((textBound) => {
|
|
7873
|
+
this.renderTextBoundWithPaintOrder(textBound, styles, paintOrder);
|
|
7874
|
+
});
|
|
7875
|
+
}
|
|
7876
|
+
// For the last line, truncate with ellipsis
|
|
7877
|
+
const lastLine = lines[maxLines - 1];
|
|
7878
|
+
if (lastLine && lastLine.length > 0 && containerBounds) {
|
|
7879
|
+
const lastLineText = lastLine.map((tb) => tb.text).join('');
|
|
7880
|
+
const firstBound = lastLine[0];
|
|
7881
|
+
const availableWidth = containerBounds.width - (firstBound.bounds.left - containerBounds.left);
|
|
7882
|
+
const truncatedText = this.truncateTextWithEllipsis(lastLineText, availableWidth, styles.letterSpacing);
|
|
7883
|
+
paintOrder.forEach((paintOrderLayer) => {
|
|
7884
|
+
switch (paintOrderLayer) {
|
|
7885
|
+
case 0 /* PAINT_ORDER_LAYER.FILL */:
|
|
7886
|
+
this.ctx.fillStyle = asString(styles.color);
|
|
7887
|
+
if (styles.letterSpacing === 0) {
|
|
7888
|
+
this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
|
|
7889
|
+
}
|
|
7890
|
+
else {
|
|
7891
|
+
const letters = segmentGraphemes(truncatedText);
|
|
7892
|
+
letters.reduce((left, letter) => {
|
|
7893
|
+
this.ctx.fillText(letter, left, firstBound.bounds.top + styles.fontSize.number);
|
|
7894
|
+
return left + this.ctx.measureText(letter).width + styles.letterSpacing;
|
|
7895
|
+
}, firstBound.bounds.left);
|
|
7896
|
+
}
|
|
7897
|
+
break;
|
|
7898
|
+
case 1 /* PAINT_ORDER_LAYER.STROKE */:
|
|
7899
|
+
if (styles.webkitTextStrokeWidth && truncatedText.trim().length) {
|
|
7900
|
+
this.ctx.strokeStyle = asString(styles.webkitTextStrokeColor);
|
|
7901
|
+
this.ctx.lineWidth = styles.webkitTextStrokeWidth;
|
|
7902
|
+
this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
|
|
7903
|
+
if (styles.letterSpacing === 0) {
|
|
7904
|
+
this.ctx.strokeText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
|
|
7905
|
+
}
|
|
7906
|
+
else {
|
|
7907
|
+
const letters = segmentGraphemes(truncatedText);
|
|
7908
|
+
letters.reduce((left, letter) => {
|
|
7909
|
+
this.ctx.strokeText(letter, left, firstBound.bounds.top + styles.fontSize.number);
|
|
7910
|
+
return left + this.ctx.measureText(letter).width + styles.letterSpacing;
|
|
7911
|
+
}, firstBound.bounds.left);
|
|
7912
|
+
}
|
|
7913
|
+
}
|
|
7914
|
+
break;
|
|
7915
|
+
}
|
|
7916
|
+
});
|
|
7917
|
+
}
|
|
7918
|
+
return; // Don't render anything else
|
|
7919
|
+
}
|
|
7920
|
+
// If lines.length <= maxLines, fall through to normal rendering
|
|
7921
|
+
}
|
|
7922
|
+
// Check if we need to apply text-overflow: ellipsis
|
|
7923
|
+
// Issue #203: Only apply ellipsis for single-line text overflow
|
|
7924
|
+
// Multi-line text truncation (like -webkit-line-clamp) should not be affected
|
|
7925
|
+
const shouldApplyEllipsis = styles.textOverflow === 1 /* TEXT_OVERFLOW.ELLIPSIS */ &&
|
|
7926
|
+
containerBounds &&
|
|
7927
|
+
styles.overflowX === 1 /* OVERFLOW.HIDDEN */ &&
|
|
7928
|
+
text.textBounds.length > 0;
|
|
7929
|
+
// Calculate total text width if ellipsis might be needed
|
|
7930
|
+
let needsEllipsis = false;
|
|
7931
|
+
let truncatedText = '';
|
|
7932
|
+
if (shouldApplyEllipsis) {
|
|
7933
|
+
// Check if all text bounds are on approximately the same line (single-line scenario)
|
|
7934
|
+
// For multi-line text (like -webkit-line-clamp), textBounds will have different Y positions
|
|
7935
|
+
const firstTop = text.textBounds[0].bounds.top;
|
|
7936
|
+
const isSingleLine = text.textBounds.every((tb) => Math.abs(tb.bounds.top - firstTop) < lineHeight * 0.5);
|
|
7937
|
+
if (isSingleLine) {
|
|
7938
|
+
// Measure the full text content
|
|
7939
|
+
// Note: text.textBounds may contain whitespace characters from HTML formatting
|
|
7940
|
+
// We need to collapse them like the browser does for white-space: nowrap
|
|
7941
|
+
let fullText = text.textBounds.map((tb) => tb.text).join('');
|
|
7942
|
+
// Collapse whitespace: replace sequences of whitespace (including newlines) with single spaces
|
|
7943
|
+
// and trim leading/trailing whitespace
|
|
7944
|
+
fullText = fullText.replace(/\s+/g, ' ').trim();
|
|
7945
|
+
const fullTextWidth = this.ctx.measureText(fullText).width;
|
|
7946
|
+
const availableWidth = containerBounds.width;
|
|
7947
|
+
if (fullTextWidth > availableWidth) {
|
|
7948
|
+
needsEllipsis = true;
|
|
7949
|
+
truncatedText = this.truncateTextWithEllipsis(fullText, availableWidth, styles.letterSpacing);
|
|
7950
|
+
}
|
|
7951
|
+
}
|
|
7952
|
+
}
|
|
7953
|
+
// If ellipsis is needed, render the truncated text once
|
|
7954
|
+
if (needsEllipsis) {
|
|
7955
|
+
const firstBound = text.textBounds[0];
|
|
7956
|
+
paintOrder.forEach((paintOrderLayer) => {
|
|
7957
|
+
switch (paintOrderLayer) {
|
|
7958
|
+
case 0 /* PAINT_ORDER_LAYER.FILL */:
|
|
7959
|
+
this.ctx.fillStyle = asString(styles.color);
|
|
7960
|
+
if (styles.letterSpacing === 0) {
|
|
7961
|
+
this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
|
|
7962
|
+
}
|
|
7963
|
+
else {
|
|
7964
|
+
const letters = segmentGraphemes(truncatedText);
|
|
7965
|
+
letters.reduce((left, letter) => {
|
|
7966
|
+
this.ctx.fillText(letter, left, firstBound.bounds.top + styles.fontSize.number);
|
|
7967
|
+
return left + this.ctx.measureText(letter).width + styles.letterSpacing;
|
|
7968
|
+
}, firstBound.bounds.left);
|
|
7969
|
+
}
|
|
7970
|
+
const textShadows = styles.textShadow;
|
|
7971
|
+
if (textShadows.length && truncatedText.trim().length) {
|
|
7972
|
+
textShadows
|
|
7973
|
+
.slice(0)
|
|
7974
|
+
.reverse()
|
|
7975
|
+
.forEach((textShadow) => {
|
|
7976
|
+
this.ctx.shadowColor = asString(textShadow.color);
|
|
7977
|
+
this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
|
|
7978
|
+
this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
|
|
7979
|
+
this.ctx.shadowBlur = textShadow.blur.number;
|
|
7980
|
+
if (styles.letterSpacing === 0) {
|
|
7981
|
+
this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
|
|
7982
|
+
}
|
|
7983
|
+
else {
|
|
7984
|
+
const letters = segmentGraphemes(truncatedText);
|
|
7985
|
+
letters.reduce((left, letter) => {
|
|
7986
|
+
this.ctx.fillText(letter, left, firstBound.bounds.top + styles.fontSize.number);
|
|
7987
|
+
return left + this.ctx.measureText(letter).width + styles.letterSpacing;
|
|
7988
|
+
}, firstBound.bounds.left);
|
|
7989
|
+
}
|
|
7990
|
+
});
|
|
7991
|
+
this.ctx.shadowColor = '';
|
|
7992
|
+
this.ctx.shadowOffsetX = 0;
|
|
7993
|
+
this.ctx.shadowOffsetY = 0;
|
|
7994
|
+
this.ctx.shadowBlur = 0;
|
|
7995
|
+
}
|
|
7996
|
+
break;
|
|
7997
|
+
case 1 /* PAINT_ORDER_LAYER.STROKE */:
|
|
7998
|
+
if (styles.webkitTextStrokeWidth && truncatedText.trim().length) {
|
|
7999
|
+
this.ctx.strokeStyle = asString(styles.webkitTextStrokeColor);
|
|
8000
|
+
this.ctx.lineWidth = styles.webkitTextStrokeWidth;
|
|
8001
|
+
this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
|
|
8002
|
+
if (styles.letterSpacing === 0) {
|
|
8003
|
+
this.ctx.strokeText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
|
|
8004
|
+
}
|
|
8005
|
+
else {
|
|
8006
|
+
const letters = segmentGraphemes(truncatedText);
|
|
8007
|
+
letters.reduce((left, letter) => {
|
|
8008
|
+
this.ctx.strokeText(letter, left, firstBound.bounds.top + styles.fontSize.number);
|
|
8009
|
+
return left + this.ctx.measureText(letter).width + styles.letterSpacing;
|
|
8010
|
+
}, firstBound.bounds.left);
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
break;
|
|
8014
|
+
}
|
|
8015
|
+
});
|
|
8016
|
+
return;
|
|
8017
|
+
}
|
|
8018
|
+
// Normal rendering (no ellipsis needed)
|
|
7549
8019
|
text.textBounds.forEach((text) => {
|
|
7550
8020
|
paintOrder.forEach((paintOrderLayer) => {
|
|
7551
8021
|
switch (paintOrderLayer) {
|
|
@@ -7570,22 +8040,7 @@ class CanvasRenderer extends Renderer {
|
|
|
7570
8040
|
this.ctx.shadowBlur = 0;
|
|
7571
8041
|
}
|
|
7572
8042
|
if (styles.textDecorationLine.length) {
|
|
7573
|
-
this.
|
|
7574
|
-
const decorationLineHeight = 1;
|
|
7575
|
-
styles.textDecorationLine.forEach((textDecorationLine) => {
|
|
7576
|
-
// Fix the issue where textDecorationLine exhibits x-axis positioning errors on high-resolution devices due to varying devicePixelRatio, corrected by using relative values of element heights.
|
|
7577
|
-
switch (textDecorationLine) {
|
|
7578
|
-
case 1 /* TEXT_DECORATION_LINE.UNDERLINE */:
|
|
7579
|
-
this.ctx.fillRect(text.bounds.left, text.bounds.top + text.bounds.height - decorationLineHeight, text.bounds.width, decorationLineHeight);
|
|
7580
|
-
break;
|
|
7581
|
-
case 2 /* TEXT_DECORATION_LINE.OVERLINE */:
|
|
7582
|
-
this.ctx.fillRect(text.bounds.left, text.bounds.top, text.bounds.width, decorationLineHeight);
|
|
7583
|
-
break;
|
|
7584
|
-
case 3 /* TEXT_DECORATION_LINE.LINE_THROUGH */:
|
|
7585
|
-
this.ctx.fillRect(text.bounds.left, text.bounds.top + (text.bounds.height / 2 - decorationLineHeight / 2), text.bounds.width, decorationLineHeight);
|
|
7586
|
-
break;
|
|
7587
|
-
}
|
|
7588
|
-
});
|
|
8043
|
+
this.renderTextDecoration(text.bounds, styles);
|
|
7589
8044
|
}
|
|
7590
8045
|
break;
|
|
7591
8046
|
case 1 /* PAINT_ORDER_LAYER.STROKE */:
|
|
@@ -7707,8 +8162,11 @@ class CanvasRenderer extends Renderer {
|
|
|
7707
8162
|
const container = paint.container;
|
|
7708
8163
|
const curves = paint.curves;
|
|
7709
8164
|
const styles = container.styles;
|
|
8165
|
+
// Use content box for text overflow calculation (excludes padding and border)
|
|
8166
|
+
// This matches browser behavior where text-overflow uses the content width
|
|
8167
|
+
const textBounds = contentBox(container);
|
|
7710
8168
|
for (const child of container.textNodes) {
|
|
7711
|
-
await this.renderTextNode(child, styles);
|
|
8169
|
+
await this.renderTextNode(child, styles, textBounds);
|
|
7712
8170
|
}
|
|
7713
8171
|
if (container instanceof ImageElementContainer) {
|
|
7714
8172
|
try {
|
|
@@ -8363,9 +8821,14 @@ class Context {
|
|
|
8363
8821
|
}
|
|
8364
8822
|
Context.instanceCount = 1;
|
|
8365
8823
|
|
|
8824
|
+
let cspNonce;
|
|
8825
|
+
const setCspNonce = (nonce) => {
|
|
8826
|
+
cspNonce = nonce;
|
|
8827
|
+
};
|
|
8366
8828
|
const html2canvas = (element, options = {}) => {
|
|
8367
8829
|
return renderElement(element, options);
|
|
8368
8830
|
};
|
|
8831
|
+
html2canvas.setCspNonce = setCspNonce;
|
|
8369
8832
|
if (typeof window !== 'undefined') {
|
|
8370
8833
|
CacheStorage.setContext(window);
|
|
8371
8834
|
}
|
|
@@ -8406,8 +8869,10 @@ const renderElement = async (element, opts) => {
|
|
|
8406
8869
|
allowTaint: opts.allowTaint ?? false,
|
|
8407
8870
|
onclone: opts.onclone,
|
|
8408
8871
|
ignoreElements: opts.ignoreElements,
|
|
8872
|
+
iframeContainer: opts.iframeContainer,
|
|
8409
8873
|
inlineImages: foreignObjectRendering,
|
|
8410
|
-
copyStyles: foreignObjectRendering
|
|
8874
|
+
copyStyles: foreignObjectRendering,
|
|
8875
|
+
cspNonce
|
|
8411
8876
|
};
|
|
8412
8877
|
context.logger.debug(`Starting document clone with size ${windowBounds.width}x${windowBounds.height} scrolled to ${-windowBounds.left},${-windowBounds.top}`);
|
|
8413
8878
|
const documentCloner = new DocumentCloner(context, element, cloneOptions);
|
|
@@ -8477,5 +8942,5 @@ const parseBackgroundColor = (context, element, backgroundColorOverride) => {
|
|
|
8477
8942
|
: defaultBackgroundColor;
|
|
8478
8943
|
};
|
|
8479
8944
|
|
|
8480
|
-
export { html2canvas as default, html2canvas };
|
|
8945
|
+
export { html2canvas as default, html2canvas, setCspNonce };
|
|
8481
8946
|
//# sourceMappingURL=html2canvas-pro.esm.js.map
|