html2canvas-pro 2.5.2 → 2.5.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/README.md +2 -2
- package/dist/html2canvas-pro.cjs +66 -6
- package/dist/html2canvas-pro.cjs.map +1 -1
- package/dist/html2canvas-pro.esm.js +66 -6
- package/dist/html2canvas-pro.esm.js.map +1 -1
- package/dist/html2canvas-pro.js +66 -6
- package/dist/html2canvas-pro.js.map +1 -1
- package/dist/html2canvas-pro.min.js +6 -6
- package/dist/types/dom/iframe-mount.d.ts +19 -0
- package/package.json +18 -19
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ html2canvas-pro is a fork of [niklasvh/html2canvas](https://github.com/niklasvh/
|
|
|
38
38
|
- `conic-gradient()`, `repeating-conic-gradient()` and `repeating-radial-gradient()` — complete gradient family
|
|
39
39
|
- `text-emphasis` (CJK emphasis marks), `-webkit-box-reflect`, `image-set()`, `border-image-outset` / `border-image-width`
|
|
40
40
|
- `accent-color`, `outline`, `-webkit-text-fill-color`, `isolation`
|
|
41
|
-
- `filter` compositing for eligible layers —
|
|
41
|
+
- `filter` chain compositing for eligible layers — the full standard filter function set rendered on a dedicated surface with correct layer `opacity` (see [filter support notes](docs/filter-support.md))
|
|
42
42
|
- Faithful `box-shadow` rendering, including inset shadows, blur scaling, and shadows through transformed ancestors
|
|
43
43
|
- `image-rendering` CSS property plus `imageSmoothing` / `imageSmoothingQuality` options for pixel-perfect output
|
|
44
44
|
- Border image, counters & quotes, `direction`, `line-height`, `transform-origin`, and more — see the full [feature list](docs/features.md)
|
|
@@ -187,7 +187,7 @@ The project uses [pnpm](https://pnpm.io) (the version is pinned via the `package
|
|
|
187
187
|
corepack pnpm install
|
|
188
188
|
corepack pnpm build # tsc + Rolldown bundles (CJS/ESM/UMD)
|
|
189
189
|
corepack pnpm unittest # Vitest unit tests
|
|
190
|
-
corepack pnpm test # lint + unit tests + browser (
|
|
190
|
+
corepack pnpm test # lint + unit tests + browser reftests (Playwright)
|
|
191
191
|
corepack pnpm docs:dev # VitePress dev server
|
|
192
192
|
corepack pnpm docs:api # Generate the TypeDoc API reference
|
|
193
193
|
```
|
package/dist/html2canvas-pro.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* html2canvas-pro 2.5.
|
|
2
|
+
* html2canvas-pro 2.5.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
|
*/
|
|
@@ -8037,6 +8037,54 @@ const imageReady = (img) => {
|
|
|
8037
8037
|
const imagesReady = (document) => {
|
|
8038
8038
|
return Promise.all([].slice.call(document.images, 0).map(imageReady));
|
|
8039
8039
|
};
|
|
8040
|
+
/**
|
|
8041
|
+
* Wait until every external stylesheet link of the clone has been applied or
|
|
8042
|
+
* definitively failed, or the readiness budget expires.
|
|
8043
|
+
*
|
|
8044
|
+
* The clone is mounted by adoptNode() into an already-closed document
|
|
8045
|
+
* (close() must precede adoptNode for Chrome uppercase class-name matching,
|
|
8046
|
+
* see the mount sequence below), so the cloned <link rel="stylesheet">
|
|
8047
|
+
* elements are inserted after the document finished loading: they neither
|
|
8048
|
+
* delay the iframe load event nor change readyState, and the iframeLoader
|
|
8049
|
+
* readiness poll cannot observe them. On a cold cache the first capture
|
|
8050
|
+
* could then measure the clone before its external CSS arrived and render
|
|
8051
|
+
* an unstyled layout (issue #245). Inline <style> clones apply
|
|
8052
|
+
* synchronously and document-level adopted stylesheets are serialised at
|
|
8053
|
+
* clone time, so neither needs this wait.
|
|
8054
|
+
* @internal – exported for testing only.
|
|
8055
|
+
*/
|
|
8056
|
+
const stylesheetsReady = (documentClone, logger, signal) => {
|
|
8057
|
+
if (signal?.aborted) return Promise.resolve();
|
|
8058
|
+
const pending = [];
|
|
8059
|
+
const links = documentClone.getElementsByTagName("link");
|
|
8060
|
+
for (let i = 0; i < links.length; i++) {
|
|
8061
|
+
const link = links[i];
|
|
8062
|
+
if (!link || link.sheet) continue;
|
|
8063
|
+
const rel = (link.getAttribute("rel") ?? "").trim().toLowerCase().split(/\s+/);
|
|
8064
|
+
if (!rel.includes("stylesheet") || rel.includes("alternate") || link.disabled || !link.getAttribute("href")) continue;
|
|
8065
|
+
pending.push(new Promise((resolve) => {
|
|
8066
|
+
let timer;
|
|
8067
|
+
const settle = () => {
|
|
8068
|
+
link.removeEventListener("load", settle);
|
|
8069
|
+
link.removeEventListener("error", settle);
|
|
8070
|
+
signal?.removeEventListener("abort", onAbort);
|
|
8071
|
+
clearTimeout(timer);
|
|
8072
|
+
resolve();
|
|
8073
|
+
};
|
|
8074
|
+
const onAbort = () => settle();
|
|
8075
|
+
timer = setTimeout(() => {
|
|
8076
|
+
logger?.warn(`Stylesheet ${link.href} did not finish loading within ${CLONE_READY_TIMEOUT_MS / 1e3}s; continuing with a possibly unstyled clone`);
|
|
8077
|
+
settle();
|
|
8078
|
+
}, CLONE_READY_TIMEOUT_MS);
|
|
8079
|
+
link.addEventListener("load", settle);
|
|
8080
|
+
link.addEventListener("error", settle);
|
|
8081
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8082
|
+
if (link.sheet) settle();
|
|
8083
|
+
}));
|
|
8084
|
+
}
|
|
8085
|
+
if (pending.length === 0) return Promise.resolve();
|
|
8086
|
+
return Promise.all(pending).then(() => void 0);
|
|
8087
|
+
};
|
|
8040
8088
|
const iframeLoader = (iframe, logger, signal) => {
|
|
8041
8089
|
return new Promise((resolve, reject) => {
|
|
8042
8090
|
const cloneWindow = iframe.contentWindow;
|
|
@@ -8079,6 +8127,15 @@ const iframeLoader = (iframe, logger, signal) => {
|
|
|
8079
8127
|
});
|
|
8080
8128
|
};
|
|
8081
8129
|
/**
|
|
8130
|
+
* Reduce a doctype component to characters that are inert inside a
|
|
8131
|
+
* <!DOCTYPE ...> declaration. Doctype content is parsed as a declaration
|
|
8132
|
+
* token, never as HTML markup, but the fields originate from the captured
|
|
8133
|
+
* document — stripping markup delimiters and control characters here makes
|
|
8134
|
+
* that property locally verifiable (and keeps the sanitizer guarantee even
|
|
8135
|
+
* if a future serializer ever embeds the result elsewhere).
|
|
8136
|
+
*/
|
|
8137
|
+
const sanitizeDoctypePart = (value) => value.replace(/[<>&"'`\\]|[\u0000-\u001f\u007f]/g, "");
|
|
8138
|
+
/**
|
|
8082
8139
|
* Serialise a document type declaration to an HTML string.
|
|
8083
8140
|
* @internal – exported for testing only, not part of the public API.
|
|
8084
8141
|
*/
|
|
@@ -8086,12 +8143,12 @@ const serializeDoctype = (doctype) => {
|
|
|
8086
8143
|
let str = "";
|
|
8087
8144
|
if (doctype) {
|
|
8088
8145
|
str += "<!DOCTYPE ";
|
|
8089
|
-
if (doctype.name) str += doctype.name;
|
|
8090
|
-
if (doctype.internalSubset) str += " " + doctype.internalSubset
|
|
8146
|
+
if (doctype.name) str += sanitizeDoctypePart(doctype.name);
|
|
8147
|
+
if (doctype.internalSubset) str += " " + sanitizeDoctypePart(doctype.internalSubset);
|
|
8091
8148
|
if (doctype.publicId) {
|
|
8092
|
-
str += " PUBLIC \"" + doctype.publicId
|
|
8093
|
-
if (doctype.systemId) str += " \"" + doctype.systemId
|
|
8094
|
-
} else if (doctype.systemId) str += " SYSTEM \"" + doctype.systemId
|
|
8149
|
+
str += " PUBLIC \"" + sanitizeDoctypePart(doctype.publicId) + "\"";
|
|
8150
|
+
if (doctype.systemId) str += " \"" + sanitizeDoctypePart(doctype.systemId) + "\"";
|
|
8151
|
+
} else if (doctype.systemId) str += " SYSTEM \"" + sanitizeDoctypePart(doctype.systemId) + "\"";
|
|
8095
8152
|
str += ">";
|
|
8096
8153
|
}
|
|
8097
8154
|
return str;
|
|
@@ -8127,6 +8184,8 @@ const mountCloneInIFrame = (context, ownerDocument, windowSize, mount) => {
|
|
|
8127
8184
|
const referenceElement = mount.clonedReferenceElement;
|
|
8128
8185
|
if (typeof referenceElement === "undefined") throw new Error(`Error finding the ${mount.referenceElementName} in the cloned document`);
|
|
8129
8186
|
throwIfAborted(mount.signal);
|
|
8187
|
+
await stylesheetWait;
|
|
8188
|
+
throwIfAborted(mount.signal);
|
|
8130
8189
|
if (documentClone.fonts && documentClone.fonts.ready) await documentClone.fonts.ready;
|
|
8131
8190
|
throwIfAborted(mount.signal);
|
|
8132
8191
|
if (/(AppleWebKit)/g.test(navigator.userAgent)) {
|
|
@@ -8177,6 +8236,7 @@ const mountCloneInIFrame = (context, ownerDocument, windowSize, mount) => {
|
|
|
8177
8236
|
const adoptedNode = documentClone.adoptNode(mount.documentElement);
|
|
8178
8237
|
addBase(adoptedNode, baseUri);
|
|
8179
8238
|
documentClone.replaceChild(adoptedNode, documentClone.documentElement);
|
|
8239
|
+
const stylesheetWait = stylesheetsReady(documentClone, context.logger, mount.signal);
|
|
8180
8240
|
return iframeLoad;
|
|
8181
8241
|
};
|
|
8182
8242
|
//#endregion
|