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