html2canvas-pro 2.3.0 → 2.3.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 +25 -22
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +25 -22
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +25 -22
- 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 -1
- package/dist/lib/render/canvas/text-renderer.js +26 -20
- package/dist/types/render/canvas/text-renderer.d.ts +3 -2
- package/package.json +1 -1
|
@@ -100,6 +100,7 @@ class TextRenderer {
|
|
|
100
100
|
*/
|
|
101
101
|
this.glyphWidthCache = null;
|
|
102
102
|
this.ctx = deps.ctx;
|
|
103
|
+
this.fontMetrics = deps.fontMetrics;
|
|
103
104
|
this.options = deps.options;
|
|
104
105
|
this.decorationRenderer = new text_decoration_renderer_1.TextDecorationRenderer(deps.ctx);
|
|
105
106
|
}
|
|
@@ -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,23 @@ 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, fontFamily, fontSize] = 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
|
+
// Use the measured baseline from FontMetrics instead of fontSize,
|
|
450
|
+
// which correctly accounts for fonts with large descenders (e.g. Paul)
|
|
451
|
+
// where the alphabetic baseline is closer to the top of the em-square.
|
|
452
|
+
const { baseline } = this.fontMetrics.getMetrics(fontFamily, fontSize);
|
|
447
453
|
// -webkit-line-clamp
|
|
448
454
|
const clamp = styles.webkitLineClamp > 0 &&
|
|
449
455
|
(styles.display & 2 /* DISPLAY.BLOCK */) !== 0 &&
|
|
450
456
|
styles.overflowY === 1 /* OVERFLOW.HIDDEN */ &&
|
|
451
457
|
text.textBounds.length > 0;
|
|
452
458
|
if (clamp) {
|
|
453
|
-
if (this.renderLineClampedText(text, styles, paintOrder, containerBounds))
|
|
459
|
+
if (this.renderLineClampedText(text, styles, paintOrder, baseline, containerBounds))
|
|
454
460
|
return;
|
|
455
461
|
}
|
|
456
462
|
// text-overflow: ellipsis (single-line only)
|
|
@@ -458,18 +464,18 @@ class TextRenderer {
|
|
|
458
464
|
containerBounds &&
|
|
459
465
|
styles.overflowX === 1 /* OVERFLOW.HIDDEN */ &&
|
|
460
466
|
text.textBounds.length > 0;
|
|
461
|
-
if (ellipsis && this.renderEllipsisText(text, styles, paintOrder, containerBounds))
|
|
467
|
+
if (ellipsis && this.renderEllipsisText(text, styles, paintOrder, baseline, containerBounds))
|
|
462
468
|
return;
|
|
463
469
|
// Normal rendering: fill + stroke + decorations per text bound
|
|
464
470
|
text.textBounds.forEach((tb) => {
|
|
465
471
|
paintOrder.forEach((layer) => {
|
|
466
472
|
if (layer === 0 /* PAINT_ORDER_LAYER.FILL */) {
|
|
467
|
-
this.renderTextFillWithShadows(tb, styles);
|
|
473
|
+
this.renderTextFillWithShadows(tb, styles, baseline);
|
|
468
474
|
if (styles.textDecorationLine.length)
|
|
469
475
|
this.renderTextDecoration(tb.bounds, styles);
|
|
470
476
|
}
|
|
471
477
|
else if (layer === 1 /* PAINT_ORDER_LAYER.STROKE */) {
|
|
472
|
-
this.renderTextStrokeWithStyle(tb, styles);
|
|
478
|
+
this.renderTextStrokeWithStyle(tb, styles, baseline);
|
|
473
479
|
}
|
|
474
480
|
});
|
|
475
481
|
});
|
|
@@ -10,18 +10,18 @@
|
|
|
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';
|
|
17
16
|
import { TextBounds } from '../../css/layout/text';
|
|
18
17
|
import { WRITING_MODE } from '../../css/property-descriptors/writing-mode';
|
|
18
|
+
import { FontMetrics } from '../font-metrics';
|
|
19
19
|
/**
|
|
20
20
|
* Dependencies required for TextRenderer
|
|
21
21
|
*/
|
|
22
22
|
export interface TextRendererDependencies {
|
|
23
23
|
ctx: CanvasRenderingContext2D;
|
|
24
|
-
|
|
24
|
+
fontMetrics: FontMetrics;
|
|
25
25
|
options: {
|
|
26
26
|
scale: number;
|
|
27
27
|
};
|
|
@@ -35,6 +35,7 @@ export declare const hasCJKCharacters: (text: string) => boolean;
|
|
|
35
35
|
*/
|
|
36
36
|
export declare class TextRenderer {
|
|
37
37
|
private readonly ctx;
|
|
38
|
+
private readonly fontMetrics;
|
|
38
39
|
private readonly options;
|
|
39
40
|
private readonly decorationRenderer;
|
|
40
41
|
/**
|