html2canvas-pro 2.3.2 → 2.3.3
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 +98 -10
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +98 -10
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +98 -10
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +3 -3
- package/dist/lib/css/property-descriptors/background-clip.js +2 -0
- 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
package/dist/html2canvas-pro.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.3.
|
|
2
|
+
* html2canvas-pro 2.3.3 <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
|
*/
|
|
@@ -3962,6 +3962,7 @@
|
|
|
3962
3962
|
if (isIdentToken(token)) switch (token.value) {
|
|
3963
3963
|
case "padding-box": return 1;
|
|
3964
3964
|
case "content-box": return 2;
|
|
3965
|
+
case "text": return 3;
|
|
3965
3966
|
}
|
|
3966
3967
|
return 0;
|
|
3967
3968
|
});
|
|
@@ -10528,6 +10529,7 @@
|
|
|
10528
10529
|
this.effectsRenderer.applyEffects(paint.getEffects(2));
|
|
10529
10530
|
const styles = paint.container.styles;
|
|
10530
10531
|
const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length;
|
|
10532
|
+
const hasTextClip = hasTextBackgroundClip(styles);
|
|
10531
10533
|
const borders = [
|
|
10532
10534
|
{
|
|
10533
10535
|
style: styles.borderTopStyle,
|
|
@@ -10550,17 +10552,21 @@
|
|
|
10550
10552
|
width: styles.borderLeftWidth
|
|
10551
10553
|
}
|
|
10552
10554
|
];
|
|
10553
|
-
const
|
|
10555
|
+
const backgroundClipValue = getBackgroundValueForIndex(styles.backgroundClip, 0);
|
|
10556
|
+
const backgroundPaintingArea = calculateBackgroundCurvedPaintingArea(backgroundClipValue, paint.curves);
|
|
10554
10557
|
if (hasBackground || styles.boxShadow.length) {
|
|
10555
|
-
this.
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
this.ctx.
|
|
10560
|
-
|
|
10558
|
+
if (hasTextClip && paint.container.textNodes.length > 0) await this.renderTextClippedBackground(paint);
|
|
10559
|
+
else {
|
|
10560
|
+
this.ctx.save();
|
|
10561
|
+
this.path(backgroundPaintingArea);
|
|
10562
|
+
this.ctx.clip();
|
|
10563
|
+
if (!isTransparent(styles.backgroundColor)) {
|
|
10564
|
+
this.ctx.fillStyle = asString(styles.backgroundColor);
|
|
10565
|
+
this.ctx.fill();
|
|
10566
|
+
}
|
|
10567
|
+
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
10568
|
+
this.ctx.restore();
|
|
10561
10569
|
}
|
|
10562
|
-
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
10563
|
-
this.ctx.restore();
|
|
10564
10570
|
styles.boxShadow.slice(0).reverse().forEach((shadow) => {
|
|
10565
10571
|
this.ctx.save();
|
|
10566
10572
|
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
|
@@ -10619,6 +10625,82 @@
|
|
|
10619
10625
|
this.effectsRenderer.applyEffects([]);
|
|
10620
10626
|
return this.canvas;
|
|
10621
10627
|
}
|
|
10628
|
+
/**
|
|
10629
|
+
* Render background clipped to text shape using offscreen canvas with destination-in compositing.
|
|
10630
|
+
* This implements background-clip: text support.
|
|
10631
|
+
*
|
|
10632
|
+
* Algorithm:
|
|
10633
|
+
* 1. Draw background (color + images) onto an offscreen canvas
|
|
10634
|
+
* 2. Use destination-in compositing to clip the background to the text glyph shapes
|
|
10635
|
+
* 3. Composite the result back onto the main canvas
|
|
10636
|
+
*
|
|
10637
|
+
* The offscreen canvas uses CSS-pixel dimensions (not device-pixel) because the
|
|
10638
|
+
* background renderer handles scaling internally via BackgroundRenderer options.
|
|
10639
|
+
*/
|
|
10640
|
+
async renderTextClippedBackground(paint) {
|
|
10641
|
+
const container = paint.container;
|
|
10642
|
+
const styles = container.styles;
|
|
10643
|
+
const bounds = container.bounds;
|
|
10644
|
+
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
10645
|
+
const offscreen = (this.canvas.ownerDocument ?? document).createElement("canvas");
|
|
10646
|
+
const width = Math.ceil(bounds.width);
|
|
10647
|
+
const height = Math.ceil(bounds.height);
|
|
10648
|
+
offscreen.width = width;
|
|
10649
|
+
offscreen.height = height;
|
|
10650
|
+
const offCtx = offscreen.getContext("2d");
|
|
10651
|
+
if (!offCtx) return;
|
|
10652
|
+
const [fontString] = this.textRenderer.createFontStyle(styles);
|
|
10653
|
+
offCtx.font = fontString;
|
|
10654
|
+
offCtx.textBaseline = "alphabetic";
|
|
10655
|
+
offCtx.textAlign = "left";
|
|
10656
|
+
offCtx.direction = styles.direction === 1 ? "rtl" : "ltr";
|
|
10657
|
+
const baseline = measureBaseline(offCtx, styles.fontSize.number);
|
|
10658
|
+
const bgRenderer = new BackgroundRenderer({
|
|
10659
|
+
ctx: offCtx,
|
|
10660
|
+
context: this.context,
|
|
10661
|
+
canvas: offscreen,
|
|
10662
|
+
options: {
|
|
10663
|
+
width,
|
|
10664
|
+
height,
|
|
10665
|
+
scale: 1
|
|
10666
|
+
}
|
|
10667
|
+
});
|
|
10668
|
+
if (!isTransparent(styles.backgroundColor)) {
|
|
10669
|
+
offCtx.fillStyle = asString(styles.backgroundColor);
|
|
10670
|
+
offCtx.fillRect(0, 0, width, height);
|
|
10671
|
+
}
|
|
10672
|
+
offCtx.save();
|
|
10673
|
+
offCtx.translate(-bounds.left, -bounds.top);
|
|
10674
|
+
await bgRenderer.renderBackgroundImage(container);
|
|
10675
|
+
offCtx.restore();
|
|
10676
|
+
offCtx.globalCompositeOperation = "destination-in";
|
|
10677
|
+
offCtx.fillStyle = "#000";
|
|
10678
|
+
const writingMode = styles.writingMode;
|
|
10679
|
+
const letterSpacing = styles.letterSpacing;
|
|
10680
|
+
for (const textNode of container.textNodes) for (const textBound of textNode.textBounds) {
|
|
10681
|
+
const localLeft = textBound.bounds.left - bounds.left;
|
|
10682
|
+
const localTop = textBound.bounds.top - bounds.top + baseline;
|
|
10683
|
+
if (letterSpacing > 0) this.renderTextMaskWithLetterSpacing(offCtx, textBound, letterSpacing, localLeft, localTop, writingMode);
|
|
10684
|
+
else offCtx.fillText(textBound.text, localLeft, localTop);
|
|
10685
|
+
}
|
|
10686
|
+
this.ctx.drawImage(offscreen, bounds.left, bounds.top);
|
|
10687
|
+
}
|
|
10688
|
+
/**
|
|
10689
|
+
* Render text glyphs one-by-one as a mask, applying letter-spacing between characters.
|
|
10690
|
+
* This ensures the mask matches how the text renderer lays out the characters.
|
|
10691
|
+
*/
|
|
10692
|
+
renderTextMaskWithLetterSpacing(ctx, text, letterSpacing, x, y, writingMode) {
|
|
10693
|
+
const letters = segmentGraphemes(text.text);
|
|
10694
|
+
let offset = x;
|
|
10695
|
+
for (const letter of letters) {
|
|
10696
|
+
if (isVerticalWritingMode(writingMode)) {
|
|
10697
|
+
ctx.fillText(text.text, x, y);
|
|
10698
|
+
return;
|
|
10699
|
+
}
|
|
10700
|
+
ctx.fillText(letter, offset, y);
|
|
10701
|
+
offset += ctx.measureText(letter).width + letterSpacing;
|
|
10702
|
+
}
|
|
10703
|
+
}
|
|
10622
10704
|
};
|
|
10623
10705
|
const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
|
|
10624
10706
|
switch (clip) {
|
|
@@ -10627,6 +10709,12 @@
|
|
|
10627
10709
|
default: return calculatePaddingBoxPath(curves);
|
|
10628
10710
|
}
|
|
10629
10711
|
};
|
|
10712
|
+
/**
|
|
10713
|
+
* Check if any background layer uses background-clip: text
|
|
10714
|
+
*/
|
|
10715
|
+
const hasTextBackgroundClip = (styles) => {
|
|
10716
|
+
return styles.backgroundClip.some((clip) => clip === 3);
|
|
10717
|
+
};
|
|
10630
10718
|
//#endregion
|
|
10631
10719
|
//#region src/render/canvas/foreignobject-renderer.ts
|
|
10632
10720
|
var ForeignObjectRenderer = class {
|