html2canvas-pro 2.3.5 → 2.3.7
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 +54 -11
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +54 -11
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +54 -11
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +3 -3
- package/dist/lib/dom/document-cloner.js +25 -6
- package/dist/lib/dom/element-container.js +15 -0
- package/dist/lib/render/canvas/canvas-renderer.js +42 -9
- package/dist/lib/render/canvas/text-renderer.js +8 -1
- package/dist/types/dom/document-cloner.d.ts +1 -1
- package/dist/types/dom/element-container.d.ts +6 -0
- package/package.json +1 -1
|
@@ -168,17 +168,36 @@ class DocumentCloner {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
if ((0, node_type_guards_1.isCustomElement)(clone) && !(0, node_type_guards_1.isSVGElementNode)(clone)) {
|
|
171
|
-
return this.createCustomElementClone(clone);
|
|
171
|
+
return this.createCustomElementClone(node, clone);
|
|
172
172
|
}
|
|
173
173
|
return clone;
|
|
174
174
|
}
|
|
175
|
-
createCustomElementClone(
|
|
175
|
+
createCustomElementClone(originalNode, clonedNode) {
|
|
176
176
|
const clone = document.createElement('div');
|
|
177
|
-
clone.className =
|
|
178
|
-
(0, exports.copyCSSStyles)(
|
|
179
|
-
//
|
|
177
|
+
clone.className = clonedNode.className;
|
|
178
|
+
(0, exports.copyCSSStyles)(clonedNode.style, clone);
|
|
179
|
+
// Copy all attributes onto the fresh <div> (except class/style which are
|
|
180
|
+
// handled above). Without this, attributes such as `slot="suffix"` are
|
|
181
|
+
// dropped and slotted custom elements lose their slot assignment in the
|
|
182
|
+
// cloned shadow DOM, so their content is never rendered (issue #226).
|
|
183
|
+
// Iterate by index: `NamedNodeMap` is not guaranteed to be iterable
|
|
184
|
+
// across TypeScript DOM lib versions (TS2488 on older libs).
|
|
185
|
+
const attributes = clonedNode.attributes;
|
|
186
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
187
|
+
const attr = attributes[i];
|
|
188
|
+
if (attr.name !== 'class' && attr.name !== 'style') {
|
|
189
|
+
clone.setAttribute(attr.name, attr.value);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Clone shadow DOM if it exists. The shadow root must be detected on the
|
|
193
|
+
// ORIGINAL element: `clonedNode` comes from `cloneNode(false)`, which only
|
|
194
|
+
// preserves the shadow root when it was created with `clonable: true` (or
|
|
195
|
+
// by frameworks that set it). For other components (e.g. Shoelace / Web
|
|
196
|
+
// Awesome) the clone has no shadow root, so without this check the clone
|
|
197
|
+
// falls back to flattening the shadow content and the slotted light-DOM
|
|
198
|
+
// label content is lost.
|
|
180
199
|
// Fix for Issue #108: This is critical for Web Components with slots to work correctly
|
|
181
|
-
if (
|
|
200
|
+
if (originalNode.shadowRoot) {
|
|
182
201
|
try {
|
|
183
202
|
clone.attachShadow({ mode: 'open' });
|
|
184
203
|
// The actual shadow DOM content will be cloned in cloneChildNodes
|
|
@@ -25,6 +25,21 @@ class ElementContainer {
|
|
|
25
25
|
this.originalElement = element;
|
|
26
26
|
}
|
|
27
27
|
this.bounds = (0, bounds_1.parseBounds)(this.context, element);
|
|
28
|
+
if (element.tagName === 'FIELDSET') {
|
|
29
|
+
const legend = element.querySelector(':scope > legend');
|
|
30
|
+
if (legend) {
|
|
31
|
+
const legendBounds = (0, bounds_1.parseBounds)(this.context, legend);
|
|
32
|
+
this.legendBounds = legendBounds;
|
|
33
|
+
// A <fieldset>'s legend is centered on its top border line and
|
|
34
|
+
// overflows ABOVE it. Chrome's getBoundingClientRect() for the
|
|
35
|
+
// fieldset includes that overflow, so bounds.top is too high and
|
|
36
|
+
// the border would be drawn above the legend. Shift the bounds so
|
|
37
|
+
// the top border sits at the legend's vertical center, matching
|
|
38
|
+
// how the browser renders fieldset + legend (issue #227).
|
|
39
|
+
const borderBoxTop = legendBounds.top + legendBounds.height / 2 - this.styles.borderTopWidth / 2;
|
|
40
|
+
this.bounds = new bounds_1.Bounds(this.bounds.left, borderBoxTop, this.bounds.width, this.bounds.height - (borderBoxTop - this.bounds.top));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
28
43
|
if ((0, debugger_1.isDebugging)(element, 4 /* DebuggerType.RENDER */)) {
|
|
29
44
|
this.debugRender = true;
|
|
30
45
|
}
|
|
@@ -282,17 +282,50 @@ class CanvasRenderer {
|
|
|
282
282
|
let side = 0;
|
|
283
283
|
for (const border of borders) {
|
|
284
284
|
if (border.style !== 0 /* BORDER_STYLE.NONE */ && !(0, color_utilities_1.isTransparent)(border.color) && border.width > 0) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
285
|
+
const renderBorderSide = async () => {
|
|
286
|
+
if (border.style === 2 /* BORDER_STYLE.DASHED */) {
|
|
287
|
+
await this.borderRenderer.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 2 /* BORDER_STYLE.DASHED */);
|
|
288
|
+
}
|
|
289
|
+
else if (border.style === 3 /* BORDER_STYLE.DOTTED */) {
|
|
290
|
+
await this.borderRenderer.renderDashedDottedBorder(border.color, border.width, side, paint.curves, 3 /* BORDER_STYLE.DOTTED */);
|
|
291
|
+
}
|
|
292
|
+
else if (border.style === 4 /* BORDER_STYLE.DOUBLE */) {
|
|
293
|
+
await this.borderRenderer.renderDoubleBorder(border.color, border.width, side, paint.curves);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
await this.borderRenderer.renderSolidBorder(border.color, side, paint.curves);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
// Browsers break a <fieldset>'s top border where its <legend>
|
|
300
|
+
// sits, instead of drawing the border through the legend (issue
|
|
301
|
+
// #227). Render the top border in two clipped segments around
|
|
302
|
+
// the legend's horizontal span.
|
|
303
|
+
const legend = paint.container.legendBounds;
|
|
304
|
+
if (side === 0 && legend) {
|
|
305
|
+
const bounds = paint.container.bounds;
|
|
306
|
+
const legendLeft = legend.left - bounds.left;
|
|
307
|
+
const legendRight = legend.left + legend.width - bounds.left;
|
|
308
|
+
if (legendLeft > 0) {
|
|
309
|
+
this.ctx.save();
|
|
310
|
+
this.ctx.beginPath();
|
|
311
|
+
this.ctx.rect(bounds.left, bounds.top, legendLeft, bounds.height);
|
|
312
|
+
this.ctx.clip();
|
|
313
|
+
await renderBorderSide();
|
|
314
|
+
this.ctx.restore();
|
|
315
|
+
}
|
|
316
|
+
const rightStart = bounds.left + legendRight;
|
|
317
|
+
const rightWidth = bounds.width - legendRight;
|
|
318
|
+
if (rightWidth > 0) {
|
|
319
|
+
this.ctx.save();
|
|
320
|
+
this.ctx.beginPath();
|
|
321
|
+
this.ctx.rect(rightStart, bounds.top, rightWidth, bounds.height);
|
|
322
|
+
this.ctx.clip();
|
|
323
|
+
await renderBorderSide();
|
|
324
|
+
this.ctx.restore();
|
|
325
|
+
}
|
|
293
326
|
}
|
|
294
327
|
else {
|
|
295
|
-
await
|
|
328
|
+
await renderBorderSide();
|
|
296
329
|
}
|
|
297
330
|
}
|
|
298
331
|
side++;
|
|
@@ -21,6 +21,10 @@ const text_decoration_renderer_1 = require("./text/text-decoration-renderer");
|
|
|
21
21
|
const font_utils_1 = require("./font-utils");
|
|
22
22
|
// iOS font fix - see https://github.com/niklasvh/html2canvas/pull/2645
|
|
23
23
|
const iOSBrokenFonts = ['-apple-system', 'system-ui'];
|
|
24
|
+
// Tolerance (px) for text-overflow: ellipsis detection. Canvas measureText can
|
|
25
|
+
// report a width a fraction of a pixel wider than the browser's layout box, so
|
|
26
|
+
// text that fits exactly must not be truncated (issue #226).
|
|
27
|
+
const TEXT_OVERFLOW_EPSILON = 1;
|
|
24
28
|
/**
|
|
25
29
|
* Detect CJK (Chinese, Japanese, Korean) characters in a string.
|
|
26
30
|
* CJK characters use the ideographic baseline in browsers, which differs
|
|
@@ -424,7 +428,10 @@ class TextRenderer {
|
|
|
424
428
|
.replace(/\s+/g, ' ')
|
|
425
429
|
.trim();
|
|
426
430
|
const fullWidth = this.ctx.measureText(fullText).width;
|
|
427
|
-
|
|
431
|
+
// Small tolerance: canvas measurement can differ from the browser's text
|
|
432
|
+
// layout by a fraction of a pixel (kerning/font rounding), so text that
|
|
433
|
+
// exactly fits the container must not be falsely truncated (issue #226).
|
|
434
|
+
if (fullWidth <= containerBounds.width + TEXT_OVERFLOW_EPSILON)
|
|
428
435
|
return false;
|
|
429
436
|
const truncated = this.truncateTextWithEllipsis(fullText, containerBounds.width, styles.letterSpacing);
|
|
430
437
|
const bounds = new text_1.TextBounds(truncated, text.textBounds[0].bounds);
|
|
@@ -30,7 +30,7 @@ export declare class DocumentCloner {
|
|
|
30
30
|
constructor(context: Context, element: HTMLElement, options: CloneConfigurations);
|
|
31
31
|
toIFrame(ownerDocument: Document, windowSize: Bounds): Promise<HTMLIFrameElement>;
|
|
32
32
|
createElementClone<T extends HTMLElement | SVGElement>(node: T): HTMLElement | SVGElement;
|
|
33
|
-
createCustomElementClone(
|
|
33
|
+
createCustomElementClone(originalNode: HTMLElement, clonedNode: HTMLElement): HTMLElement;
|
|
34
34
|
createStyleClone(node: HTMLStyleElement): HTMLStyleElement;
|
|
35
35
|
createCanvasClone(canvas: HTMLCanvasElement): HTMLImageElement | HTMLCanvasElement;
|
|
36
36
|
createVideoClone(video: HTMLVideoElement): HTMLCanvasElement;
|
|
@@ -11,6 +11,12 @@ export declare class ElementContainer {
|
|
|
11
11
|
readonly textNodes: TextContainer[];
|
|
12
12
|
readonly elements: ElementContainer[];
|
|
13
13
|
bounds: Bounds;
|
|
14
|
+
/**
|
|
15
|
+
* Bounds of the <legend> child of a <fieldset>, when present. Browsers break
|
|
16
|
+
* the fieldset's top border where the legend sits; the renderer uses these
|
|
17
|
+
* bounds to leave a matching gap (issue #227).
|
|
18
|
+
*/
|
|
19
|
+
readonly legendBounds?: Bounds;
|
|
14
20
|
createsStackingContext: boolean;
|
|
15
21
|
createsRealStackingContext: boolean;
|
|
16
22
|
isListOwner: boolean;
|