html2canvas-pro 2.3.9 → 2.4.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 +73 -26
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +73 -26
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +73 -26
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +4 -4
- package/dist/lib/dom/document-cloner.js +10 -8
- package/dist/lib/dom/replaced-elements/svg-element-container.js +40 -2
- package/dist/lib/render/canvas/content-renderer.js +22 -9
- package/dist/lib/render/canvas/font-utils.js +24 -7
- package/dist/types/dom/replaced-elements/svg-element-container.d.ts +1 -0
- package/dist/types/render/canvas/content-renderer.d.ts +9 -0
- package/dist/types/render/canvas/font-utils.d.ts +10 -2
- package/package.json +1 -1
package/dist/html2canvas-pro.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.
|
|
2
|
+
* html2canvas-pro 2.4.1 <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
|
*/
|
|
@@ -6990,19 +6990,18 @@
|
|
|
6990
6990
|
const documentClone = cloneWindow.document;
|
|
6991
6991
|
const iframeLoad = iframeLoader(iframe).then(async () => {
|
|
6992
6992
|
this.scrolledElements.forEach(restoreNodeScroll);
|
|
6993
|
-
if (cloneWindow)
|
|
6994
|
-
cloneWindow.scrollTo(windowSize.left, windowSize.top);
|
|
6995
|
-
if (/AppleWebKit/g.test(navigator.userAgent) && (cloneWindow.scrollY !== windowSize.top || cloneWindow.scrollX !== windowSize.left)) {
|
|
6996
|
-
this.context.logger.warn("Unable to restore scroll position for cloned document");
|
|
6997
|
-
this.context.windowBounds = this.context.windowBounds.add(cloneWindow.scrollX - windowSize.left, cloneWindow.scrollY - windowSize.top, 0, 0);
|
|
6998
|
-
}
|
|
6999
|
-
}
|
|
6993
|
+
if (cloneWindow) cloneWindow.scrollTo(windowSize.left, windowSize.top);
|
|
7000
6994
|
const onclone = this.options.onclone;
|
|
7001
6995
|
const referenceElement = this.clonedReferenceElement;
|
|
7002
6996
|
if (typeof referenceElement === "undefined") throw new Error(`Error finding the ${this.referenceElement.nodeName} in the cloned document`);
|
|
7003
6997
|
if (documentClone.fonts && documentClone.fonts.ready) await documentClone.fonts.ready;
|
|
7004
6998
|
if (/(AppleWebKit)/g.test(navigator.userAgent)) await imagesReady(documentClone);
|
|
7005
|
-
if (typeof onclone === "function")
|
|
6999
|
+
if (typeof onclone === "function") await Promise.resolve().then(() => onclone(documentClone, referenceElement));
|
|
7000
|
+
cloneWindow.scrollTo(windowSize.left, windowSize.top);
|
|
7001
|
+
if (/AppleWebKit/g.test(navigator.userAgent) && (cloneWindow.scrollY !== windowSize.top || cloneWindow.scrollX !== windowSize.left)) {
|
|
7002
|
+
this.context.logger.warn("Unable to restore scroll position for cloned document");
|
|
7003
|
+
this.context.windowBounds = this.context.windowBounds.add(cloneWindow.scrollX - windowSize.left, cloneWindow.scrollY - windowSize.top, 0, 0);
|
|
7004
|
+
}
|
|
7006
7005
|
return iframe;
|
|
7007
7006
|
});
|
|
7008
7007
|
/**
|
|
@@ -8090,6 +8089,39 @@
|
|
|
8090
8089
|
};
|
|
8091
8090
|
//#endregion
|
|
8092
8091
|
//#region src/dom/replaced-elements/svg-element-container.ts
|
|
8092
|
+
const BOX_OFFSET_PROPERTIES = [
|
|
8093
|
+
"position",
|
|
8094
|
+
"inset",
|
|
8095
|
+
"top",
|
|
8096
|
+
"right",
|
|
8097
|
+
"bottom",
|
|
8098
|
+
"left",
|
|
8099
|
+
"inset-inline",
|
|
8100
|
+
"inset-inline-start",
|
|
8101
|
+
"inset-inline-end",
|
|
8102
|
+
"inset-block",
|
|
8103
|
+
"inset-block-start",
|
|
8104
|
+
"inset-block-end",
|
|
8105
|
+
"margin",
|
|
8106
|
+
"margin-top",
|
|
8107
|
+
"margin-right",
|
|
8108
|
+
"margin-bottom",
|
|
8109
|
+
"margin-left",
|
|
8110
|
+
"margin-inline",
|
|
8111
|
+
"margin-inline-start",
|
|
8112
|
+
"margin-inline-end",
|
|
8113
|
+
"margin-block",
|
|
8114
|
+
"margin-block-start",
|
|
8115
|
+
"margin-block-end"
|
|
8116
|
+
];
|
|
8117
|
+
const serializeWithoutBoxOffsets = (serializer, img) => {
|
|
8118
|
+
const style = img.getAttribute("style");
|
|
8119
|
+
BOX_OFFSET_PROPERTIES.forEach((property) => img.style.removeProperty(property));
|
|
8120
|
+
const serialized = serializer.serializeToString(img);
|
|
8121
|
+
if (style === null) img.removeAttribute("style");
|
|
8122
|
+
else img.setAttribute("style", style);
|
|
8123
|
+
return serialized;
|
|
8124
|
+
};
|
|
8093
8125
|
var SVGElementContainer = class extends ElementContainer {
|
|
8094
8126
|
constructor(context, img) {
|
|
8095
8127
|
super(context, img);
|
|
@@ -8097,7 +8129,7 @@
|
|
|
8097
8129
|
const bounds = parseBounds(context, img);
|
|
8098
8130
|
img.setAttribute("width", `${bounds.width}px`);
|
|
8099
8131
|
img.setAttribute("height", `${bounds.height}px`);
|
|
8100
|
-
this.svg = `data:image/svg+xml,${encodeURIComponent(s
|
|
8132
|
+
this.svg = `data:image/svg+xml,${encodeURIComponent(serializeWithoutBoxOffsets(s, img))}`;
|
|
8101
8133
|
this.intrinsicWidth = img.width.baseVal.value;
|
|
8102
8134
|
this.intrinsicHeight = img.height.baseVal.value;
|
|
8103
8135
|
this.context.cache.addImage(this.svg);
|
|
@@ -9092,8 +9124,8 @@
|
|
|
9092
9124
|
/**
|
|
9093
9125
|
* Font measurement utility
|
|
9094
9126
|
*
|
|
9095
|
-
* Provides Canvas API-based
|
|
9096
|
-
*
|
|
9127
|
+
* Provides Canvas API-based font measurement that works correctly with webfonts
|
|
9128
|
+
* (which may not be loaded in the original document) and system fonts.
|
|
9097
9129
|
*
|
|
9098
9130
|
* Fallback chain:
|
|
9099
9131
|
* 1. fontBoundingBoxAscent — font-level metric (Chrome 99+, FF 116+, Safari 17.4+)
|
|
@@ -9101,6 +9133,18 @@
|
|
|
9101
9133
|
* 3. fallback value — coarse CSS fallback
|
|
9102
9134
|
*/
|
|
9103
9135
|
const SAMPLE_TEXT = "Mg";
|
|
9136
|
+
const isValidMetric = (value) => value !== void 0 && !Number.isNaN(value);
|
|
9137
|
+
/**
|
|
9138
|
+
* Measure the baseline ascent and full font box for the currently set font.
|
|
9139
|
+
*/
|
|
9140
|
+
const measureFontMetrics = (ctx, fallback) => {
|
|
9141
|
+
const tm = ctx.measureText(SAMPLE_TEXT);
|
|
9142
|
+
const fontMetrics = tm;
|
|
9143
|
+
return {
|
|
9144
|
+
baseline: isValidMetric(fontMetrics.fontBoundingBoxAscent) ? fontMetrics.fontBoundingBoxAscent : isValidMetric(tm.actualBoundingBoxAscent) ? tm.actualBoundingBoxAscent : fallback,
|
|
9145
|
+
height: isValidMetric(fontMetrics.fontBoundingBoxAscent) && isValidMetric(fontMetrics.fontBoundingBoxDescent) ? fontMetrics.fontBoundingBoxAscent + fontMetrics.fontBoundingBoxDescent : isValidMetric(tm.actualBoundingBoxAscent) && isValidMetric(tm.actualBoundingBoxDescent) ? tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent : fallback
|
|
9146
|
+
};
|
|
9147
|
+
};
|
|
9104
9148
|
/**
|
|
9105
9149
|
* Measure the baseline ascent for the currently set font.
|
|
9106
9150
|
*
|
|
@@ -9109,10 +9153,7 @@
|
|
|
9109
9153
|
* @returns The distance from the text baseline to the top of the bounding box
|
|
9110
9154
|
*/
|
|
9111
9155
|
const measureBaseline = (ctx, fallback) => {
|
|
9112
|
-
|
|
9113
|
-
const fmAscent = tm.fontBoundingBoxAscent;
|
|
9114
|
-
const ascent = fmAscent !== void 0 && !Number.isNaN(fmAscent) ? fmAscent : tm.actualBoundingBoxAscent;
|
|
9115
|
-
return ascent !== void 0 && !Number.isNaN(ascent) ? ascent : fallback;
|
|
9156
|
+
return measureFontMetrics(ctx, fallback).baseline;
|
|
9116
9157
|
};
|
|
9117
9158
|
//#endregion
|
|
9118
9159
|
//#region src/render/canvas/text-renderer.ts
|
|
@@ -10359,11 +10400,13 @@
|
|
|
10359
10400
|
if (isTextInputElement(container) && container.value.length) {
|
|
10360
10401
|
const [font] = textRenderer.createFontStyle(styles);
|
|
10361
10402
|
ctx.font = font;
|
|
10362
|
-
const
|
|
10403
|
+
const fontSizeValue = getAbsoluteValue(styles.fontSize, 0);
|
|
10404
|
+
const { baseline, height: fontHeight } = measureFontMetrics(ctx, fontSizeValue);
|
|
10363
10405
|
ctx.fillStyle = container instanceof InputElementContainer && container.isPlaceholder ? asString(PLACEHOLDER_COLOR) : asString(styles.color);
|
|
10364
10406
|
ctx.textBaseline = "alphabetic";
|
|
10365
10407
|
ctx.textAlign = canvasTextAlign(container.styles.textAlign);
|
|
10366
10408
|
const bounds = contentBox(container);
|
|
10409
|
+
const clipBounds = container instanceof InputElementContainer ? inputTextClipBounds(bounds, paddingBox(container), fontHeight) : bounds;
|
|
10367
10410
|
let x = 0;
|
|
10368
10411
|
switch (container.styles.textAlign) {
|
|
10369
10412
|
case 1:
|
|
@@ -10374,21 +10417,17 @@
|
|
|
10374
10417
|
break;
|
|
10375
10418
|
}
|
|
10376
10419
|
let verticalOffset = 0;
|
|
10377
|
-
if (container instanceof InputElementContainer)
|
|
10378
|
-
const fontSizeValue = getAbsoluteValue(styles.fontSize, 0);
|
|
10379
|
-
verticalOffset = (bounds.height - fontSizeValue) / 2;
|
|
10380
|
-
}
|
|
10420
|
+
if (container instanceof InputElementContainer) verticalOffset = (bounds.height - fontHeight) / 2;
|
|
10381
10421
|
const textBounds = bounds.add(x, verticalOffset, 0, 0);
|
|
10382
10422
|
ctx.save();
|
|
10383
10423
|
pathFn([
|
|
10384
|
-
new Vector(
|
|
10385
|
-
new Vector(
|
|
10386
|
-
new Vector(
|
|
10387
|
-
new Vector(
|
|
10424
|
+
new Vector(clipBounds.left, clipBounds.top),
|
|
10425
|
+
new Vector(clipBounds.left + clipBounds.width, clipBounds.top),
|
|
10426
|
+
new Vector(clipBounds.left + clipBounds.width, clipBounds.top + clipBounds.height),
|
|
10427
|
+
new Vector(clipBounds.left, clipBounds.top + clipBounds.height)
|
|
10388
10428
|
]);
|
|
10389
10429
|
ctx.clip();
|
|
10390
10430
|
const lines = wrapTextToWidth(ctx, container.value, bounds.width, styles.whiteSpace === "pre" || styles.whiteSpace === "nowrap");
|
|
10391
|
-
const fontSizeValue = getAbsoluteValue(styles.fontSize, 0);
|
|
10392
10431
|
const lineHeight = computeLineHeight(styles.lineHeight, fontSizeValue);
|
|
10393
10432
|
lines.forEach((line, index) => {
|
|
10394
10433
|
let lineX = textBounds.left;
|
|
@@ -10410,6 +10449,14 @@
|
|
|
10410
10449
|
}
|
|
10411
10450
|
}
|
|
10412
10451
|
/**
|
|
10452
|
+
* Native single-line inputs may paint value text into their padding area when
|
|
10453
|
+
* fixed height and vertical padding leave a content box shorter than the font.
|
|
10454
|
+
* Preserve the content box horizontally, but avoid clipping those glyphs to the
|
|
10455
|
+
* collapsed vertical content box.
|
|
10456
|
+
* @internal exported for testing
|
|
10457
|
+
*/
|
|
10458
|
+
const inputTextClipBounds = (contentBounds, inputPaddingBox, fontHeight) => contentBounds.height < fontHeight ? new Bounds(contentBounds.left, inputPaddingBox.top, contentBounds.width, inputPaddingBox.height) : contentBounds;
|
|
10459
|
+
/**
|
|
10413
10460
|
* Split text into lines that fit the given pixel width, preserving hard line
|
|
10414
10461
|
* breaks and (when wrapping is enabled) breaking at word boundaries — mirroring
|
|
10415
10462
|
* how a <textarea> lays out its value.
|