html2canvas-pro 2.3.0 → 2.3.2
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 -81
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +54 -81
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +54 -81
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +2 -2
- package/dist/lib/render/canvas/canvas-renderer.js +1 -4
- package/dist/lib/render/canvas/content-renderer.js +5 -3
- package/dist/lib/render/canvas/font-utils.js +29 -0
- package/dist/lib/render/canvas/text-renderer.js +27 -20
- package/dist/types/render/canvas/canvas-renderer.d.ts +0 -1
- package/dist/types/render/canvas/content-renderer.d.ts +1 -2
- package/dist/types/render/canvas/font-utils.d.ts +19 -0
- package/dist/types/render/canvas/text-renderer.d.ts +0 -2
- package/package.json +1 -1
package/dist/html2canvas-pro.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.3.
|
|
2
|
+
* html2canvas-pro 2.3.2 <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
|
*/
|
|
@@ -8947,60 +8947,6 @@ const calculateBackgroundRepeatPath = (repeat, [x, y], [width, height], backgrou
|
|
|
8947
8947
|
}
|
|
8948
8948
|
};
|
|
8949
8949
|
//#endregion
|
|
8950
|
-
//#region src/core/util.ts
|
|
8951
|
-
const SMALL_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
8952
|
-
//#endregion
|
|
8953
|
-
//#region src/render/font-metrics.ts
|
|
8954
|
-
const SAMPLE_TEXT = "Hidden Text";
|
|
8955
|
-
var FontMetrics = class {
|
|
8956
|
-
constructor(document) {
|
|
8957
|
-
this._data = {};
|
|
8958
|
-
this._document = document;
|
|
8959
|
-
}
|
|
8960
|
-
parseMetrics(fontFamily, fontSize) {
|
|
8961
|
-
const container = this._document.createElement("div");
|
|
8962
|
-
const img = this._document.createElement("img");
|
|
8963
|
-
const span = this._document.createElement("span");
|
|
8964
|
-
const body = this._document.body;
|
|
8965
|
-
container.style.visibility = "hidden";
|
|
8966
|
-
container.style.fontFamily = fontFamily;
|
|
8967
|
-
container.style.fontSize = fontSize;
|
|
8968
|
-
container.style.margin = "0";
|
|
8969
|
-
container.style.padding = "0";
|
|
8970
|
-
container.style.whiteSpace = "nowrap";
|
|
8971
|
-
body.appendChild(container);
|
|
8972
|
-
img.src = SMALL_IMAGE;
|
|
8973
|
-
img.width = 1;
|
|
8974
|
-
img.height = 1;
|
|
8975
|
-
img.style.margin = "0";
|
|
8976
|
-
img.style.padding = "0";
|
|
8977
|
-
img.style.verticalAlign = "baseline";
|
|
8978
|
-
span.style.fontFamily = fontFamily;
|
|
8979
|
-
span.style.fontSize = fontSize;
|
|
8980
|
-
span.style.margin = "0";
|
|
8981
|
-
span.style.padding = "0";
|
|
8982
|
-
span.appendChild(this._document.createTextNode(SAMPLE_TEXT));
|
|
8983
|
-
container.appendChild(span);
|
|
8984
|
-
container.appendChild(img);
|
|
8985
|
-
const baseline = img.offsetTop - span.offsetTop + 2;
|
|
8986
|
-
container.removeChild(span);
|
|
8987
|
-
container.appendChild(this._document.createTextNode(SAMPLE_TEXT));
|
|
8988
|
-
container.style.lineHeight = "normal";
|
|
8989
|
-
img.style.verticalAlign = "super";
|
|
8990
|
-
const middle = img.offsetTop - container.offsetTop + 2;
|
|
8991
|
-
body.removeChild(container);
|
|
8992
|
-
return {
|
|
8993
|
-
baseline,
|
|
8994
|
-
middle
|
|
8995
|
-
};
|
|
8996
|
-
}
|
|
8997
|
-
getMetrics(fontFamily, fontSize) {
|
|
8998
|
-
const key = `${fontFamily} ${fontSize}`;
|
|
8999
|
-
if (typeof this._data[key] === "undefined") this._data[key] = this.parseMetrics(fontFamily, fontSize);
|
|
9000
|
-
return this._data[key];
|
|
9001
|
-
}
|
|
9002
|
-
};
|
|
9003
|
-
//#endregion
|
|
9004
8950
|
//#region src/render/canvas/text/text-decoration-renderer.ts
|
|
9005
8951
|
var TextDecorationRenderer = class {
|
|
9006
8952
|
constructor(ctx) {
|
|
@@ -9089,6 +9035,33 @@ var TextDecorationRenderer = class {
|
|
|
9089
9035
|
}
|
|
9090
9036
|
};
|
|
9091
9037
|
//#endregion
|
|
9038
|
+
//#region src/render/canvas/font-utils.ts
|
|
9039
|
+
/**
|
|
9040
|
+
* Font measurement utility
|
|
9041
|
+
*
|
|
9042
|
+
* Provides Canvas API-based baseline measurement that works correctly with
|
|
9043
|
+
* webfonts (which may not be loaded in the original document) and system fonts.
|
|
9044
|
+
*
|
|
9045
|
+
* Fallback chain:
|
|
9046
|
+
* 1. fontBoundingBoxAscent — font-level metric (Chrome 99+, FF 116+, Safari 17.4+)
|
|
9047
|
+
* 2. actualBoundingBoxAscent — glyph-level metric (widely supported)
|
|
9048
|
+
* 3. fallback value — coarse CSS fallback
|
|
9049
|
+
*/
|
|
9050
|
+
const SAMPLE_TEXT = "Mg";
|
|
9051
|
+
/**
|
|
9052
|
+
* Measure the baseline ascent for the currently set font.
|
|
9053
|
+
*
|
|
9054
|
+
* @param ctx - Canvas 2D rendering context with ctx.font already set
|
|
9055
|
+
* @param fallback - Fallback value when no metrics are available (e.g. fontSize.number)
|
|
9056
|
+
* @returns The distance from the text baseline to the top of the bounding box
|
|
9057
|
+
*/
|
|
9058
|
+
const measureBaseline = (ctx, fallback) => {
|
|
9059
|
+
const tm = ctx.measureText(SAMPLE_TEXT);
|
|
9060
|
+
const fmAscent = tm.fontBoundingBoxAscent;
|
|
9061
|
+
const ascent = fmAscent !== void 0 && !Number.isNaN(fmAscent) ? fmAscent : tm.actualBoundingBoxAscent;
|
|
9062
|
+
return ascent !== void 0 && !Number.isNaN(ascent) ? ascent : fallback;
|
|
9063
|
+
};
|
|
9064
|
+
//#endregion
|
|
9092
9065
|
//#region src/render/canvas/text-renderer.ts
|
|
9093
9066
|
const iOSBrokenFonts = ["-apple-system", "system-ui"];
|
|
9094
9067
|
/**
|
|
@@ -9255,19 +9228,19 @@ var TextRenderer = class {
|
|
|
9255
9228
|
this.ctx.strokeText(letter, x, y);
|
|
9256
9229
|
});
|
|
9257
9230
|
}
|
|
9258
|
-
renderTextStrokeWithStyle(text, styles) {
|
|
9231
|
+
renderTextStrokeWithStyle(text, styles, baseline) {
|
|
9259
9232
|
if (!styles.webkitTextStrokeWidth || !text.text.trim().length) return;
|
|
9260
9233
|
this.ctx.strokeStyle = asString(styles.webkitTextStrokeColor);
|
|
9261
9234
|
this.ctx.lineWidth = styles.webkitTextStrokeWidth;
|
|
9262
9235
|
this.ctx.lineJoin = getTextStrokeLineJoin();
|
|
9263
|
-
this.renderStrokeText(text, styles.letterSpacing,
|
|
9236
|
+
this.renderStrokeText(text, styles.letterSpacing, baseline, styles.writingMode);
|
|
9264
9237
|
this.ctx.strokeStyle = "";
|
|
9265
9238
|
this.ctx.lineWidth = 0;
|
|
9266
9239
|
this.ctx.lineJoin = "miter";
|
|
9267
9240
|
}
|
|
9268
|
-
renderTextFillWithShadows(text, styles) {
|
|
9241
|
+
renderTextFillWithShadows(text, styles, baseline) {
|
|
9269
9242
|
this.ctx.fillStyle = asString(styles.color);
|
|
9270
|
-
this.renderTextWithLetterSpacing(text, styles.letterSpacing,
|
|
9243
|
+
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline, styles.writingMode);
|
|
9271
9244
|
const textShadows = styles.textShadow;
|
|
9272
9245
|
if (textShadows.length && text.text.trim().length) {
|
|
9273
9246
|
textShadows.slice(0).reverse().forEach((textShadow) => {
|
|
@@ -9275,7 +9248,7 @@ var TextRenderer = class {
|
|
|
9275
9248
|
this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
|
|
9276
9249
|
this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
|
|
9277
9250
|
this.ctx.shadowBlur = textShadow.blur.number;
|
|
9278
|
-
this.renderTextWithLetterSpacing(text, styles.letterSpacing,
|
|
9251
|
+
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline, styles.writingMode);
|
|
9279
9252
|
});
|
|
9280
9253
|
this.ctx.shadowColor = "";
|
|
9281
9254
|
this.ctx.shadowOffsetX = 0;
|
|
@@ -9287,15 +9260,15 @@ var TextRenderer = class {
|
|
|
9287
9260
|
* Helper method to render text with paint order support
|
|
9288
9261
|
* Reduces code duplication in line-clamp and normal rendering
|
|
9289
9262
|
*/
|
|
9290
|
-
renderTextBoundWithPaintOrder(textBound, styles, paintOrderLayers) {
|
|
9263
|
+
renderTextBoundWithPaintOrder(textBound, styles, paintOrderLayers, baseline) {
|
|
9291
9264
|
paintOrderLayers.forEach((paintOrderLayer) => {
|
|
9292
9265
|
switch (paintOrderLayer) {
|
|
9293
9266
|
case 0:
|
|
9294
9267
|
this.ctx.fillStyle = asString(styles.color);
|
|
9295
|
-
this.renderTextWithLetterSpacing(textBound, styles.letterSpacing,
|
|
9268
|
+
this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, baseline, styles.writingMode);
|
|
9296
9269
|
break;
|
|
9297
9270
|
case 1:
|
|
9298
|
-
this.renderTextStrokeWithStyle(textBound, styles);
|
|
9271
|
+
this.renderTextStrokeWithStyle(textBound, styles, baseline);
|
|
9299
9272
|
break;
|
|
9300
9273
|
}
|
|
9301
9274
|
});
|
|
@@ -9355,7 +9328,7 @@ var TextRenderer = class {
|
|
|
9355
9328
|
* Groups text bounds by their Y position into visual lines, then renders
|
|
9356
9329
|
* only the first N-1 complete lines followed by an ellipsis on the Nth line.
|
|
9357
9330
|
*/
|
|
9358
|
-
renderLineClampedText(text, styles, paintOrder, containerBounds) {
|
|
9331
|
+
renderLineClampedText(text, styles, paintOrder, baseline, containerBounds) {
|
|
9359
9332
|
const lineHeight = styles.fontSize.number * 1.5;
|
|
9360
9333
|
const lines = [];
|
|
9361
9334
|
let currentLine = [];
|
|
@@ -9370,7 +9343,7 @@ var TextRenderer = class {
|
|
|
9370
9343
|
if (currentLine.length > 0) lines.push(currentLine);
|
|
9371
9344
|
const maxLines = styles.webkitLineClamp;
|
|
9372
9345
|
if (lines.length <= maxLines) return false;
|
|
9373
|
-
for (let i = 0; i < maxLines - 1; i++) lines[i].forEach((tb) => this.renderTextBoundWithPaintOrder(tb, styles, paintOrder));
|
|
9346
|
+
for (let i = 0; i < maxLines - 1; i++) lines[i].forEach((tb) => this.renderTextBoundWithPaintOrder(tb, styles, paintOrder, baseline));
|
|
9374
9347
|
const lastLine = lines[maxLines - 1];
|
|
9375
9348
|
if (lastLine?.length && containerBounds) {
|
|
9376
9349
|
const textStr = lastLine.map((tb) => tb.text).join("");
|
|
@@ -9379,8 +9352,8 @@ var TextRenderer = class {
|
|
|
9379
9352
|
const bounds = new TextBounds(this.truncateTextWithEllipsis(textStr, avail, styles.letterSpacing), first.bounds);
|
|
9380
9353
|
for (const layer of paintOrder) if (layer === 0) {
|
|
9381
9354
|
this.ctx.fillStyle = asString(styles.color);
|
|
9382
|
-
this.renderTextWithLetterSpacing(bounds, styles.letterSpacing,
|
|
9383
|
-
} else if (layer === 1) this.renderTextStrokeWithStyle(bounds, styles);
|
|
9355
|
+
this.renderTextWithLetterSpacing(bounds, styles.letterSpacing, baseline, styles.writingMode);
|
|
9356
|
+
} else if (layer === 1) this.renderTextStrokeWithStyle(bounds, styles, baseline);
|
|
9384
9357
|
}
|
|
9385
9358
|
return true;
|
|
9386
9359
|
}
|
|
@@ -9388,34 +9361,36 @@ var TextRenderer = class {
|
|
|
9388
9361
|
* Render single-line text with text-overflow: ellipsis.
|
|
9389
9362
|
* Returns true if ellipsis was applied (caller should skip normal rendering).
|
|
9390
9363
|
*/
|
|
9391
|
-
renderEllipsisText(text, styles, paintOrder, containerBounds) {
|
|
9364
|
+
renderEllipsisText(text, styles, paintOrder, baseline, containerBounds) {
|
|
9392
9365
|
const lineHeight = styles.fontSize.number * 1.5;
|
|
9393
9366
|
const firstTop = text.textBounds[0].bounds.top;
|
|
9394
9367
|
if (!text.textBounds.every((tb) => Math.abs(tb.bounds.top - firstTop) < lineHeight * .5)) return false;
|
|
9395
9368
|
let fullText = text.textBounds.map((tb) => tb.text).join("").replace(/\s+/g, " ").trim();
|
|
9396
9369
|
if (this.ctx.measureText(fullText).width <= containerBounds.width) return false;
|
|
9397
9370
|
const bounds = new TextBounds(this.truncateTextWithEllipsis(fullText, containerBounds.width, styles.letterSpacing), text.textBounds[0].bounds);
|
|
9398
|
-
for (const layer of paintOrder) if (layer === 0) this.renderTextFillWithShadows(bounds, styles);
|
|
9399
|
-
else if (layer === 1) this.renderTextStrokeWithStyle(bounds, styles);
|
|
9371
|
+
for (const layer of paintOrder) if (layer === 0) this.renderTextFillWithShadows(bounds, styles, baseline);
|
|
9372
|
+
else if (layer === 1) this.renderTextStrokeWithStyle(bounds, styles, baseline);
|
|
9400
9373
|
return true;
|
|
9401
9374
|
}
|
|
9402
9375
|
async renderTextNode(text, styles, containerBounds) {
|
|
9403
9376
|
this.glyphWidthCache = null;
|
|
9404
|
-
|
|
9377
|
+
const [fontString] = this.createFontStyle(styles);
|
|
9378
|
+
this.ctx.font = fontString;
|
|
9405
9379
|
this.ctx.direction = styles.direction === 1 ? "rtl" : "ltr";
|
|
9406
9380
|
this.ctx.textAlign = "left";
|
|
9407
9381
|
this.ctx.textBaseline = "alphabetic";
|
|
9408
9382
|
const paintOrder = styles.paintOrder;
|
|
9383
|
+
const baseline = measureBaseline(this.ctx, styles.fontSize.number);
|
|
9409
9384
|
if (styles.webkitLineClamp > 0 && (styles.display & 2) !== 0 && styles.overflowY === 1 && text.textBounds.length > 0) {
|
|
9410
|
-
if (this.renderLineClampedText(text, styles, paintOrder, containerBounds)) return;
|
|
9385
|
+
if (this.renderLineClampedText(text, styles, paintOrder, baseline, containerBounds)) return;
|
|
9411
9386
|
}
|
|
9412
|
-
if (styles.textOverflow === 1 && containerBounds && styles.overflowX === 1 && text.textBounds.length > 0 && this.renderEllipsisText(text, styles, paintOrder, containerBounds)) return;
|
|
9387
|
+
if (styles.textOverflow === 1 && containerBounds && styles.overflowX === 1 && text.textBounds.length > 0 && this.renderEllipsisText(text, styles, paintOrder, baseline, containerBounds)) return;
|
|
9413
9388
|
text.textBounds.forEach((tb) => {
|
|
9414
9389
|
paintOrder.forEach((layer) => {
|
|
9415
9390
|
if (layer === 0) {
|
|
9416
|
-
this.renderTextFillWithShadows(tb, styles);
|
|
9391
|
+
this.renderTextFillWithShadows(tb, styles, baseline);
|
|
9417
9392
|
if (styles.textDecorationLine.length) this.renderTextDecoration(tb.bounds, styles);
|
|
9418
|
-
} else if (layer === 1) this.renderTextStrokeWithStyle(tb, styles);
|
|
9393
|
+
} else if (layer === 1) this.renderTextStrokeWithStyle(tb, styles, baseline);
|
|
9419
9394
|
});
|
|
9420
9395
|
});
|
|
9421
9396
|
}
|
|
@@ -10301,7 +10276,7 @@ async function renderReplacedElements(ctx, context, options, iframeRendererFacto
|
|
|
10301
10276
|
/**
|
|
10302
10277
|
* Render form element content: checkbox, radio, text input.
|
|
10303
10278
|
*/
|
|
10304
|
-
function renderFormElements(ctx,
|
|
10279
|
+
function renderFormElements(ctx, textRenderer, pathFn, container, styles) {
|
|
10305
10280
|
if (container instanceof InputElementContainer) {
|
|
10306
10281
|
const size = Math.min(container.bounds.width, container.bounds.height);
|
|
10307
10282
|
if (container.type === "checkbox" && container.checked) {
|
|
@@ -10328,9 +10303,9 @@ function renderFormElements(ctx, fontMetrics, textRenderer, pathFn, container, s
|
|
|
10328
10303
|
}
|
|
10329
10304
|
}
|
|
10330
10305
|
if (isTextInputElement(container) && container.value.length) {
|
|
10331
|
-
const [font
|
|
10332
|
-
const { baseline } = fontMetrics.getMetrics(fontFamily, fontSize);
|
|
10306
|
+
const [font] = textRenderer.createFontStyle(styles);
|
|
10333
10307
|
ctx.font = font;
|
|
10308
|
+
const baseline = measureBaseline(ctx, getAbsoluteValue(styles.fontSize, 0));
|
|
10334
10309
|
ctx.fillStyle = container instanceof InputElementContainer && container.isPlaceholder ? asString(PLACEHOLDER_COLOR) : asString(styles.color);
|
|
10335
10310
|
ctx.textBaseline = "alphabetic";
|
|
10336
10311
|
ctx.textAlign = canvasTextAlign(container.styles.textAlign);
|
|
@@ -10428,7 +10403,6 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10428
10403
|
this.canvas.style.width = `${options.width}px`;
|
|
10429
10404
|
this.canvas.style.height = `${options.height}px`;
|
|
10430
10405
|
}
|
|
10431
|
-
this.fontMetrics = new FontMetrics(document);
|
|
10432
10406
|
this.ctx.scale(this.options.scale, this.options.scale);
|
|
10433
10407
|
this.ctx.translate(-options.x, -options.y);
|
|
10434
10408
|
this.ctx.textBaseline = "bottom";
|
|
@@ -10452,7 +10426,6 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10452
10426
|
this.effectsRenderer = new EffectsRenderer({ ctx: this.ctx }, { path: (paths) => this.path(paths) });
|
|
10453
10427
|
this.textRenderer = new TextRenderer({
|
|
10454
10428
|
ctx: this.ctx,
|
|
10455
|
-
context: this.context,
|
|
10456
10429
|
options: { scale: options.scale }
|
|
10457
10430
|
});
|
|
10458
10431
|
this.context.logger.debug(`Canvas renderer initialized (${options.width}x${options.height}) with scale ${options.scale}`);
|
|
@@ -10500,7 +10473,7 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10500
10473
|
width: this.options.width,
|
|
10501
10474
|
height: this.options.height
|
|
10502
10475
|
}, (ctx, opts) => new CanvasRenderer(ctx, opts), container, curves, styles, (c, cv, img) => this.renderReplacedElement(c, cv, img));
|
|
10503
|
-
renderFormElements(this.ctx, this.
|
|
10476
|
+
renderFormElements(this.ctx, this.textRenderer, this.path.bind(this), container, styles);
|
|
10504
10477
|
await renderListMarker(this.ctx, this.context, this.textRenderer, paint, container, styles);
|
|
10505
10478
|
}
|
|
10506
10479
|
async renderStackContent(stack) {
|