html2canvas-pro 2.6.0 → 2.6.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.
@@ -1,4 +1,4 @@
1
- import { Context } from './context';
1
+ import { Logger } from './logger';
2
2
  /**
3
3
  * Performance Metric
4
4
  *
@@ -33,7 +33,8 @@ export interface PerformanceSummary {
33
33
  *
34
34
  * Usage:
35
35
  * ```typescript
36
- * const monitor = new PerformanceMonitor(context);
36
+ * // Standalone (logs disabled) or with the render pipeline's context.
37
+ * const monitor = new PerformanceMonitor(null);
37
38
  *
38
39
  * monitor.start('clone');
39
40
  * await cloneDocument();
@@ -43,12 +44,12 @@ export interface PerformanceSummary {
43
44
  * ```
44
45
  */
45
46
  export declare class PerformanceMonitor {
46
- private readonly context;
47
+ private readonly logger;
47
48
  private readonly activeMetrics;
48
49
  private readonly completedMetrics;
49
50
  private readonly enabled;
50
51
  private readonly getTime;
51
- constructor(context: Context | null, enabled?: boolean);
52
+ constructor(logger: Logger | null, enabled?: boolean);
52
53
  /**
53
54
  * Start measuring a performance metric
54
55
  *
@@ -74,6 +74,8 @@ import { boxDecorationBreak } from './property-descriptors/box-decoration-break'
74
74
  import { type AccentColor } from './property-descriptors/accent-color';
75
75
  import { type WebkitTextFillColor } from './property-descriptors/webkit-text-fill-color';
76
76
  import { type ISOLATION } from './property-descriptors/isolation';
77
+ import { type WILL_CHANGE } from './property-descriptors/will-change';
78
+ import { type CONTAIN } from './property-descriptors/contain';
77
79
  import { type OUTLINE_STYLE, type OutlineColor } from './property-descriptors/outline';
78
80
  import { type FilterValue } from './property-descriptors/filter';
79
81
  import { type MaskImage, type MaskPosition, type MaskRepeat, type MaskSize } from './property-descriptors/mask';
@@ -173,6 +175,8 @@ export declare class CSSParsedDeclaration {
173
175
  accentColor: AccentColor;
174
176
  webkitTextFillColor: WebkitTextFillColor;
175
177
  isolation: ISOLATION;
178
+ willChange: WILL_CHANGE;
179
+ contain: CONTAIN;
176
180
  outlineColor: OutlineColor;
177
181
  outlineStyle: OUTLINE_STYLE;
178
182
  outlineWidth: number;
@@ -0,0 +1,7 @@
1
+ import { IPropertyIdentValueDescriptor } from '../property-descriptor';
2
+ export declare const enum CONTAIN {
3
+ NONE = 0,
4
+ /** `paint`, `layout`, `strict`, `content` or `size paint`-style combos. */
5
+ PAINT_ISOLATION = 1
6
+ }
7
+ export declare const contain: IPropertyIdentValueDescriptor<CONTAIN>;
@@ -0,0 +1,7 @@
1
+ import { IPropertyListDescriptor } from '../property-descriptor';
2
+ export declare const enum WILL_CHANGE {
3
+ AUTO = 0,
4
+ /** Any paint-affecting hint: the element creates a stacking context. */
5
+ STACKING = 1
6
+ }
7
+ export declare const willChange: IPropertyListDescriptor<WILL_CHANGE>;
@@ -42,6 +42,13 @@ export declare class DocumentCloner {
42
42
  * the other stylesheet-cloning paths.
43
43
  */
44
44
  private appendDocumentAdoptedStyles;
45
+ /**
46
+ * Add the rules that hide the source ::before / ::after (the clone draws
47
+ * them as surrogate elements) to the clone's <head>. Inside <body> the
48
+ * <style> would be one of its children, and `body > :first-child`,
49
+ * `:nth-child()` and sibling selectors would match the wrong elements.
50
+ */
51
+ private appendPseudoHideStyles;
45
52
  toIFrame(ownerDocument: Document, windowSize: Bounds): Promise<HTMLIFrameElement>;
46
53
  createElementClone<T extends HTMLElement | SVGElement>(node: T): HTMLElement | SVGElement;
47
54
  createCustomElementClone(originalNode: HTMLElement, clonedNode: HTMLElement): HTMLElement;
@@ -2,6 +2,7 @@ import { CSSParsedDeclaration } from '../css/index';
2
2
  import { TextContainer } from './text-container';
3
3
  import { Bounds } from '../css/layout/bounds';
4
4
  import { Context } from '../core/context';
5
+ import { FilterReferences } from './filter-reference';
5
6
  export interface ElementContainerOptions {
6
7
  normalizeDom?: boolean;
7
8
  }
@@ -17,6 +18,11 @@ export declare class ElementContainer {
17
18
  * bounds to leave a matching gap (issue #227).
18
19
  */
19
20
  readonly legendBounds?: Bounds;
21
+ /**
22
+ * Same-document `filter: url(#id)` references, resolved while the cloned
23
+ * DOM is available. The surface compositor renders them in an SVG pass.
24
+ */
25
+ readonly filterReferences?: FilterReferences;
20
26
  createsStackingContext: boolean;
21
27
  createsRealStackingContext: boolean;
22
28
  isListOwner: boolean;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * CSS `filter: url(#id)` references for the surface compositor.
3
+ *
4
+ * The referenced <filter> is resolved while the cloned DOM is still available
5
+ * and copied into a detached, self-contained form: its region is converted to
6
+ * user space around the element's border box, and presentation properties a
7
+ * stylesheet may set are inlined. The copy is later serialized into an SVG
8
+ * image together with the composited surface, so it must not depend on the
9
+ * document it came from.
10
+ */
11
+ export interface FilterRegion {
12
+ x: number;
13
+ y: number;
14
+ width: number;
15
+ height: number;
16
+ }
17
+ export interface FilterReference {
18
+ /** Detached copy with a userSpaceOnUse region relative to the border box. */
19
+ readonly element: SVGFilterElement;
20
+ readonly region: FilterRegion;
21
+ /** How far (CSS px) the filter region reaches beyond the border box. */
22
+ readonly outset: number;
23
+ }
24
+ export type FilterReferences = ReadonlyMap<string, FilterReference>;
25
+ /** The `#id` of a same-document url() body (`"#id"`, `'#id'` or `#id`), or null. */
26
+ export declare const parseFilterReference: (body: string) => string | null;
27
+ /**
28
+ * Resolve every same-document url() reference of a computed filter value.
29
+ * References that cannot be expressed in the serialized SVG are left out, so
30
+ * the compositor rejects the chain and the subtree keeps the legacy renderer.
31
+ */
32
+ export declare function resolveFilterReferences(element: Element, filter: string | null, box: {
33
+ width: number;
34
+ height: number;
35
+ }): FilterReferences | undefined;
@@ -15,9 +15,9 @@ export declare enum PseudoElementType {
15
15
  }
16
16
  /**
17
17
  * Inject the stylesheet that hides pseudo-element surrogate containers.
18
- * Called on the cloned <body> so the surrogates themselves never render.
18
+ * Added to the cloned <head> so the surrogates themselves never render.
19
19
  */
20
- export declare const createPseudoHideStyles: (body: HTMLElement, cspNonce?: string) => void;
20
+ export declare const createPseudoHideStyles: (parent: Element, cspNonce?: string) => void;
21
21
  /**
22
22
  * Resolves computed `content` of ::before/::after into surrogate DOM nodes.
23
23
  *
@@ -50,11 +50,26 @@ export declare class CanvasRenderer {
50
50
  constructor(context: Context, options: RenderConfigurations, surfaceRoot?: ElementPaint | undefined, surfaceBudget?: SurfaceBudget);
51
51
  renderStack(stack: StackingContext): Promise<void>;
52
52
  private canComposite;
53
+ /**
54
+ * Root-to-element transform accumulated from the ancestor chain's
55
+ * TransformEffects (including zoom), stopping at the surface root. The
56
+ * element's own TransformEffect is intentionally excluded: inside the
57
+ * offscreen surface it is still applied by the normal effects pass, and
58
+ * at composition time it is filtered from the recomposed effect list.
59
+ */
60
+ private ancestorTransform;
53
61
  private renderCompositedStack;
54
62
  /**
55
63
  * Composite an alpha mask onto a finished element surface: all mask
56
64
  * layers are painted additively into a mask surface, then kept pixels are
57
65
  * selected with destination-in.
66
+ *
67
+ * Coordinate spaces: the source surface paints in LAYOUT space (the
68
+ * ancestor TransformEffects stay active on the main canvas and transform
69
+ * the draw-back), so the mask must be painted in layout space too —
70
+ * anchored to the element's border box (containerBounds) — riding ONLY
71
+ * the element's own transform effects, which apply inside the surface
72
+ * rather than at draw-back.
58
73
  */
59
74
  private applyMask;
60
75
  /**
@@ -1,12 +1,14 @@
1
+ import { FilterReferences } from '../../dom/filter-reference';
1
2
  export declare class FilterSurfaceError extends Error {
2
3
  }
3
4
  export declare const releaseSurface: (canvas: HTMLCanvasElement) => void;
4
5
  /**
5
6
  * A parsed CSS filter chain. Functions keep their declaration order — CSS
6
7
  * applies them left to right, and the native canvas backend reproduces that
7
- * order exactly. Chains containing functions the backends cannot express
8
- * (url() references, unknown functions) fail parsing and stay on the legacy
9
- * renderer, where ctx.filter receives the full string natively.
8
+ * order exactly. Same-document url() references are kept as their own step
9
+ * and rendered by an SVG pass. Chains containing functions the backends
10
+ * cannot express (external url() references, unknown functions) fail parsing
11
+ * and stay on the legacy renderer, where ctx.filter receives the full string.
10
12
  */
11
13
  export type FilterFunction = {
12
14
  kind: 'blur';
@@ -21,6 +23,9 @@ export type FilterFunction = {
21
23
  kind: 'unitless';
22
24
  name: UnitlessFilterName;
23
25
  value: number;
26
+ } | {
27
+ kind: 'url';
28
+ ref: string;
24
29
  };
25
30
  export type UnitlessFilterName = 'brightness' | 'contrast' | 'grayscale' | 'hue-rotate' | 'invert' | 'opacity' | 'saturate' | 'sepia';
26
31
  export interface ParsedFilter {
@@ -36,8 +41,18 @@ export declare const nativeFilterString: (filter: ParsedFilter, scale: number) =
36
41
  /**
37
42
  * Outset (in CSS pixels) a surface must grow so blur/shadow spillover is not
38
43
  * cropped. Unitless functions never spread beyond the bounds. Consecutive
39
- * blurs compound, so their radii sum.
44
+ * blurs compound, so their radii sum. A url() reference can paint up to its
45
+ * filter region, which may extend beyond the border box.
40
46
  */
41
- export declare function filterOutset(filter: ParsedFilter): number;
47
+ export declare function filterOutset(filter: ParsedFilter, references?: FilterReferences): number;
42
48
  export declare function supportsNativeFilters(owner?: Document): boolean;
43
- export declare function renderFilterSurface(source: HTMLCanvasElement, filter: ParsedFilter, opacity: number, scale: number, signal?: AbortSignal): Promise<HTMLCanvasElement>;
49
+ /** Where url() references resolve, and the border box origin inside the surface (CSS px). */
50
+ export interface FilterSurfaceReferences {
51
+ references?: FilterReferences;
52
+ origin: {
53
+ x: number;
54
+ y: number;
55
+ };
56
+ }
57
+ export declare function renderFilterSurface(source: HTMLCanvasElement, filter: ParsedFilter, opacity: number, scale: number, signal?: AbortSignal, references?: FilterSurfaceReferences): Promise<HTMLCanvasElement>;
58
+ export declare function renderSvgFilterSurface(source: HTMLCanvasElement, filter: ParsedFilter, opacity: number, scale: number, signal?: AbortSignal): Promise<HTMLCanvasElement>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 2D affine matrix utilities for the composited-surface render path.
3
+ *
4
+ * Matrices use the canvas `transform(a, b, c, d, e, f)` column-vector
5
+ * convention:
6
+ *
7
+ * | a c e | | x' | | a·x + c·y + e |
8
+ * | b d f | × | y' | = | b·x + d·y + f |
9
+ * | 0 0 1 | | 1 | | 1 |
10
+ *
11
+ * `TransformEffect` carries the same tuple order, so effects and bounds can
12
+ * share these helpers. Composition follows the canvas post-multiply rule:
13
+ * `multiplyMatrix(ancestor, own)` equals `ancestor` followed by `own`.
14
+ */
15
+ import { Bounds } from '../css/layout/bounds';
16
+ import type { Matrix } from '../css/property-descriptors/transform';
17
+ export declare const IDENTITY_MATRIX: Matrix;
18
+ export declare const isIdentityMatrix: (m: Matrix) => boolean;
19
+ /**
20
+ * Compose two transforms: the result applies `a` first, then `b`
21
+ * (canvas post-multiply, matching successive `ctx.transform` calls).
22
+ */
23
+ export declare const multiplyMatrix: (a: Matrix, b: Matrix) => Matrix;
24
+ /** Apply a matrix to a point, returning the mapped [x, y]. */
25
+ export declare const applyMatrixToPoint: (m: Matrix, x: number, y: number) => [number, number];
26
+ /**
27
+ * Map a layout-space Bounds through the matrix and return the axis-aligned
28
+ * bounding box of the four transformed corners. Non-uniform scales and
29
+ * rotations grow the box; this is intentionally conservative.
30
+ */
31
+ export declare const transformBoundsToAabb: (m: Matrix, bounds: Bounds) => Bounds;
32
+ /**
33
+ * Invert an affine matrix. Returns null for singular matrices
34
+ * (zero-scale transforms have no meaningful inverse).
35
+ */
36
+ export declare const invertMatrix: (m: Matrix) => Matrix | null;
37
+ /**
38
+ * Map a visual-space viewport back into the layout space of `m`'s inputs.
39
+ * Used where a crop window (visual coordinates) must select content measured
40
+ * in layout coordinates: the conservative answer is the AABB of the inverse
41
+ * viewport corners.
42
+ */
43
+ export declare const inverseTransformBoundsToAabb: (m: Matrix, bounds: Bounds) => Bounds | null;
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "default": "./dist/html2canvas-pro.esm.js"
20
20
  }
21
21
  },
22
- "version": "2.6.0",
22
+ "version": "2.6.2",
23
23
  "author": {
24
24
  "name": "yorickshan",
25
25
  "email": "yorickshan@gmail.com",
@@ -39,44 +39,44 @@
39
39
  "url": "https://github.com/yorickshan/html2canvas-pro"
40
40
  },
41
41
  "devDependencies": {
42
- "@commitlint/cli": "^21.2.3",
43
- "@commitlint/config-conventional": "^21.2.3",
42
+ "@commitlint/cli": "^21.2.4",
43
+ "@commitlint/config-conventional": "^21.2.4",
44
44
  "@types/express": "^5.0.6",
45
- "@types/node": "^24.0.0",
45
+ "@types/node": "^24.19.2",
46
46
  "@types/pngjs": "^6.0.5",
47
47
  "@types/serve-index": "^1.9.4",
48
- "@typescript-eslint/eslint-plugin": "^8.71.2",
49
- "@typescript-eslint/parser": "^8.71.2",
48
+ "@typescript-eslint/eslint-plugin": "^8.72.1",
49
+ "@typescript-eslint/parser": "^8.72.1",
50
50
  "@typescript/native": "npm:typescript@^7.0.2",
51
- "@vitest/coverage-v8": "^5.0.4",
52
- "axios": "^1.20.0",
53
- "body-parser": "^2.3.0",
51
+ "@vitest/coverage-v8": "^5.1.0",
52
+ "axios": "^1.21.0",
53
+ "body-parser": "^2.4.0",
54
54
  "conventional-changelog": "^8.1.3",
55
55
  "conventional-changelog-conventionalcommits": "^10.4.1",
56
56
  "cors": "^2.8.6",
57
- "eslint": "^10.12.0",
57
+ "eslint": "^10.14.0",
58
58
  "eslint-config-prettier": "^10.1.8",
59
- "eslint-plugin-prettier": "^5.5.6",
59
+ "eslint-plugin-prettier": "^5.5.7",
60
60
  "express": "^5.3.0",
61
- "express-rate-limit": "^8.7.1",
61
+ "express-rate-limit": "^8.7.3",
62
62
  "glob": "^13.0.6",
63
63
  "husky": "^9.1.7",
64
- "jsdom": "^30.1.2",
64
+ "jsdom": "^30.1.3",
65
65
  "lint-staged": "^17.6.0",
66
66
  "pixelmatch": "^8.0.0",
67
67
  "playwright": "1.64.0",
68
68
  "pngjs": "^7.0.0",
69
69
  "prettier": "^3.9.10",
70
- "rimraf": "^6.1.3",
71
- "rolldown": "^1.2.13",
70
+ "rimraf": "^6.1.4",
71
+ "rolldown": "^1.2.16",
72
72
  "serve-index": "^1.9.2",
73
73
  "slash": "5.1.0",
74
74
  "tsx": "^4.23.15",
75
75
  "typedoc": "^0.28.20",
76
76
  "typescript": "npm:@typescript/typescript6@^6.0.2",
77
- "vite": "^8.3.4",
78
- "vitepress": "^2.0.0-alpha.20",
79
- "vitest": "^5.0.4",
77
+ "vite": "^8.3.5",
78
+ "vitepress": "2.0.0-alpha.21",
79
+ "vitest": "^5.1.0",
80
80
  "yargs": "^18.2.0"
81
81
  },
82
82
  "lint-staged": {
@@ -99,7 +99,7 @@
99
99
  "unittest:coverage": "vitest run --coverage",
100
100
  "bench:css-parse": "tsx scripts/benchmarks/css-parse.bench.ts",
101
101
  "bench:render": "node scripts/benchmarks/render.bench.mjs",
102
- "reftests-diff": "mkdir -p tmp/snapshots && vitest run --config vitest.reftest.config.ts",
102
+ "reftests-diff": "vitest run --config vitest.reftest.config.ts",
103
103
  "watch": "rolldown -w",
104
104
  "watch:unittest": "vitest",
105
105
  "start": "tsx tests/server --port=8080 --cors=8081",