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
package/README.md
CHANGED
|
@@ -22,14 +22,18 @@ Next generation JavaScript screenshot tool.
|
|
|
22
22
|
## Why html2canvas-pro?
|
|
23
23
|
|
|
24
24
|
html2canvas-pro is a fork of [niklasvh/html2canvas](https://github.com/niklasvh/html2canvas) that includes various fixes and new features. It offers several advantages over the original html2canvas:
|
|
25
|
-
- Color
|
|
26
|
-
-
|
|
25
|
+
- Color functions `color()` (incl. relative colors), `lab()`, `lch()`, `oklab()`, `oklch()`
|
|
26
|
+
- `background-clip: text` support
|
|
27
|
+
- `mix-blend-mode` support
|
|
27
28
|
- `object-fit` support for `<img/>`
|
|
28
29
|
- CSS `clip-path` support (inset, circle, ellipse, polygon, path)
|
|
29
30
|
- CSS `writing-mode` support (horizontal-tb, vertical-rl, vertical-lr)
|
|
31
|
+
- Native `drop-shadow()` filter rendering via the canvas shadow API
|
|
30
32
|
- **Image smoothing control** — CSS `image-rendering` property and `imageSmoothing`/`imageSmoothingQuality` options
|
|
31
33
|
- **Security validation** — Built-in input validation (XSS/SSRF protection)
|
|
32
34
|
- **Performance monitoring** — Built-in performance measurement API
|
|
35
|
+
- **Progress & error callbacks** — `onProgress` / `onError` options and `AbortSignal` cancellation
|
|
36
|
+
- **Performance optimizations** — deferred CSS parsing and LRU caches for gradients & CSS parsing
|
|
33
37
|
- Various bug fixes from the [changelog](./CHANGELOG.md)
|
|
34
38
|
|
|
35
39
|
If you found this helpful, don't forget to
|
package/dist/html2canvas-pro.cjs
CHANGED
|
@@ -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
|
*/
|
|
@@ -1731,9 +1731,10 @@ const parseFunctionArgs = (tokens) => {
|
|
|
1731
1731
|
let arg = [];
|
|
1732
1732
|
tokens.forEach((token) => {
|
|
1733
1733
|
if (token.type === 4) {
|
|
1734
|
-
if (arg.length
|
|
1735
|
-
|
|
1736
|
-
|
|
1734
|
+
if (arg.length) {
|
|
1735
|
+
args.push(arg);
|
|
1736
|
+
arg = [];
|
|
1737
|
+
}
|
|
1737
1738
|
return;
|
|
1738
1739
|
}
|
|
1739
1740
|
if (token.type !== 31) arg.push(token);
|
|
@@ -3959,6 +3960,7 @@ const backgroundClip = {
|
|
|
3959
3960
|
if (isIdentToken(token)) switch (token.value) {
|
|
3960
3961
|
case "padding-box": return 1;
|
|
3961
3962
|
case "content-box": return 2;
|
|
3963
|
+
case "text": return 3;
|
|
3962
3964
|
}
|
|
3963
3965
|
return 0;
|
|
3964
3966
|
});
|
|
@@ -6731,7 +6733,8 @@ var CSSParsedCounterDeclaration = class {
|
|
|
6731
6733
|
};
|
|
6732
6734
|
const parseCache = /* @__PURE__ */ new Map();
|
|
6733
6735
|
const parse = (context, descriptor, style) => {
|
|
6734
|
-
|
|
6736
|
+
let rawValue = style !== null && typeof style !== "undefined" ? style.toString() : descriptor.initialValue;
|
|
6737
|
+
if (rawValue.trim() === "") rawValue = descriptor.initialValue;
|
|
6735
6738
|
let valueCache = parseCache.get(descriptor);
|
|
6736
6739
|
if (valueCache) {
|
|
6737
6740
|
const cached = valueCache.get(rawValue);
|
|
@@ -10525,6 +10528,7 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10525
10528
|
this.effectsRenderer.applyEffects(paint.getEffects(2));
|
|
10526
10529
|
const styles = paint.container.styles;
|
|
10527
10530
|
const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length;
|
|
10531
|
+
const hasTextClip = hasTextBackgroundClip(styles);
|
|
10528
10532
|
const borders = [
|
|
10529
10533
|
{
|
|
10530
10534
|
style: styles.borderTopStyle,
|
|
@@ -10547,17 +10551,21 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10547
10551
|
width: styles.borderLeftWidth
|
|
10548
10552
|
}
|
|
10549
10553
|
];
|
|
10550
|
-
const
|
|
10554
|
+
const backgroundClipValue = getBackgroundValueForIndex(styles.backgroundClip, 0);
|
|
10555
|
+
const backgroundPaintingArea = calculateBackgroundCurvedPaintingArea(backgroundClipValue, paint.curves);
|
|
10551
10556
|
if (hasBackground || styles.boxShadow.length) {
|
|
10552
|
-
this.
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
this.ctx.
|
|
10557
|
-
|
|
10557
|
+
if (hasTextClip && paint.container.textNodes.length > 0) await this.renderTextClippedBackground(paint);
|
|
10558
|
+
else {
|
|
10559
|
+
this.ctx.save();
|
|
10560
|
+
this.path(backgroundPaintingArea);
|
|
10561
|
+
this.ctx.clip();
|
|
10562
|
+
if (!isTransparent(styles.backgroundColor)) {
|
|
10563
|
+
this.ctx.fillStyle = asString(styles.backgroundColor);
|
|
10564
|
+
this.ctx.fill();
|
|
10565
|
+
}
|
|
10566
|
+
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
10567
|
+
this.ctx.restore();
|
|
10558
10568
|
}
|
|
10559
|
-
await this.backgroundRenderer.renderBackgroundImage(paint.container);
|
|
10560
|
-
this.ctx.restore();
|
|
10561
10569
|
styles.boxShadow.slice(0).reverse().forEach((shadow) => {
|
|
10562
10570
|
this.ctx.save();
|
|
10563
10571
|
const borderBoxArea = calculateBorderBoxPath(paint.curves);
|
|
@@ -10616,6 +10624,82 @@ var CanvasRenderer = class CanvasRenderer {
|
|
|
10616
10624
|
this.effectsRenderer.applyEffects([]);
|
|
10617
10625
|
return this.canvas;
|
|
10618
10626
|
}
|
|
10627
|
+
/**
|
|
10628
|
+
* Render background clipped to text shape using offscreen canvas with destination-in compositing.
|
|
10629
|
+
* This implements background-clip: text support.
|
|
10630
|
+
*
|
|
10631
|
+
* Algorithm:
|
|
10632
|
+
* 1. Draw background (color + images) onto an offscreen canvas
|
|
10633
|
+
* 2. Use destination-in compositing to clip the background to the text glyph shapes
|
|
10634
|
+
* 3. Composite the result back onto the main canvas
|
|
10635
|
+
*
|
|
10636
|
+
* The offscreen canvas uses CSS-pixel dimensions (not device-pixel) because the
|
|
10637
|
+
* background renderer handles scaling internally via BackgroundRenderer options.
|
|
10638
|
+
*/
|
|
10639
|
+
async renderTextClippedBackground(paint) {
|
|
10640
|
+
const container = paint.container;
|
|
10641
|
+
const styles = container.styles;
|
|
10642
|
+
const bounds = container.bounds;
|
|
10643
|
+
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
10644
|
+
const offscreen = (this.canvas.ownerDocument ?? document).createElement("canvas");
|
|
10645
|
+
const width = Math.ceil(bounds.width);
|
|
10646
|
+
const height = Math.ceil(bounds.height);
|
|
10647
|
+
offscreen.width = width;
|
|
10648
|
+
offscreen.height = height;
|
|
10649
|
+
const offCtx = offscreen.getContext("2d");
|
|
10650
|
+
if (!offCtx) return;
|
|
10651
|
+
const [fontString] = this.textRenderer.createFontStyle(styles);
|
|
10652
|
+
offCtx.font = fontString;
|
|
10653
|
+
offCtx.textBaseline = "alphabetic";
|
|
10654
|
+
offCtx.textAlign = "left";
|
|
10655
|
+
offCtx.direction = styles.direction === 1 ? "rtl" : "ltr";
|
|
10656
|
+
const baseline = measureBaseline(offCtx, styles.fontSize.number);
|
|
10657
|
+
const bgRenderer = new BackgroundRenderer({
|
|
10658
|
+
ctx: offCtx,
|
|
10659
|
+
context: this.context,
|
|
10660
|
+
canvas: offscreen,
|
|
10661
|
+
options: {
|
|
10662
|
+
width,
|
|
10663
|
+
height,
|
|
10664
|
+
scale: 1
|
|
10665
|
+
}
|
|
10666
|
+
});
|
|
10667
|
+
if (!isTransparent(styles.backgroundColor)) {
|
|
10668
|
+
offCtx.fillStyle = asString(styles.backgroundColor);
|
|
10669
|
+
offCtx.fillRect(0, 0, width, height);
|
|
10670
|
+
}
|
|
10671
|
+
offCtx.save();
|
|
10672
|
+
offCtx.translate(-bounds.left, -bounds.top);
|
|
10673
|
+
await bgRenderer.renderBackgroundImage(container);
|
|
10674
|
+
offCtx.restore();
|
|
10675
|
+
offCtx.globalCompositeOperation = "destination-in";
|
|
10676
|
+
offCtx.fillStyle = "#000";
|
|
10677
|
+
const writingMode = styles.writingMode;
|
|
10678
|
+
const letterSpacing = styles.letterSpacing;
|
|
10679
|
+
for (const textNode of container.textNodes) for (const textBound of textNode.textBounds) {
|
|
10680
|
+
const localLeft = textBound.bounds.left - bounds.left;
|
|
10681
|
+
const localTop = textBound.bounds.top - bounds.top + baseline;
|
|
10682
|
+
if (letterSpacing > 0) this.renderTextMaskWithLetterSpacing(offCtx, textBound, letterSpacing, localLeft, localTop, writingMode);
|
|
10683
|
+
else offCtx.fillText(textBound.text, localLeft, localTop);
|
|
10684
|
+
}
|
|
10685
|
+
this.ctx.drawImage(offscreen, bounds.left, bounds.top);
|
|
10686
|
+
}
|
|
10687
|
+
/**
|
|
10688
|
+
* Render text glyphs one-by-one as a mask, applying letter-spacing between characters.
|
|
10689
|
+
* This ensures the mask matches how the text renderer lays out the characters.
|
|
10690
|
+
*/
|
|
10691
|
+
renderTextMaskWithLetterSpacing(ctx, text, letterSpacing, x, y, writingMode) {
|
|
10692
|
+
const letters = segmentGraphemes(text.text);
|
|
10693
|
+
let offset = x;
|
|
10694
|
+
for (const letter of letters) {
|
|
10695
|
+
if (isVerticalWritingMode(writingMode)) {
|
|
10696
|
+
ctx.fillText(text.text, x, y);
|
|
10697
|
+
return;
|
|
10698
|
+
}
|
|
10699
|
+
ctx.fillText(letter, offset, y);
|
|
10700
|
+
offset += ctx.measureText(letter).width + letterSpacing;
|
|
10701
|
+
}
|
|
10702
|
+
}
|
|
10619
10703
|
};
|
|
10620
10704
|
const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
|
|
10621
10705
|
switch (clip) {
|
|
@@ -10624,6 +10708,12 @@ const calculateBackgroundCurvedPaintingArea = (clip, curves) => {
|
|
|
10624
10708
|
default: return calculatePaddingBoxPath(curves);
|
|
10625
10709
|
}
|
|
10626
10710
|
};
|
|
10711
|
+
/**
|
|
10712
|
+
* Check if any background layer uses background-clip: text
|
|
10713
|
+
*/
|
|
10714
|
+
const hasTextBackgroundClip = (styles) => {
|
|
10715
|
+
return styles.backgroundClip.some((clip) => clip === 3);
|
|
10716
|
+
};
|
|
10627
10717
|
//#endregion
|
|
10628
10718
|
//#region src/render/canvas/foreignobject-renderer.ts
|
|
10629
10719
|
var ForeignObjectRenderer = class {
|