html2canvas-pro 2.3.2 → 2.3.4
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/README.md +6 -2
- package/dist/html2canvas-pro.cjs +104 -14
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +104 -14
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +104 -14
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +3 -3
- package/dist/lib/css/index.js +8 -1
- package/dist/lib/css/property-descriptors/background-clip.js +2 -0
- package/dist/lib/css/syntax/parser.js +5 -4
- package/dist/lib/render/canvas/canvas-renderer.js +123 -9
- package/dist/types/css/property-descriptors/background-clip.d.ts +2 -1
- package/dist/types/render/canvas/canvas-renderer.d.ts +18 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.3.
|
|
2
|
+
* html2canvas-pro 2.3.4 <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
|
*/
|
|
@@ -1727,9 +1727,10 @@ const parseFunctionArgs = (tokens) => {
|
|
|
1727
1727
|
let arg = [];
|
|
1728
1728
|
tokens.forEach((token) => {
|
|
1729
1729
|
if (token.type === 4) {
|
|
1730
|
-
if (arg.length
|
|
1731
|
-
|
|
1732
|
-
|
|
1730
|
+
if (arg.length) {
|
|
1731
|
+
args.push(arg);
|
|
1732
|
+
arg = [];
|
|
1733
|
+
}
|
|
1733
1734
|
return;
|
|
1734
1735
|
}
|
|
1735
1736
|
if (token.type !== 31) arg.push(token);
|
|
@@ -3955,6 +3956,7 @@ const backgroundClip = {
|
|
|
3955
3956
|
if (isIdentToken(token)) switch (token.value) {
|
|
3956
3957
|
case "padding-box": return 1;
|
|
3957
3958
|
case "content-box": return 2;
|
|
3959
|
+
case "text": return 3;
|
|
3958
3960
|
}
|
|
3959
3961
|
return 0;
|
|
3960
3962
|
});
|
|
@@ -6727,7 +6729,8 @@ var CSSParsedCounterDeclaration = class {
|
|
|
6727
6729
|
};
|
|
6728
6730
|
const parseCache = /* @__PURE__ */ new Map();
|
|
6729
6731
|
const parse = (context, descriptor, style) => {
|
|
6730
|
-
|
|
6732
|
+
let rawValue = style !== null && typeof style !== "undefined" ? style.toString() : descriptor.initialValue;
|
|
6733
|
+
if (rawValue.trim() === "") rawValue = descriptor.initialValue;
|
|
6731
6734
|
let valueCache = parseCache.get(descriptor);
|
|
6732
6735
|
if (valueCache) {
|
|
6733
6736
|
const cached = valueCache.get(rawValue);
|
|
@@ -10521,6 +10524,7 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10521
10524
|
this.effectsRenderer.applyEffects(paint.getEffects(2));
|
|
10522
10525
|
const styles = paint.container.styles;
|
|
10523
10526
|
const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length;
|
|
10527
|
+
const hasTextClip = hasTextBackgroundClip(styles);
|
|
10524
10528
|
const borders = [
|
|
10525
10529
|
{
|
|
10526
10530
|
style: styles.borderTopStyle,
|
|
@@ -10543,17 +10547,21 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10543
10547
|
width: styles.borderLeftWidth
|
|
10544
10548
|
}
|
|
10545
10549
|
];
|
|
10546
|
-
const
|
|
10550
|
+
const backgroundClipValue = getBackgroundValueForIndex(styles.backgroundClip, 0);
|
|
10551
|
+
const backgroundPaintingArea = calculateBackgroundCurvedPaintingArea(backgroundClipValue, paint.curves);
|
|
10547
10552
|
if (hasBackground || styles.boxShadow.length) {
|
|
10548
|
-
this.
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
this.ctx.
|
|
10553
|
-
|
|
10553
|
+
if (hasTextClip && paint.container.textNodes.length > 0) await this.renderTextClippedBackground(paint);
|
|
10554
|
+
else {
|
|
10555
|
+
this.ctx.save();
|
|
10556
|
+
this.path(backgroundPaintingArea);
|
|
10557
|
+
this.ctx.clip();
|
|
10558
|
+
if (!isTransparent(styles.backgroundColor)) {
|
|
10559
|
+
this.ctx.fillStyle = asString(styles.backgroundColor);
|
|
10560
|
+
this.ctx.fill();
|
|
10561
|
+
}
|
|
10562
|
+
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
10563
|
+
this.ctx.restore();
|
|
10554
10564
|
}
|
|
10555
|
-
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
10556
|
-
this.ctx.restore();
|
|
10557
10565
|
styles.boxShadow.slice(0).reverse().forEach((shadow) => {
|
|
10558
10566
|
this.ctx.save();
|
|
10559
10567
|
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
|
@@ -10612,6 +10620,82 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10612
10620
|
this.effectsRenderer.applyEffects([]);
|
|
10613
10621
|
return this.canvas;
|
|
10614
10622
|
}
|
|
10623
|
+
/**
|
|
10624
|
+
* Render background clipped to text shape using offscreen canvas with destination-in compositing.
|
|
10625
|
+
* This implements background-clip: text support.
|
|
10626
|
+
*
|
|
10627
|
+
* Algorithm:
|
|
10628
|
+
* 1. Draw background (color + images) onto an offscreen canvas
|
|
10629
|
+
* 2. Use destination-in compositing to clip the background to the text glyph shapes
|
|
10630
|
+
* 3. Composite the result back onto the main canvas
|
|
10631
|
+
*
|
|
10632
|
+
* The offscreen canvas uses CSS-pixel dimensions (not device-pixel) because the
|
|
10633
|
+
* background renderer handles scaling internally via BackgroundRenderer options.
|
|
10634
|
+
*/
|
|
10635
|
+
async renderTextClippedBackground(paint) {
|
|
10636
|
+
const container = paint.container;
|
|
10637
|
+
const styles = container.styles;
|
|
10638
|
+
const bounds = container.bounds;
|
|
10639
|
+
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
10640
|
+
const offscreen = (this.canvas.ownerDocument ?? document).createElement("canvas");
|
|
10641
|
+
const width = Math.ceil(bounds.width);
|
|
10642
|
+
const height = Math.ceil(bounds.height);
|
|
10643
|
+
offscreen.width = width;
|
|
10644
|
+
offscreen.height = height;
|
|
10645
|
+
const offCtx = offscreen.getContext("2d");
|
|
10646
|
+
if (!offCtx) return;
|
|
10647
|
+
const [fontString] = this.textRenderer.createFontStyle(styles);
|
|
10648
|
+
offCtx.font = fontString;
|
|
10649
|
+
offCtx.textBaseline = "alphabetic";
|
|
10650
|
+
offCtx.textAlign = "left";
|
|
10651
|
+
offCtx.direction = styles.direction === 1 ? "rtl" : "ltr";
|
|
10652
|
+
const baseline = measureBaseline(offCtx, styles.fontSize.number);
|
|
10653
|
+
const bgRenderer = new BackgroundRenderer({
|
|
10654
|
+
ctx: offCtx,
|
|
10655
|
+
context: this.context,
|
|
10656
|
+
canvas: offscreen,
|
|
10657
|
+
options: {
|
|
10658
|
+
width,
|
|
10659
|
+
height,
|
|
10660
|
+
scale: 1
|
|
10661
|
+
}
|
|
10662
|
+
});
|
|
10663
|
+
if (!isTransparent(styles.backgroundColor)) {
|
|
10664
|
+
offCtx.fillStyle = asString(styles.backgroundColor);
|
|
10665
|
+
offCtx.fillRect(0, 0, width, height);
|
|
10666
|
+
}
|
|
10667
|
+
offCtx.save();
|
|
10668
|
+
offCtx.translate(-bounds.left, -bounds.top);
|
|
10669
|
+
await bgRenderer.renderBackgroundImage(container);
|
|
10670
|
+
offCtx.restore();
|
|
10671
|
+
offCtx.globalCompositeOperation = "destination-in";
|
|
10672
|
+
offCtx.fillStyle = "#000";
|
|
10673
|
+
const writingMode = styles.writingMode;
|
|
10674
|
+
const letterSpacing = styles.letterSpacing;
|
|
10675
|
+
for (const textNode of container.textNodes) for (const textBound of textNode.textBounds) {
|
|
10676
|
+
const localLeft = textBound.bounds.left - bounds.left;
|
|
10677
|
+
const localTop = textBound.bounds.top - bounds.top + baseline;
|
|
10678
|
+
if (letterSpacing > 0) this.renderTextMaskWithLetterSpacing(offCtx, textBound, letterSpacing, localLeft, localTop, writingMode);
|
|
10679
|
+
else offCtx.fillText(textBound.text, localLeft, localTop);
|
|
10680
|
+
}
|
|
10681
|
+
this.ctx.drawImage(offscreen, bounds.left, bounds.top);
|
|
10682
|
+
}
|
|
10683
|
+
/**
|
|
10684
|
+
* Render text glyphs one-by-one as a mask, applying letter-spacing between characters.
|
|
10685
|
+
* This ensures the mask matches how the text renderer lays out the characters.
|
|
10686
|
+
*/
|
|
10687
|
+
renderTextMaskWithLetterSpacing(ctx, text, letterSpacing, x, y, writingMode) {
|
|
10688
|
+
const letters = segmentGraphemes(text.text);
|
|
10689
|
+
let offset = x;
|
|
10690
|
+
for (const letter of letters) {
|
|
10691
|
+
if (isVerticalWritingMode(writingMode)) {
|
|
10692
|
+
ctx.fillText(text.text, x, y);
|
|
10693
|
+
return;
|
|
10694
|
+
}
|
|
10695
|
+
ctx.fillText(letter, offset, y);
|
|
10696
|
+
offset += ctx.measureText(letter).width + letterSpacing;
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10615
10699
|
};
|
|
10616
10700
|
const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
|
|
10617
10701
|
switch (clip) {
|
|
@@ -10620,6 +10704,12 @@ const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
|
|
|
10620
10704
|
default: return calculatePaddingBoxPath(curves);
|
|
10621
10705
|
}
|
|
10622
10706
|
};
|
|
10707
|
+
/**
|
|
10708
|
+
* Check if any background layer uses background-clip: text
|
|
10709
|
+
*/
|
|
10710
|
+
const hasTextBackgroundClip = (styles) => {
|
|
10711
|
+
return styles.backgroundClip.some((clip) => clip === 3);
|
|
10712
|
+
};
|
|
10623
10713
|
//#endregion
|
|
10624
10714
|
//#region src/render/canvas/foreignobject-renderer.ts
|
|
10625
10715
|
var ForeignObjectRenderer = class {
|