html2canvas-pro 2.2.2 → 2.2.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/dist/html2canvas-pro.esm.js +120 -13
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +120 -13
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +3 -3
- package/dist/lib/css/index.js +9 -4
- package/dist/lib/css/property-descriptors/background-image.js +1 -0
- package/dist/lib/css/property-descriptors/border-image-source.js +1 -0
- package/dist/lib/css/property-descriptors/list-style-image.js +1 -0
- package/dist/lib/render/effects.js +134 -13
- package/dist/types/css/property-descriptor.d.ts +4 -0
- package/dist/types/render/effects.d.ts +35 -1
- package/package.json +1 -1
package/dist/lib/css/index.js
CHANGED
|
@@ -310,10 +310,15 @@ const parse = (context, descriptor, style) => {
|
|
|
310
310
|
valueCache = new Map();
|
|
311
311
|
parseCache.set(descriptor, valueCache);
|
|
312
312
|
}
|
|
313
|
-
// Skip caching for
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
|
|
313
|
+
// Skip caching for descriptors whose parse() has the critical side effect
|
|
314
|
+
// of calling context.cache.addImage() which must run on every render pass
|
|
315
|
+
// (different cache instances per html2canvas call). Two paths:
|
|
316
|
+
// 1. Per-descriptor skipCache flag (backgroundImage, listStyleImage,
|
|
317
|
+
// borderImageSource — all call image.parse() internally).
|
|
318
|
+
// 2. TYPE_VALUE + format 'image' — the image type descriptor itself,
|
|
319
|
+
// which calls addImage() directly.
|
|
320
|
+
const skipCache = descriptor.skipCache ||
|
|
321
|
+
(descriptor.type === 3 /* PropertyDescriptorParsingType.TYPE_VALUE */ && descriptor.format === 'image');
|
|
317
322
|
if (!skipCache) {
|
|
318
323
|
if (valueCache.size >= constants_1.PARSE_CACHE_MAX_PER_DESCRIPTOR) {
|
|
319
324
|
const oldestKey = valueCache.keys().next().value;
|
|
@@ -7,6 +7,7 @@ exports.listStyleImage = {
|
|
|
7
7
|
initialValue: 'none',
|
|
8
8
|
type: 0 /* PropertyDescriptorParsingType.VALUE */,
|
|
9
9
|
prefix: false,
|
|
10
|
+
skipCache: true,
|
|
10
11
|
parse: (context, token) => {
|
|
11
12
|
if (token.type === 20 /* TokenType.IDENT_TOKEN */ && token.value === 'none') {
|
|
12
13
|
return null;
|
|
@@ -78,21 +78,142 @@ class FilterEffect {
|
|
|
78
78
|
constructor(filterString) {
|
|
79
79
|
this.type = 5 /* EffectType.FILTER */;
|
|
80
80
|
this.target = 2 /* EffectTarget.BACKGROUND_BORDERS */ | 4 /* EffectTarget.CONTENT */;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
const parsed = FilterEffect.parseDropShadow(filterString);
|
|
82
|
+
this.shadow = parsed.shadow;
|
|
83
|
+
this.safeFilterString = parsed.safeFilterString;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse a CSS filter string, extracting drop-shadow() parameters for
|
|
87
|
+
* rendering via ctx.shadow* (which avoids canvas taint), and producing
|
|
88
|
+
* a safe filter string with all drop-shadow() calls removed.
|
|
89
|
+
*
|
|
90
|
+
* Uses paren-depth tracking to correctly handle nested function
|
|
91
|
+
* arguments — e.g. rgba() / hsla() inside drop-shadow() — which
|
|
92
|
+
* the previous regex-based approach ([^)]+) could not handle.
|
|
93
|
+
*
|
|
94
|
+
* Per CSS spec the shadow value is: <color>? && <length>{2,3}
|
|
95
|
+
* (components can appear in any order).
|
|
96
|
+
*/
|
|
97
|
+
static parseDropShadow(filterString) {
|
|
98
|
+
if (!filterString) {
|
|
99
|
+
return { safeFilterString: '' };
|
|
100
|
+
}
|
|
101
|
+
const matches = FilterEffect.findDropShadows(filterString);
|
|
102
|
+
if (matches.length === 0) {
|
|
103
|
+
return { safeFilterString: filterString };
|
|
104
|
+
}
|
|
105
|
+
// Parse the first drop-shadow body to extract shadow params
|
|
106
|
+
const shadow = FilterEffect.parseDropShadowBody(matches[0].body);
|
|
107
|
+
// Build safe filter string by removing ALL drop-shadow() occurrences
|
|
108
|
+
let result = '';
|
|
109
|
+
let lastEnd = 0;
|
|
110
|
+
for (const m of matches) {
|
|
111
|
+
result += filterString.slice(lastEnd, m.start);
|
|
112
|
+
lastEnd = m.end;
|
|
92
113
|
}
|
|
93
|
-
|
|
94
|
-
|
|
114
|
+
result += filterString.slice(lastEnd);
|
|
115
|
+
return {
|
|
116
|
+
shadow,
|
|
117
|
+
safeFilterString: result.replace(/\s+/g, ' ').trim()
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Find all drop-shadow() function calls in a CSS filter string.
|
|
122
|
+
* Uses paren-depth tracking to correctly skip over nested
|
|
123
|
+
* parentheses (e.g. rgba(0, 0, 0, 0.15)).
|
|
124
|
+
*/
|
|
125
|
+
static findDropShadows(str) {
|
|
126
|
+
const results = [];
|
|
127
|
+
const re = /drop-shadow\(/gi;
|
|
128
|
+
let m;
|
|
129
|
+
while ((m = re.exec(str)) !== null) {
|
|
130
|
+
const openParen = m.index + 'drop-shadow('.length - 1; // position of '('
|
|
131
|
+
const start = m.index;
|
|
132
|
+
let depth = 1;
|
|
133
|
+
let pos = openParen + 1;
|
|
134
|
+
while (pos < str.length && depth > 0) {
|
|
135
|
+
const ch = str[pos];
|
|
136
|
+
if (ch === '(')
|
|
137
|
+
depth++;
|
|
138
|
+
else if (ch === ')')
|
|
139
|
+
depth--;
|
|
140
|
+
pos++;
|
|
141
|
+
}
|
|
142
|
+
if (depth !== 0)
|
|
143
|
+
break; // malformed — stop scanning
|
|
144
|
+
const body = str.slice(openParen + 1, pos - 1);
|
|
145
|
+
results.push({ body, start, end: pos });
|
|
146
|
+
}
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Parse the body of a single drop-shadow() function.
|
|
151
|
+
* Body format (order-independent): <offsetX> <offsetY> [<blur>] [<color>?]
|
|
152
|
+
*/
|
|
153
|
+
static parseDropShadowBody(body) {
|
|
154
|
+
const tokens = FilterEffect.tokenizeFilterArgs(body.trim());
|
|
155
|
+
const lengths = [];
|
|
156
|
+
let color;
|
|
157
|
+
for (const token of tokens) {
|
|
158
|
+
if (FilterEffect.isCSSLength(token)) {
|
|
159
|
+
lengths.push(parseFloat(token));
|
|
160
|
+
}
|
|
161
|
+
else if (token) {
|
|
162
|
+
color = token;
|
|
163
|
+
}
|
|
95
164
|
}
|
|
165
|
+
if (lengths.length < 2 || lengths.length > 3) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
offsetX: lengths[0],
|
|
170
|
+
offsetY: lengths[1],
|
|
171
|
+
blur: lengths[2] ?? 0,
|
|
172
|
+
color: color ?? 'rgba(0,0,0,1)'
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Split whitespace-separated tokens while keeping parenthesised
|
|
177
|
+
* expressions intact.
|
|
178
|
+
*
|
|
179
|
+
* e.g. "rgba(0, 0, 0, 0.15) 0px 1px 2px"
|
|
180
|
+
* → ["rgba(0, 0, 0, 0.15)", "0px", "1px", "2px"]
|
|
181
|
+
*/
|
|
182
|
+
static tokenizeFilterArgs(str) {
|
|
183
|
+
const tokens = [];
|
|
184
|
+
let current = '';
|
|
185
|
+
let depth = 0;
|
|
186
|
+
for (let i = 0; i < str.length; i++) {
|
|
187
|
+
const ch = str[i];
|
|
188
|
+
if (ch === '(') {
|
|
189
|
+
depth++;
|
|
190
|
+
current += ch;
|
|
191
|
+
}
|
|
192
|
+
else if (ch === ')') {
|
|
193
|
+
depth--;
|
|
194
|
+
current += ch;
|
|
195
|
+
}
|
|
196
|
+
else if (/\s/.test(ch)) {
|
|
197
|
+
if (depth > 0) {
|
|
198
|
+
current += ch;
|
|
199
|
+
}
|
|
200
|
+
else if (current) {
|
|
201
|
+
tokens.push(current);
|
|
202
|
+
current = '';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
current += ch;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (current) {
|
|
210
|
+
tokens.push(current);
|
|
211
|
+
}
|
|
212
|
+
return tokens;
|
|
213
|
+
}
|
|
214
|
+
/** True when token looks like a CSS length: number with optional unit. */
|
|
215
|
+
static isCSSLength(token) {
|
|
216
|
+
return /^-?[\d.]+(px|em|rem|pt|cm|mm|in|pc|ex|ch|vw|vh|vmin|vmax|%)?$/i.test(token);
|
|
96
217
|
}
|
|
97
218
|
}
|
|
98
219
|
exports.FilterEffect = FilterEffect;
|
|
@@ -13,6 +13,10 @@ export interface IPropertyDescriptor {
|
|
|
13
13
|
type: PropertyDescriptorParsingType;
|
|
14
14
|
initialValue: string;
|
|
15
15
|
prefix: boolean;
|
|
16
|
+
/** When true, the parse result is never stored in parseCache.
|
|
17
|
+
* Set this for descriptors whose parse() has side effects (e.g.,
|
|
18
|
+
* calling context.cache.addImage) that must run on every render pass. */
|
|
19
|
+
skipCache?: boolean;
|
|
16
20
|
}
|
|
17
21
|
export interface IPropertyIdentValueDescriptor<T> extends IPropertyDescriptor {
|
|
18
22
|
type: PropertyDescriptorParsingType.IDENT_VALUE;
|
|
@@ -61,7 +61,7 @@ export declare class FilterEffect implements IElementEffect {
|
|
|
61
61
|
readonly target: number;
|
|
62
62
|
/** CSS filter string with drop-shadow() stripped (safe for ctx.filter). */
|
|
63
63
|
readonly safeFilterString: string;
|
|
64
|
-
/** Shadow params for drop-shadow(), if present. */
|
|
64
|
+
/** Shadow params for drop-shadow(), if present and successfully parsed. */
|
|
65
65
|
readonly shadow?: {
|
|
66
66
|
offsetX: number;
|
|
67
67
|
offsetY: number;
|
|
@@ -69,6 +69,40 @@ export declare class FilterEffect implements IElementEffect {
|
|
|
69
69
|
color: string;
|
|
70
70
|
};
|
|
71
71
|
constructor(filterString: string);
|
|
72
|
+
/**
|
|
73
|
+
* Parse a CSS filter string, extracting drop-shadow() parameters for
|
|
74
|
+
* rendering via ctx.shadow* (which avoids canvas taint), and producing
|
|
75
|
+
* a safe filter string with all drop-shadow() calls removed.
|
|
76
|
+
*
|
|
77
|
+
* Uses paren-depth tracking to correctly handle nested function
|
|
78
|
+
* arguments — e.g. rgba() / hsla() inside drop-shadow() — which
|
|
79
|
+
* the previous regex-based approach ([^)]+) could not handle.
|
|
80
|
+
*
|
|
81
|
+
* Per CSS spec the shadow value is: <color>? && <length>{2,3}
|
|
82
|
+
* (components can appear in any order).
|
|
83
|
+
*/
|
|
84
|
+
private static parseDropShadow;
|
|
85
|
+
/**
|
|
86
|
+
* Find all drop-shadow() function calls in a CSS filter string.
|
|
87
|
+
* Uses paren-depth tracking to correctly skip over nested
|
|
88
|
+
* parentheses (e.g. rgba(0, 0, 0, 0.15)).
|
|
89
|
+
*/
|
|
90
|
+
private static findDropShadows;
|
|
91
|
+
/**
|
|
92
|
+
* Parse the body of a single drop-shadow() function.
|
|
93
|
+
* Body format (order-independent): <offsetX> <offsetY> [<blur>] [<color>?]
|
|
94
|
+
*/
|
|
95
|
+
private static parseDropShadowBody;
|
|
96
|
+
/**
|
|
97
|
+
* Split whitespace-separated tokens while keeping parenthesised
|
|
98
|
+
* expressions intact.
|
|
99
|
+
*
|
|
100
|
+
* e.g. "rgba(0, 0, 0, 0.15) 0px 1px 2px"
|
|
101
|
+
* → ["rgba(0, 0, 0, 0.15)", "0px", "1px", "2px"]
|
|
102
|
+
*/
|
|
103
|
+
private static tokenizeFilterArgs;
|
|
104
|
+
/** True when token looks like a CSS length: number with optional unit. */
|
|
105
|
+
private static isCSSLength;
|
|
72
106
|
}
|
|
73
107
|
export declare const isTransformEffect: (effect: IElementEffect) => effect is TransformEffect;
|
|
74
108
|
export declare const isClipEffect: (effect: IElementEffect) => effect is ClipEffect;
|