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