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