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