html2canvas-pro 2.1.0 → 2.1.1
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 +21 -7
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +21 -7
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +3 -3
- package/dist/lib/core/cache-storage.js +2 -2
- package/dist/lib/core/features.js +2 -2
- package/dist/lib/render/canvas/background-renderer.js +6 -0
- package/dist/lib/render/canvas/canvas-renderer.js +5 -1
- package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
- package/package.json +3 -11
- package/dist/lib/invariant.js +0 -9
- package/dist/types/invariant.d.ts +0 -1
- package/src/__tests__/index.ts +0 -99
- package/src/config.ts +0 -107
- package/src/core/__mocks__/cache-storage.ts +0 -1
- package/src/core/__mocks__/context.ts +0 -19
- package/src/core/__mocks__/features.ts +0 -8
- package/src/core/__mocks__/logger.ts +0 -17
- package/src/core/__tests__/cache-storage.test.ts +0 -205
- package/src/core/__tests__/cache-storage.ts +0 -278
- package/src/core/__tests__/logger.ts +0 -29
- package/src/core/__tests__/validator.ts +0 -359
- package/src/core/bitwise.ts +0 -1
- package/src/core/cache-storage.ts +0 -315
- package/src/core/context.ts +0 -31
- package/src/core/debugger.ts +0 -32
- package/src/core/features.ts +0 -222
- package/src/core/logger.ts +0 -64
- package/src/core/origin-checker.ts +0 -57
- package/src/core/performance-monitor.ts +0 -241
- package/src/core/render-element.ts +0 -272
- package/src/core/util.ts +0 -1
- package/src/core/validator.ts +0 -593
- package/src/css/index.ts +0 -427
- package/src/css/layout/__mocks__/bounds.ts +0 -6
- package/src/css/layout/bounds.ts +0 -79
- package/src/css/layout/text.ts +0 -161
- package/src/css/property-descriptor.ts +0 -49
- package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
- package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
- package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
- package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
- package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
- package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
- package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
- package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
- package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
- package/src/css/property-descriptors/background-clip.ts +0 -30
- package/src/css/property-descriptors/background-color.ts +0 -9
- package/src/css/property-descriptors/background-image.ts +0 -27
- package/src/css/property-descriptors/background-origin.ts +0 -31
- package/src/css/property-descriptors/background-position.ts +0 -38
- package/src/css/property-descriptors/background-repeat.ts +0 -44
- package/src/css/property-descriptors/background-size.ts +0 -27
- package/src/css/property-descriptors/border-color.ts +0 -13
- package/src/css/property-descriptors/border-radius.ts +0 -19
- package/src/css/property-descriptors/border-style.ts +0 -34
- package/src/css/property-descriptors/border-width.ts +0 -20
- package/src/css/property-descriptors/box-shadow.ts +0 -60
- package/src/css/property-descriptors/clip-path.ts +0 -271
- package/src/css/property-descriptors/color.ts +0 -9
- package/src/css/property-descriptors/content.ts +0 -26
- package/src/css/property-descriptors/counter-increment.ts +0 -43
- package/src/css/property-descriptors/counter-reset.ts +0 -36
- package/src/css/property-descriptors/direction.ts +0 -23
- package/src/css/property-descriptors/display.ts +0 -117
- package/src/css/property-descriptors/duration.ts +0 -14
- package/src/css/property-descriptors/float.ts +0 -29
- package/src/css/property-descriptors/font-family.ts +0 -38
- package/src/css/property-descriptors/font-size.ts +0 -9
- package/src/css/property-descriptors/font-style.ts +0 -25
- package/src/css/property-descriptors/font-variant.ts +0 -12
- package/src/css/property-descriptors/font-weight.ts +0 -26
- package/src/css/property-descriptors/image-rendering.ts +0 -33
- package/src/css/property-descriptors/letter-spacing.ts +0 -25
- package/src/css/property-descriptors/line-break.ts +0 -22
- package/src/css/property-descriptors/line-height.ts +0 -22
- package/src/css/property-descriptors/list-style-image.ts +0 -19
- package/src/css/property-descriptors/list-style-position.ts +0 -22
- package/src/css/property-descriptors/list-style-type.ts +0 -179
- package/src/css/property-descriptors/margin.ts +0 -13
- package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
- package/src/css/property-descriptors/object-fit.ts +0 -39
- package/src/css/property-descriptors/opacity.ts +0 -15
- package/src/css/property-descriptors/overflow-wrap.ts +0 -22
- package/src/css/property-descriptors/overflow.ts +0 -34
- package/src/css/property-descriptors/padding.ts +0 -14
- package/src/css/property-descriptors/paint-order.ts +0 -42
- package/src/css/property-descriptors/position.ts +0 -30
- package/src/css/property-descriptors/quotes.ts +0 -57
- package/src/css/property-descriptors/rotate.ts +0 -34
- package/src/css/property-descriptors/text-align.ts +0 -26
- package/src/css/property-descriptors/text-decoration-color.ts +0 -9
- package/src/css/property-descriptors/text-decoration-line.ts +0 -38
- package/src/css/property-descriptors/text-decoration-style.ts +0 -32
- package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
- package/src/css/property-descriptors/text-overflow.ts +0 -23
- package/src/css/property-descriptors/text-shadow.ts +0 -52
- package/src/css/property-descriptors/text-transform.ts +0 -27
- package/src/css/property-descriptors/text-underline-offset.ts +0 -27
- package/src/css/property-descriptors/transform-origin.ts +0 -29
- package/src/css/property-descriptors/transform.ts +0 -74
- package/src/css/property-descriptors/visibility.ts +0 -25
- package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
- package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
- package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
- package/src/css/property-descriptors/word-break.ts +0 -25
- package/src/css/property-descriptors/writing-mode.ts +0 -37
- package/src/css/property-descriptors/z-index.ts +0 -27
- package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
- package/src/css/syntax/parser.ts +0 -188
- package/src/css/syntax/tokenizer.ts +0 -822
- package/src/css/type-descriptor.ts +0 -7
- package/src/css/types/__tests__/color-tests.ts +0 -147
- package/src/css/types/__tests__/image-tests.ts +0 -239
- package/src/css/types/angle.ts +0 -86
- package/src/css/types/color-math.ts +0 -22
- package/src/css/types/color-spaces/a98.ts +0 -86
- package/src/css/types/color-spaces/p3.ts +0 -92
- package/src/css/types/color-spaces/pro-photo.ts +0 -87
- package/src/css/types/color-spaces/rec2020.ts +0 -90
- package/src/css/types/color-spaces/srgb.ts +0 -87
- package/src/css/types/color-utilities.ts +0 -452
- package/src/css/types/color.ts +0 -485
- package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
- package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
- package/src/css/types/functions/-webkit-gradient.ts +0 -69
- package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
- package/src/css/types/functions/counter.ts +0 -511
- package/src/css/types/functions/gradient.ts +0 -206
- package/src/css/types/functions/linear-gradient.ts +0 -28
- package/src/css/types/functions/radial-gradient.ts +0 -101
- package/src/css/types/image.ts +0 -120
- package/src/css/types/index.ts +0 -1
- package/src/css/types/length-percentage.ts +0 -137
- package/src/css/types/length.ts +0 -7
- package/src/css/types/time.ts +0 -20
- package/src/dom/__mocks__/document-cloner.ts +0 -22
- package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
- package/src/dom/__tests__/element-container.test.ts +0 -129
- package/src/dom/document-cloner.ts +0 -929
- package/src/dom/dom-normalizer.ts +0 -133
- package/src/dom/element-container.ts +0 -75
- package/src/dom/elements/li-element-container.ts +0 -10
- package/src/dom/elements/ol-element-container.ts +0 -12
- package/src/dom/elements/select-element-container.ts +0 -10
- package/src/dom/elements/textarea-element-container.ts +0 -9
- package/src/dom/node-parser.ts +0 -177
- package/src/dom/node-type-guards.ts +0 -70
- package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
- package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
- package/src/dom/replaced-elements/image-element-container.ts +0 -16
- package/src/dom/replaced-elements/index.ts +0 -5
- package/src/dom/replaced-elements/input-element-container.ts +0 -105
- package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
- package/src/dom/replaced-elements/svg-element-container.ts +0 -23
- package/src/dom/text-container.ts +0 -42
- package/src/global.d.ts +0 -19
- package/src/index.ts +0 -82
- package/src/invariant.ts +0 -5
- package/src/options.ts +0 -55
- package/src/render/__tests__/object-fit.test.ts +0 -85
- package/src/render/background.ts +0 -298
- package/src/render/bezier-curve.ts +0 -47
- package/src/render/border.ts +0 -165
- package/src/render/bound-curves.ts +0 -388
- package/src/render/box-sizing.ts +0 -31
- package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
- package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
- package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
- package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
- package/src/render/canvas/background-renderer.ts +0 -271
- package/src/render/canvas/border-renderer.ts +0 -224
- package/src/render/canvas/canvas-path.ts +0 -31
- package/src/render/canvas/canvas-renderer.ts +0 -641
- package/src/render/canvas/effects-renderer.ts +0 -130
- package/src/render/canvas/foreignobject-renderer.ts +0 -53
- package/src/render/canvas/text-renderer.ts +0 -700
- package/src/render/effects.ts +0 -75
- package/src/render/font-metrics.ts +0 -72
- package/src/render/object-fit.ts +0 -100
- package/src/render/path.ts +0 -37
- package/src/render/renderer-interface.ts +0 -28
- package/src/render/stacking-context.ts +0 -386
- package/src/render/vector.ts +0 -19
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
import { FEATURES } from './features';
|
|
2
|
-
import { Context } from './context';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* CacheStorage (Deprecated static methods)
|
|
6
|
-
*
|
|
7
|
-
* @deprecated The static methods of CacheStorage are deprecated.
|
|
8
|
-
* Use OriginChecker class instead for instance-based origin checking.
|
|
9
|
-
*
|
|
10
|
-
* For backward compatibility, these methods remain but should not be used in new code.
|
|
11
|
-
*/
|
|
12
|
-
export class CacheStorage {
|
|
13
|
-
private static _link?: HTMLAnchorElement;
|
|
14
|
-
private static _origin = 'about:blank';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @deprecated Use OriginChecker.getOrigin() instead
|
|
18
|
-
*/
|
|
19
|
-
static getOrigin(url: string): string {
|
|
20
|
-
const link = CacheStorage._link;
|
|
21
|
-
if (!link) {
|
|
22
|
-
return 'about:blank';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
link.href = url;
|
|
26
|
-
link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
|
|
27
|
-
return link.protocol + link.hostname + link.port;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @deprecated Use OriginChecker.isSameOrigin() instead
|
|
32
|
-
*/
|
|
33
|
-
static isSameOrigin(src: string): boolean {
|
|
34
|
-
return CacheStorage.getOrigin(src) === CacheStorage._origin;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @deprecated No longer needed. OriginChecker is created per Context.
|
|
39
|
-
*/
|
|
40
|
-
static setContext(window: Window): void {
|
|
41
|
-
CacheStorage._link = window.document.createElement('a');
|
|
42
|
-
CacheStorage._origin = CacheStorage.getOrigin(window.location.href);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface ResourceOptions {
|
|
47
|
-
imageTimeout: number;
|
|
48
|
-
useCORS: boolean;
|
|
49
|
-
allowTaint: boolean;
|
|
50
|
-
proxy?: string;
|
|
51
|
-
customIsSameOrigin?: (this: void, src: string, oldFn: (src: string) => boolean) => boolean | Promise<boolean>;
|
|
52
|
-
maxCacheSize?: number; // Maximum cache size (default: 100, max: 10000)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface CacheEntry {
|
|
56
|
-
value: Promise<HTMLImageElement | HTMLCanvasElement | undefined>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export class Cache {
|
|
60
|
-
private readonly _cache: Map<string, CacheEntry> = new Map();
|
|
61
|
-
private readonly maxSize: number;
|
|
62
|
-
private readonly _pendingOperations: Map<string, Promise<void>> = new Map();
|
|
63
|
-
|
|
64
|
-
constructor(
|
|
65
|
-
private readonly context: Context,
|
|
66
|
-
private readonly _options: ResourceOptions
|
|
67
|
-
) {
|
|
68
|
-
// Default cache size: 100 items
|
|
69
|
-
this.maxSize = _options.maxCacheSize ?? 100;
|
|
70
|
-
|
|
71
|
-
if (this.maxSize < 1) {
|
|
72
|
-
throw new Error('Cache maxSize must be at least 1');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (this.maxSize > 10000) {
|
|
76
|
-
this.context.logger.warn(
|
|
77
|
-
`Cache maxSize ${this.maxSize} is very large and may cause memory issues. ` +
|
|
78
|
-
`Consider using a smaller value (recommended: 100-1000).`
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
addImage(src: string): Promise<void> {
|
|
84
|
-
// Wait for any pending operations on this key
|
|
85
|
-
const pending = this._pendingOperations.get(src);
|
|
86
|
-
if (pending) {
|
|
87
|
-
return pending;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (this.has(src)) {
|
|
91
|
-
// Move to end for LRU ordering
|
|
92
|
-
const entry = this._cache.get(src)!;
|
|
93
|
-
this._cache.delete(src);
|
|
94
|
-
this._cache.set(src, entry);
|
|
95
|
-
return Promise.resolve();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (isBlobImage(src) || isRenderable(src)) {
|
|
99
|
-
// Create a pending operation to ensure atomicity
|
|
100
|
-
const operation = this._addImageInternal(src);
|
|
101
|
-
this._pendingOperations.set(src, operation);
|
|
102
|
-
operation.finally(() => {
|
|
103
|
-
this._pendingOperations.delete(src);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
return operation;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return Promise.resolve();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private async _addImageInternal(src: string): Promise<void> {
|
|
113
|
-
const timeoutMs = this._options.imageTimeout ?? 15000;
|
|
114
|
-
const imageWithTimeout = this.withTimeout(
|
|
115
|
-
this.loadImage(src),
|
|
116
|
-
timeoutMs,
|
|
117
|
-
`Timed out (${timeoutMs}ms) loading image`
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
// Handle errors to prevent unhandled rejections
|
|
121
|
-
imageWithTimeout.catch((error) => {
|
|
122
|
-
this.context.logger.error(
|
|
123
|
-
`Failed to load image ${src}: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Store the promise with timeout in cache
|
|
128
|
-
this.set(src, imageWithTimeout);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
132
|
-
if (timeoutMs <= 0) {
|
|
133
|
-
return promise;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
137
|
-
const timeout = new Promise<never>((_, reject) => {
|
|
138
|
-
timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
return Promise.race([promise, timeout]).finally(() => {
|
|
142
|
-
if (timeoutId !== undefined) {
|
|
143
|
-
clearTimeout(timeoutId);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
match(src: string): Promise<HTMLImageElement | HTMLCanvasElement | undefined> | undefined {
|
|
149
|
-
const entry = this._cache.get(src);
|
|
150
|
-
if (entry) {
|
|
151
|
-
// Move to end for LRU ordering (O(1))
|
|
152
|
-
this._cache.delete(src);
|
|
153
|
-
this._cache.set(src, entry);
|
|
154
|
-
return entry.value;
|
|
155
|
-
}
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Set a value in cache with LRU eviction (O(1) via Map insertion order).
|
|
161
|
-
* Map preserves insertion order; delete+set on access moves items to the end.
|
|
162
|
-
* The first key in Map.keys() is always the least recently used.
|
|
163
|
-
*/
|
|
164
|
-
private set(key: string, value: Promise<HTMLImageElement | HTMLCanvasElement | undefined>): void {
|
|
165
|
-
if (this._cache.has(key)) {
|
|
166
|
-
// Update existing entry: move to end of Map
|
|
167
|
-
this._cache.delete(key);
|
|
168
|
-
} else if (this._cache.size >= this.maxSize) {
|
|
169
|
-
// Evict LRU (first key = least recently used) — O(1)
|
|
170
|
-
const lruKey = this._cache.keys().next().value;
|
|
171
|
-
if (lruKey !== undefined) {
|
|
172
|
-
this._cache.delete(lruKey);
|
|
173
|
-
this.context.logger.debug(`Cache: Evicted LRU entry: ${lruKey}`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
this._cache.set(key, { value });
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Get cache size
|
|
182
|
-
*/
|
|
183
|
-
size(): number {
|
|
184
|
-
return this._cache.size;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Get max cache size
|
|
189
|
-
*/
|
|
190
|
-
getMaxSize(): number {
|
|
191
|
-
return this.maxSize;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Clear all cache entries
|
|
196
|
-
*/
|
|
197
|
-
clear(): void {
|
|
198
|
-
this._cache.clear();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
private async loadImage(key: string): Promise<HTMLImageElement | undefined> {
|
|
202
|
-
const originChecker = this.context.originChecker;
|
|
203
|
-
const defaultIsSameOrigin = (src: string) => originChecker.isSameOrigin(src);
|
|
204
|
-
|
|
205
|
-
const isSameOrigin: boolean =
|
|
206
|
-
typeof this._options.customIsSameOrigin === 'function'
|
|
207
|
-
? await this._options.customIsSameOrigin(key, defaultIsSameOrigin)
|
|
208
|
-
: defaultIsSameOrigin(key);
|
|
209
|
-
const useCORS =
|
|
210
|
-
!isInlineImage(key) && this._options.useCORS === true && FEATURES.SUPPORT_CORS_IMAGES && !isSameOrigin;
|
|
211
|
-
const useProxy =
|
|
212
|
-
!isInlineImage(key) &&
|
|
213
|
-
!isSameOrigin &&
|
|
214
|
-
!isBlobImage(key) &&
|
|
215
|
-
typeof this._options.proxy === 'string' &&
|
|
216
|
-
FEATURES.SUPPORT_CORS_XHR &&
|
|
217
|
-
!useCORS;
|
|
218
|
-
if (
|
|
219
|
-
!isSameOrigin &&
|
|
220
|
-
this._options.allowTaint === false &&
|
|
221
|
-
!isInlineImage(key) &&
|
|
222
|
-
!isBlobImage(key) &&
|
|
223
|
-
!useProxy &&
|
|
224
|
-
!useCORS
|
|
225
|
-
) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
let src = key;
|
|
230
|
-
if (useProxy) {
|
|
231
|
-
src = await this.proxy(src);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
this.context.logger.debug(`Added image ${key.substring(0, 256)}`);
|
|
235
|
-
|
|
236
|
-
return await new Promise((resolve, reject) => {
|
|
237
|
-
const img = new Image();
|
|
238
|
-
img.onload = () => resolve(img);
|
|
239
|
-
img.onerror = reject;
|
|
240
|
-
//ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous
|
|
241
|
-
if (isInlineBase64Image(src) || useCORS) {
|
|
242
|
-
img.crossOrigin = 'anonymous';
|
|
243
|
-
}
|
|
244
|
-
img.src = src;
|
|
245
|
-
if (img.complete === true) {
|
|
246
|
-
// Inline XML images may fail to parse, throwing an Error later on
|
|
247
|
-
setTimeout(() => resolve(img), 500);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private has(key: string): boolean {
|
|
253
|
-
return this._cache.has(key);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
keys(): Promise<string[]> {
|
|
257
|
-
return Promise.resolve(Array.from(this._cache.keys()));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private proxy(src: string): Promise<string> {
|
|
261
|
-
const proxy = this._options.proxy;
|
|
262
|
-
|
|
263
|
-
if (!proxy) {
|
|
264
|
-
throw new Error('No proxy defined');
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const key = src.substring(0, 256);
|
|
268
|
-
|
|
269
|
-
return new Promise((resolve, reject) => {
|
|
270
|
-
const responseType = FEATURES.SUPPORT_RESPONSE_TYPE ? 'blob' : 'text';
|
|
271
|
-
const xhr = new XMLHttpRequest();
|
|
272
|
-
xhr.onload = () => {
|
|
273
|
-
if (xhr.status === 200) {
|
|
274
|
-
if (responseType === 'text') {
|
|
275
|
-
resolve(xhr.response);
|
|
276
|
-
} else {
|
|
277
|
-
const reader = new FileReader();
|
|
278
|
-
reader.addEventListener('load', () => resolve(reader.result as string), false);
|
|
279
|
-
reader.addEventListener('error', (e) => reject(e), false);
|
|
280
|
-
reader.readAsDataURL(xhr.response);
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
reject(`Failed to proxy resource ${key} with status code ${xhr.status}`);
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
xhr.onerror = reject;
|
|
288
|
-
const queryString = proxy.indexOf('?') > -1 ? '&' : '?';
|
|
289
|
-
xhr.open('GET', `${proxy}${queryString}url=${encodeURIComponent(src)}&responseType=${responseType}`);
|
|
290
|
-
|
|
291
|
-
if (responseType !== 'text' && xhr instanceof XMLHttpRequest) {
|
|
292
|
-
xhr.responseType = responseType;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (this._options.imageTimeout) {
|
|
296
|
-
const timeout = this._options.imageTimeout;
|
|
297
|
-
xhr.timeout = timeout;
|
|
298
|
-
xhr.ontimeout = () => reject(`Timed out (${timeout}ms) proxying ${key}`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
xhr.send();
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const INLINE_SVG = /^data:image\/svg\+xml/i;
|
|
307
|
-
const INLINE_BASE64 = /^data:image\/.*;base64,/i;
|
|
308
|
-
const INLINE_IMG = /^data:image\/.*/i;
|
|
309
|
-
|
|
310
|
-
const isRenderable = (src: string): boolean => FEATURES.SUPPORT_SVG_DRAWING || !isSVG(src);
|
|
311
|
-
const isInlineImage = (src: string): boolean => INLINE_IMG.test(src);
|
|
312
|
-
const isInlineBase64Image = (src: string): boolean => INLINE_BASE64.test(src);
|
|
313
|
-
const isBlobImage = (src: string): boolean => src.substr(0, 4) === 'blob';
|
|
314
|
-
|
|
315
|
-
const isSVG = (src: string): boolean => src.substr(-3).toLowerCase() === 'svg' || INLINE_SVG.test(src);
|
package/src/core/context.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Logger } from './logger';
|
|
2
|
-
import { Cache, ResourceOptions } from './cache-storage';
|
|
3
|
-
import { Bounds } from '../css/layout/bounds';
|
|
4
|
-
import { OriginChecker } from './origin-checker';
|
|
5
|
-
import { Html2CanvasConfig } from '../config';
|
|
6
|
-
|
|
7
|
-
export type ContextOptions = {
|
|
8
|
-
logging: boolean;
|
|
9
|
-
cache?: Cache;
|
|
10
|
-
} & ResourceOptions;
|
|
11
|
-
|
|
12
|
-
export class Context {
|
|
13
|
-
private readonly instanceName = `#${Context.instanceCount++}`;
|
|
14
|
-
readonly logger: Logger;
|
|
15
|
-
readonly cache: Cache;
|
|
16
|
-
readonly originChecker: OriginChecker;
|
|
17
|
-
readonly config: Html2CanvasConfig;
|
|
18
|
-
|
|
19
|
-
private static instanceCount = 1;
|
|
20
|
-
|
|
21
|
-
constructor(
|
|
22
|
-
options: ContextOptions,
|
|
23
|
-
public windowBounds: Bounds,
|
|
24
|
-
config: Html2CanvasConfig
|
|
25
|
-
) {
|
|
26
|
-
this.config = config;
|
|
27
|
-
this.logger = new Logger({ id: this.instanceName, enabled: options.logging });
|
|
28
|
-
this.originChecker = new OriginChecker(config.window);
|
|
29
|
-
this.cache = options.cache ?? config.cache ?? new Cache(this, options);
|
|
30
|
-
}
|
|
31
|
-
}
|
package/src/core/debugger.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const elementDebuggerAttribute = 'data-html2canvas-debug';
|
|
2
|
-
export const enum DebuggerType {
|
|
3
|
-
NONE,
|
|
4
|
-
ALL,
|
|
5
|
-
CLONE,
|
|
6
|
-
PARSE,
|
|
7
|
-
RENDER
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const getElementDebugType = (element: Element): DebuggerType => {
|
|
11
|
-
if (typeof element.getAttribute !== 'function') {
|
|
12
|
-
return DebuggerType.NONE;
|
|
13
|
-
}
|
|
14
|
-
const attribute = element.getAttribute(elementDebuggerAttribute);
|
|
15
|
-
switch (attribute) {
|
|
16
|
-
case 'all':
|
|
17
|
-
return DebuggerType.ALL;
|
|
18
|
-
case 'clone':
|
|
19
|
-
return DebuggerType.CLONE;
|
|
20
|
-
case 'parse':
|
|
21
|
-
return DebuggerType.PARSE;
|
|
22
|
-
case 'render':
|
|
23
|
-
return DebuggerType.RENDER;
|
|
24
|
-
default:
|
|
25
|
-
return DebuggerType.NONE;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const isDebugging = (element: Element, type: Omit<DebuggerType, DebuggerType.NONE>): boolean => {
|
|
30
|
-
const elementType = getElementDebugType(element);
|
|
31
|
-
return elementType === DebuggerType.ALL || type === elementType;
|
|
32
|
-
};
|
package/src/core/features.ts
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { fromCodePoint, toCodePoints } from 'css-line-break';
|
|
2
|
-
|
|
3
|
-
const testRangeBounds = (document: Document) => {
|
|
4
|
-
const TEST_HEIGHT = 123;
|
|
5
|
-
|
|
6
|
-
if (document.createRange) {
|
|
7
|
-
const range = document.createRange();
|
|
8
|
-
if (range.getBoundingClientRect) {
|
|
9
|
-
const testElement = document.createElement('boundtest');
|
|
10
|
-
testElement.style.height = `${TEST_HEIGHT}px`;
|
|
11
|
-
testElement.style.display = 'block';
|
|
12
|
-
document.body.appendChild(testElement);
|
|
13
|
-
|
|
14
|
-
range.selectNode(testElement);
|
|
15
|
-
const rangeBounds = range.getBoundingClientRect();
|
|
16
|
-
const rangeHeight = Math.round(rangeBounds.height);
|
|
17
|
-
document.body.removeChild(testElement);
|
|
18
|
-
if (rangeHeight === TEST_HEIGHT) {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return false;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const testIOSLineBreak = (document: Document) => {
|
|
28
|
-
const testElement = document.createElement('boundtest');
|
|
29
|
-
testElement.style.width = '50px';
|
|
30
|
-
testElement.style.display = 'block';
|
|
31
|
-
testElement.style.fontSize = '12px';
|
|
32
|
-
testElement.style.letterSpacing = '0px';
|
|
33
|
-
testElement.style.wordSpacing = '0px';
|
|
34
|
-
document.body.appendChild(testElement);
|
|
35
|
-
const range = document.createRange();
|
|
36
|
-
|
|
37
|
-
testElement.innerHTML = typeof ''.repeat === 'function' ? '👨'.repeat(10) : '';
|
|
38
|
-
|
|
39
|
-
const node = testElement.firstChild as Text;
|
|
40
|
-
|
|
41
|
-
const textList = toCodePoints(node.data).map((i) => fromCodePoint(i));
|
|
42
|
-
let offset = 0;
|
|
43
|
-
let prev: DOMRect = {} as DOMRect;
|
|
44
|
-
|
|
45
|
-
// ios 13 does not handle range getBoundingClientRect line changes correctly #2177
|
|
46
|
-
const supports = textList.every((text, i) => {
|
|
47
|
-
range.setStart(node, offset);
|
|
48
|
-
range.setEnd(node, offset + text.length);
|
|
49
|
-
const rect = range.getBoundingClientRect();
|
|
50
|
-
|
|
51
|
-
offset += text.length;
|
|
52
|
-
const boundAhead = rect.x > prev.x || rect.y > prev.y;
|
|
53
|
-
|
|
54
|
-
prev = rect;
|
|
55
|
-
if (i === 0) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return boundAhead;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
document.body.removeChild(testElement);
|
|
63
|
-
return supports;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const testCORS = (): boolean => typeof new Image().crossOrigin !== 'undefined';
|
|
67
|
-
|
|
68
|
-
const testResponseType = (): boolean => typeof new XMLHttpRequest().responseType === 'string';
|
|
69
|
-
|
|
70
|
-
const testSVG = (document: Document): boolean => {
|
|
71
|
-
const img = new Image();
|
|
72
|
-
const canvas = document.createElement('canvas');
|
|
73
|
-
const ctx = canvas.getContext('2d');
|
|
74
|
-
if (!ctx) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
img.src = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>`;
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
ctx.drawImage(img, 0, 0);
|
|
82
|
-
canvas.toDataURL();
|
|
83
|
-
} catch (e) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return true;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const isGreenPixel = (data: Uint8ClampedArray): boolean =>
|
|
90
|
-
data[0] === 0 && data[1] === 255 && data[2] === 0 && data[3] === 255;
|
|
91
|
-
|
|
92
|
-
const testForeignObject = (document: Document): Promise<boolean> => {
|
|
93
|
-
const canvas = document.createElement('canvas');
|
|
94
|
-
const size = 100;
|
|
95
|
-
canvas.width = size;
|
|
96
|
-
canvas.height = size;
|
|
97
|
-
const ctx = canvas.getContext('2d');
|
|
98
|
-
if (!ctx) {
|
|
99
|
-
return Promise.reject(false);
|
|
100
|
-
}
|
|
101
|
-
ctx.fillStyle = 'rgb(0, 255, 0)';
|
|
102
|
-
ctx.fillRect(0, 0, size, size);
|
|
103
|
-
|
|
104
|
-
const img = new Image();
|
|
105
|
-
const greenImageSrc = canvas.toDataURL();
|
|
106
|
-
img.src = greenImageSrc;
|
|
107
|
-
const svg = createForeignObjectSVG(size, size, 0, 0, img);
|
|
108
|
-
ctx.fillStyle = 'red';
|
|
109
|
-
ctx.fillRect(0, 0, size, size);
|
|
110
|
-
|
|
111
|
-
return loadSerializedSVG(svg)
|
|
112
|
-
.then((img: HTMLImageElement) => {
|
|
113
|
-
ctx.drawImage(img, 0, 0);
|
|
114
|
-
const data = ctx.getImageData(0, 0, size, size).data;
|
|
115
|
-
ctx.fillStyle = 'red';
|
|
116
|
-
ctx.fillRect(0, 0, size, size);
|
|
117
|
-
|
|
118
|
-
const node = document.createElement('div');
|
|
119
|
-
node.style.backgroundImage = `url(${greenImageSrc})`;
|
|
120
|
-
node.style.height = `${size}px`;
|
|
121
|
-
// Firefox 55 does not render inline <img /> tags
|
|
122
|
-
return isGreenPixel(data)
|
|
123
|
-
? loadSerializedSVG(createForeignObjectSVG(size, size, 0, 0, node))
|
|
124
|
-
: Promise.reject(false);
|
|
125
|
-
})
|
|
126
|
-
.then((img: HTMLImageElement) => {
|
|
127
|
-
ctx.drawImage(img, 0, 0);
|
|
128
|
-
// Edge does not render background-images
|
|
129
|
-
return isGreenPixel(ctx.getImageData(0, 0, size, size).data);
|
|
130
|
-
})
|
|
131
|
-
.catch(() => false);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export const createForeignObjectSVG = (
|
|
135
|
-
width: number,
|
|
136
|
-
height: number,
|
|
137
|
-
x: number,
|
|
138
|
-
y: number,
|
|
139
|
-
node: Node
|
|
140
|
-
): SVGForeignObjectElement => {
|
|
141
|
-
const xmlns = 'http://www.w3.org/2000/svg';
|
|
142
|
-
const svg = document.createElementNS(xmlns, 'svg');
|
|
143
|
-
const foreignObject = document.createElementNS(xmlns, 'foreignObject');
|
|
144
|
-
svg.setAttributeNS(null, 'width', width.toString());
|
|
145
|
-
svg.setAttributeNS(null, 'height', height.toString());
|
|
146
|
-
|
|
147
|
-
foreignObject.setAttributeNS(null, 'width', '100%');
|
|
148
|
-
foreignObject.setAttributeNS(null, 'height', '100%');
|
|
149
|
-
foreignObject.setAttributeNS(null, 'x', x.toString());
|
|
150
|
-
foreignObject.setAttributeNS(null, 'y', y.toString());
|
|
151
|
-
foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true');
|
|
152
|
-
svg.appendChild(foreignObject);
|
|
153
|
-
|
|
154
|
-
foreignObject.appendChild(node);
|
|
155
|
-
|
|
156
|
-
return svg;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export const loadSerializedSVG = (svg: Node): Promise<HTMLImageElement> => {
|
|
160
|
-
return new Promise((resolve, reject) => {
|
|
161
|
-
const img = new Image();
|
|
162
|
-
img.onload = () => resolve(img);
|
|
163
|
-
img.onerror = reject;
|
|
164
|
-
|
|
165
|
-
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(new XMLSerializer().serializeToString(svg))}`;
|
|
166
|
-
});
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
export const FEATURES = {
|
|
170
|
-
get SUPPORT_RANGE_BOUNDS(): boolean {
|
|
171
|
-
'use strict';
|
|
172
|
-
const value = testRangeBounds(document);
|
|
173
|
-
Object.defineProperty(FEATURES, 'SUPPORT_RANGE_BOUNDS', { value });
|
|
174
|
-
return value;
|
|
175
|
-
},
|
|
176
|
-
get SUPPORT_WORD_BREAKING(): boolean {
|
|
177
|
-
'use strict';
|
|
178
|
-
const value = FEATURES.SUPPORT_RANGE_BOUNDS && testIOSLineBreak(document);
|
|
179
|
-
Object.defineProperty(FEATURES, 'SUPPORT_WORD_BREAKING', { value });
|
|
180
|
-
return value;
|
|
181
|
-
},
|
|
182
|
-
get SUPPORT_SVG_DRAWING(): boolean {
|
|
183
|
-
'use strict';
|
|
184
|
-
const value = testSVG(document);
|
|
185
|
-
Object.defineProperty(FEATURES, 'SUPPORT_SVG_DRAWING', { value });
|
|
186
|
-
return value;
|
|
187
|
-
},
|
|
188
|
-
get SUPPORT_FOREIGNOBJECT_DRAWING(): Promise<boolean> {
|
|
189
|
-
'use strict';
|
|
190
|
-
const value =
|
|
191
|
-
typeof Array.from === 'function' && typeof window.fetch === 'function'
|
|
192
|
-
? testForeignObject(document)
|
|
193
|
-
: Promise.resolve(false);
|
|
194
|
-
Object.defineProperty(FEATURES, 'SUPPORT_FOREIGNOBJECT_DRAWING', { value });
|
|
195
|
-
return value;
|
|
196
|
-
},
|
|
197
|
-
get SUPPORT_CORS_IMAGES(): boolean {
|
|
198
|
-
'use strict';
|
|
199
|
-
const value = testCORS();
|
|
200
|
-
Object.defineProperty(FEATURES, 'SUPPORT_CORS_IMAGES', { value });
|
|
201
|
-
return value;
|
|
202
|
-
},
|
|
203
|
-
get SUPPORT_RESPONSE_TYPE(): boolean {
|
|
204
|
-
'use strict';
|
|
205
|
-
const value = testResponseType();
|
|
206
|
-
Object.defineProperty(FEATURES, 'SUPPORT_RESPONSE_TYPE', { value });
|
|
207
|
-
return value;
|
|
208
|
-
},
|
|
209
|
-
get SUPPORT_CORS_XHR(): boolean {
|
|
210
|
-
'use strict';
|
|
211
|
-
const value = 'withCredentials' in new XMLHttpRequest();
|
|
212
|
-
Object.defineProperty(FEATURES, 'SUPPORT_CORS_XHR', { value });
|
|
213
|
-
return value;
|
|
214
|
-
},
|
|
215
|
-
get SUPPORT_NATIVE_TEXT_SEGMENTATION(): boolean {
|
|
216
|
-
'use strict';
|
|
217
|
-
|
|
218
|
-
const value = !!(typeof Intl !== 'undefined' && (Intl as any).Segmenter);
|
|
219
|
-
Object.defineProperty(FEATURES, 'SUPPORT_NATIVE_TEXT_SEGMENTATION', { value });
|
|
220
|
-
return value;
|
|
221
|
-
}
|
|
222
|
-
};
|
package/src/core/logger.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
export interface LoggerOptions {
|
|
2
|
-
id: string;
|
|
3
|
-
enabled: boolean;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export class Logger {
|
|
7
|
-
static instances: { [key: string]: Logger } = {};
|
|
8
|
-
|
|
9
|
-
private readonly id: string;
|
|
10
|
-
private readonly enabled: boolean;
|
|
11
|
-
private readonly start: number;
|
|
12
|
-
|
|
13
|
-
constructor({ id, enabled }: LoggerOptions) {
|
|
14
|
-
this.id = id;
|
|
15
|
-
this.enabled = enabled;
|
|
16
|
-
this.start = Date.now();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
debug(...args: unknown[]): void {
|
|
20
|
-
if (this.enabled) {
|
|
21
|
-
// eslint-disable-next-line no-console
|
|
22
|
-
if (typeof window !== 'undefined' && window.console && typeof console.debug === 'function') {
|
|
23
|
-
// eslint-disable-next-line no-console
|
|
24
|
-
console.debug(this.id, `${this.getTime()}ms`, ...args);
|
|
25
|
-
} else {
|
|
26
|
-
this.info(...args);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getTime(): number {
|
|
32
|
-
return Date.now() - this.start;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
info(...args: unknown[]): void {
|
|
36
|
-
if (this.enabled) {
|
|
37
|
-
// eslint-disable-next-line no-console
|
|
38
|
-
if (typeof window !== 'undefined' && window.console && typeof console.info === 'function') {
|
|
39
|
-
// eslint-disable-next-line no-console
|
|
40
|
-
console.info(this.id, `${this.getTime()}ms`, ...args);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
warn(...args: unknown[]): void {
|
|
46
|
-
if (this.enabled) {
|
|
47
|
-
if (typeof window !== 'undefined' && window.console && typeof console.warn === 'function') {
|
|
48
|
-
console.warn(this.id, `${this.getTime()}ms`, ...args);
|
|
49
|
-
} else {
|
|
50
|
-
this.info(...args);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
error(...args: unknown[]): void {
|
|
56
|
-
if (this.enabled) {
|
|
57
|
-
if (typeof window !== 'undefined' && window.console && typeof console.error === 'function') {
|
|
58
|
-
console.error(this.id, `${this.getTime()}ms`, ...args);
|
|
59
|
-
} else {
|
|
60
|
-
this.info(...args);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|