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
|
@@ -18,6 +18,7 @@ const color_utilities_1 = require("../../css/types/color-utilities");
|
|
|
18
18
|
const parser_1 = require("../../css/syntax/parser");
|
|
19
19
|
const writing_mode_1 = require("../../css/property-descriptors/writing-mode");
|
|
20
20
|
const text_decoration_renderer_1 = require("./text/text-decoration-renderer");
|
|
21
|
+
const font_utils_1 = require("./font-utils");
|
|
21
22
|
// iOS font fix - see https://github.com/niklasvh/html2canvas/pull/2645
|
|
22
23
|
const iOSBrokenFonts = ['-apple-system', 'system-ui'];
|
|
23
24
|
/**
|
|
@@ -236,21 +237,21 @@ class TextRenderer {
|
|
|
236
237
|
});
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
|
-
renderTextStrokeWithStyle(text, styles) {
|
|
240
|
+
renderTextStrokeWithStyle(text, styles, baseline) {
|
|
240
241
|
if (!styles.webkitTextStrokeWidth || !text.text.trim().length) {
|
|
241
242
|
return;
|
|
242
243
|
}
|
|
243
244
|
this.ctx.strokeStyle = (0, color_utilities_1.asString)(styles.webkitTextStrokeColor);
|
|
244
245
|
this.ctx.lineWidth = styles.webkitTextStrokeWidth;
|
|
245
246
|
this.ctx.lineJoin = getTextStrokeLineJoin();
|
|
246
|
-
this.renderStrokeText(text, styles.letterSpacing,
|
|
247
|
+
this.renderStrokeText(text, styles.letterSpacing, baseline, styles.writingMode);
|
|
247
248
|
this.ctx.strokeStyle = '';
|
|
248
249
|
this.ctx.lineWidth = 0;
|
|
249
250
|
this.ctx.lineJoin = 'miter';
|
|
250
251
|
}
|
|
251
|
-
renderTextFillWithShadows(text, styles) {
|
|
252
|
+
renderTextFillWithShadows(text, styles, baseline) {
|
|
252
253
|
this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
|
|
253
|
-
this.renderTextWithLetterSpacing(text, styles.letterSpacing,
|
|
254
|
+
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline, styles.writingMode);
|
|
254
255
|
const textShadows = styles.textShadow;
|
|
255
256
|
if (textShadows.length && text.text.trim().length) {
|
|
256
257
|
textShadows
|
|
@@ -261,7 +262,7 @@ class TextRenderer {
|
|
|
261
262
|
this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
|
|
262
263
|
this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
|
|
263
264
|
this.ctx.shadowBlur = textShadow.blur.number;
|
|
264
|
-
this.renderTextWithLetterSpacing(text, styles.letterSpacing,
|
|
265
|
+
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline, styles.writingMode);
|
|
265
266
|
});
|
|
266
267
|
this.ctx.shadowColor = '';
|
|
267
268
|
this.ctx.shadowOffsetX = 0;
|
|
@@ -273,15 +274,15 @@ class TextRenderer {
|
|
|
273
274
|
* Helper method to render text with paint order support
|
|
274
275
|
* Reduces code duplication in line-clamp and normal rendering
|
|
275
276
|
*/
|
|
276
|
-
renderTextBoundWithPaintOrder(textBound, styles, paintOrderLayers) {
|
|
277
|
+
renderTextBoundWithPaintOrder(textBound, styles, paintOrderLayers, baseline) {
|
|
277
278
|
paintOrderLayers.forEach((paintOrderLayer) => {
|
|
278
279
|
switch (paintOrderLayer) {
|
|
279
280
|
case 0 /* PAINT_ORDER_LAYER.FILL */:
|
|
280
281
|
this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
|
|
281
|
-
this.renderTextWithLetterSpacing(textBound, styles.letterSpacing,
|
|
282
|
+
this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, baseline, styles.writingMode);
|
|
282
283
|
break;
|
|
283
284
|
case 1 /* PAINT_ORDER_LAYER.STROKE */:
|
|
284
|
-
this.renderTextStrokeWithStyle(textBound, styles);
|
|
285
|
+
this.renderTextStrokeWithStyle(textBound, styles, baseline);
|
|
285
286
|
break;
|
|
286
287
|
}
|
|
287
288
|
});
|
|
@@ -362,7 +363,7 @@ class TextRenderer {
|
|
|
362
363
|
* Groups text bounds by their Y position into visual lines, then renders
|
|
363
364
|
* only the first N-1 complete lines followed by an ellipsis on the Nth line.
|
|
364
365
|
*/
|
|
365
|
-
renderLineClampedText(text, styles, paintOrder, containerBounds) {
|
|
366
|
+
renderLineClampedText(text, styles, paintOrder, baseline, containerBounds) {
|
|
366
367
|
const lineHeight = styles.fontSize.number * 1.5;
|
|
367
368
|
const lines = [];
|
|
368
369
|
let currentLine = [];
|
|
@@ -385,7 +386,7 @@ class TextRenderer {
|
|
|
385
386
|
return false; // fall through to normal rendering
|
|
386
387
|
// Render full lines (0..N-2)
|
|
387
388
|
for (let i = 0; i < maxLines - 1; i++) {
|
|
388
|
-
lines[i].forEach((tb) => this.renderTextBoundWithPaintOrder(tb, styles, paintOrder));
|
|
389
|
+
lines[i].forEach((tb) => this.renderTextBoundWithPaintOrder(tb, styles, paintOrder, baseline));
|
|
389
390
|
}
|
|
390
391
|
// Nth line: truncated with ellipsis
|
|
391
392
|
const lastLine = lines[maxLines - 1];
|
|
@@ -398,10 +399,10 @@ class TextRenderer {
|
|
|
398
399
|
for (const layer of paintOrder) {
|
|
399
400
|
if (layer === 0 /* PAINT_ORDER_LAYER.FILL */) {
|
|
400
401
|
this.ctx.fillStyle = (0, color_utilities_1.asString)(styles.color);
|
|
401
|
-
this.renderTextWithLetterSpacing(bounds, styles.letterSpacing,
|
|
402
|
+
this.renderTextWithLetterSpacing(bounds, styles.letterSpacing, baseline, styles.writingMode);
|
|
402
403
|
}
|
|
403
404
|
else if (layer === 1 /* PAINT_ORDER_LAYER.STROKE */) {
|
|
404
|
-
this.renderTextStrokeWithStyle(bounds, styles);
|
|
405
|
+
this.renderTextStrokeWithStyle(bounds, styles, baseline);
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
}
|
|
@@ -411,7 +412,7 @@ class TextRenderer {
|
|
|
411
412
|
* Render single-line text with text-overflow: ellipsis.
|
|
412
413
|
* Returns true if ellipsis was applied (caller should skip normal rendering).
|
|
413
414
|
*/
|
|
414
|
-
renderEllipsisText(text, styles, paintOrder, containerBounds) {
|
|
415
|
+
renderEllipsisText(text, styles, paintOrder, baseline, containerBounds) {
|
|
415
416
|
const lineHeight = styles.fontSize.number * 1.5;
|
|
416
417
|
const firstTop = text.textBounds[0].bounds.top;
|
|
417
418
|
const isSingleLine = text.textBounds.every((tb) => Math.abs(tb.bounds.top - firstTop) < lineHeight * 0.5);
|
|
@@ -429,9 +430,9 @@ class TextRenderer {
|
|
|
429
430
|
const bounds = new text_1.TextBounds(truncated, text.textBounds[0].bounds);
|
|
430
431
|
for (const layer of paintOrder) {
|
|
431
432
|
if (layer === 0 /* PAINT_ORDER_LAYER.FILL */)
|
|
432
|
-
this.renderTextFillWithShadows(bounds, styles);
|
|
433
|
+
this.renderTextFillWithShadows(bounds, styles, baseline);
|
|
433
434
|
else if (layer === 1 /* PAINT_ORDER_LAYER.STROKE */)
|
|
434
|
-
this.renderTextStrokeWithStyle(bounds, styles);
|
|
435
|
+
this.renderTextStrokeWithStyle(bounds, styles, baseline);
|
|
435
436
|
}
|
|
436
437
|
return true;
|
|
437
438
|
}
|
|
@@ -439,18 +440,24 @@ class TextRenderer {
|
|
|
439
440
|
// Reset glyph width cache at the start of each text node render —
|
|
440
441
|
// the font may change between nodes.
|
|
441
442
|
this.glyphWidthCache = null;
|
|
442
|
-
|
|
443
|
+
const [fontString] = this.createFontStyle(styles);
|
|
444
|
+
this.ctx.font = fontString;
|
|
443
445
|
this.ctx.direction = styles.direction === 1 /* DIRECTION.RTL */ ? 'rtl' : 'ltr';
|
|
444
446
|
this.ctx.textAlign = 'left';
|
|
445
447
|
this.ctx.textBaseline = 'alphabetic';
|
|
446
448
|
const paintOrder = styles.paintOrder;
|
|
449
|
+
// Compute baseline using the actual rendered font via Canvas API.
|
|
450
|
+
// This ensures correct positioning whether using webfonts (which may not
|
|
451
|
+
// be loaded in the original document but are active in the Canvas) or
|
|
452
|
+
// system fonts.
|
|
453
|
+
const baseline = (0, font_utils_1.measureBaseline)(this.ctx, styles.fontSize.number);
|
|
447
454
|
// -webkit-line-clamp
|
|
448
455
|
const clamp = styles.webkitLineClamp > 0 &&
|
|
449
456
|
(styles.display & 2 /* DISPLAY.BLOCK */) !== 0 &&
|
|
450
457
|
styles.overflowY === 1 /* OVERFLOW.HIDDEN */ &&
|
|
451
458
|
text.textBounds.length > 0;
|
|
452
459
|
if (clamp) {
|
|
453
|
-
if (this.renderLineClampedText(text, styles, paintOrder, containerBounds))
|
|
460
|
+
if (this.renderLineClampedText(text, styles, paintOrder, baseline, containerBounds))
|
|
454
461
|
return;
|
|
455
462
|
}
|
|
456
463
|
// text-overflow: ellipsis (single-line only)
|
|
@@ -458,18 +465,18 @@ class TextRenderer {
|
|
|
458
465
|
containerBounds &&
|
|
459
466
|
styles.overflowX === 1 /* OVERFLOW.HIDDEN */ &&
|
|
460
467
|
text.textBounds.length > 0;
|
|
461
|
-
if (ellipsis && this.renderEllipsisText(text, styles, paintOrder, containerBounds))
|
|
468
|
+
if (ellipsis && this.renderEllipsisText(text, styles, paintOrder, baseline, containerBounds))
|
|
462
469
|
return;
|
|
463
470
|
// Normal rendering: fill + stroke + decorations per text bound
|
|
464
471
|
text.textBounds.forEach((tb) => {
|
|
465
472
|
paintOrder.forEach((layer) => {
|
|
466
473
|
if (layer === 0 /* PAINT_ORDER_LAYER.FILL */) {
|
|
467
|
-
this.renderTextFillWithShadows(tb, styles);
|
|
474
|
+
this.renderTextFillWithShadows(tb, styles, baseline);
|
|
468
475
|
if (styles.textDecorationLine.length)
|
|
469
476
|
this.renderTextDecoration(tb.bounds, styles);
|
|
470
477
|
}
|
|
471
478
|
else if (layer === 1 /* PAINT_ORDER_LAYER.STROKE */) {
|
|
472
|
-
this.renderTextStrokeWithStyle(tb, styles);
|
|
479
|
+
this.renderTextStrokeWithStyle(tb, styles, baseline);
|
|
473
480
|
}
|
|
474
481
|
});
|
|
475
482
|
});
|
|
@@ -37,7 +37,6 @@ export declare class CanvasRenderer {
|
|
|
37
37
|
ctx: CanvasRenderingContext2D;
|
|
38
38
|
private readonly context;
|
|
39
39
|
private readonly options;
|
|
40
|
-
private readonly fontMetrics;
|
|
41
40
|
private readonly backgroundRenderer;
|
|
42
41
|
private readonly borderRenderer;
|
|
43
42
|
private readonly borderImageRenderer;
|
|
@@ -5,7 +5,6 @@ import { BoundCurves } from '../bound-curves';
|
|
|
5
5
|
import { Vector } from '../vector';
|
|
6
6
|
import { Context } from '../../core/context';
|
|
7
7
|
import { TextRenderer } from './text-renderer';
|
|
8
|
-
import { FontMetrics } from '../font-metrics';
|
|
9
8
|
import { Color } from '../../css/types/color';
|
|
10
9
|
import { CSSParsedDeclaration } from '../../css/index';
|
|
11
10
|
/**
|
|
@@ -36,7 +35,7 @@ export declare function renderReplacedElements(ctx: CanvasRenderingContext2D, co
|
|
|
36
35
|
/**
|
|
37
36
|
* Render form element content: checkbox, radio, text input.
|
|
38
37
|
*/
|
|
39
|
-
export declare function renderFormElements(ctx: CanvasRenderingContext2D,
|
|
38
|
+
export declare function renderFormElements(ctx: CanvasRenderingContext2D, textRenderer: TextRenderer, pathFn: (paths: Vector[]) => void, container: ElementContainer, styles: CSSParsedDeclaration): void;
|
|
40
39
|
/**
|
|
41
40
|
* Render list-item marker (image or text).
|
|
42
41
|
*/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Font measurement utility
|
|
3
|
+
*
|
|
4
|
+
* Provides Canvas API-based baseline measurement that works correctly with
|
|
5
|
+
* webfonts (which may not be loaded in the original document) and system fonts.
|
|
6
|
+
*
|
|
7
|
+
* Fallback chain:
|
|
8
|
+
* 1. fontBoundingBoxAscent — font-level metric (Chrome 99+, FF 116+, Safari 17.4+)
|
|
9
|
+
* 2. actualBoundingBoxAscent — glyph-level metric (widely supported)
|
|
10
|
+
* 3. fallback value — coarse CSS fallback
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Measure the baseline ascent for the currently set font.
|
|
14
|
+
*
|
|
15
|
+
* @param ctx - Canvas 2D rendering context with ctx.font already set
|
|
16
|
+
* @param fallback - Fallback value when no metrics are available (e.g. fontSize.number)
|
|
17
|
+
* @returns The distance from the text baseline to the top of the bounding box
|
|
18
|
+
*/
|
|
19
|
+
export declare const measureBaseline: (ctx: CanvasRenderingContext2D, fallback: number) => number;
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* - Paint order (fill/stroke)
|
|
11
11
|
* - Font styles
|
|
12
12
|
*/
|
|
13
|
-
import { Context } from '../../core/context';
|
|
14
13
|
import { TextContainer } from '../../dom/text-container';
|
|
15
14
|
import { CSSParsedDeclaration } from '../../css';
|
|
16
15
|
import { Bounds } from '../../css/layout/bounds';
|
|
@@ -21,7 +20,6 @@ import { WRITING_MODE } from '../../css/property-descriptors/writing-mode';
|
|
|
21
20
|
*/
|
|
22
21
|
export interface TextRendererDependencies {
|
|
23
22
|
ctx: CanvasRenderingContext2D;
|
|
24
|
-
context: Context;
|
|
25
23
|
options: {
|
|
26
24
|
scale: number;
|
|
27
25
|
};
|