diff --git a/README.md b/README.md index f1449e104..208ea9923 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,25 @@ doc.text("Hello world!", 10, 10); doc.save("a4.pdf"); ``` +### HTML rendering notes + +The `doc.html(...)` API relies on `html2canvas` for DOM rendering. If your HTML uses CSS Color 4 values such as +`color(display-p3 ...)`, older `html2canvas` versions may not support them. To avoid rendering errors from inline +`style` attributes using `color(display-p3 ...)`, you can opt into a best‑effort, lossy conversion to sRGB: + +```javascript +const doc = new jsPDF(); +await doc.html(elementOrHtmlString, { + // Converts inline style attributes with color(display-p3 ...) to rgb()/rgba() before rendering + colorSpaceFallback: "srgb" +}); +``` + +Notes: + +- This fallback only processes inline `style` attributes of the provided subtree. It does not transform external stylesheets. +- The conversion is approximate and lossy by design. True Display‑P3 support depends on upstream `html2canvas`. + If you want to change the paper size, orientation, or units, you can do: ```javascript diff --git a/src/modules/html.js b/src/modules/html.js index b4e1f85af..67b88ff2e 100644 --- a/src/modules/html.js +++ b/src/modules/html.js @@ -118,6 +118,59 @@ import { globalObject } from "../libs/globalObject.js"; else return "unknown"; }; + /** + * Replace CSS Color 4 color(display-p3 ...) in inline style strings with approximate sRGB rgb()/rgba() values. + * This is a best-effort, lossy fallback to avoid crashes in html2canvas when encountering unsupported color(). + * + * Note: This does not parse external stylesheets; it only touches inline styles present on elements. + * + * @private + * @ignore + */ + function replaceDisplayP3InStyle(styleText) { + if (!styleText || typeof styleText !== "string") return styleText; + var re = /color\(\s*display-p3\s+([0-9]*\.?[0-9]+)\s+([0-9]*\.?[0-9]+)\s+([0-9]*\.?[0-9]+)(?:\s*\/\s*([0-9]*\.?[0-9]+))?\s*\)/gi; + return styleText.replace(re, function(_, r, g, b, a) { + // Approximate: treat P3 components as if they were sRGB components in [0,1] + var rr = Math.max(0, Math.min(255, Math.round(parseFloat(r) * 255))); + var gg = Math.max(0, Math.min(255, Math.round(parseFloat(g) * 255))); + var bb = Math.max(0, Math.min(255, Math.round(parseFloat(b) * 255))); + if (a != null && a !== undefined) { + var af = Math.max(0, Math.min(1, parseFloat(a))); + return "rgba(" + rr + ", " + gg + ", " + bb + ", " + af + ")"; + } + return "rgb(" + rr + ", " + gg + ", " + bb + ")"; + }); + } + + /** + * Walk a DOM subtree and apply display-p3 -> sRGB replacements on inline style attributes. + * + * @private + * @ignore + */ + function applyColorFallbackInlineStyles(root) { + if (!root || !root.querySelectorAll) return; + // Process root element itself + if (root.hasAttribute && root.hasAttribute("style")) { + var current = root.getAttribute("style"); + var replaced = replaceDisplayP3InStyle(current); + if (replaced !== current) root.setAttribute("style", replaced); + } + // Process all descendants + var all = root.querySelectorAll("[*|style], *[style]"); + for (var i = 0; i < all.length; i++) { + var el = all[i]; + var s = el.getAttribute("style"); + if (s) { + var rep = replaceDisplayP3InStyle(s); + if (rep !== s) { + el.setAttribute("style", rep); + } + } + } + } + /** * Create an HTML element with optional className, innerHTML, and style. * @@ -361,6 +414,15 @@ import { globalObject } from "../libs/globalObject.js"; className: "html2pdf__container", style: containerCSS }); + // Optionally preprocess inline styles to replace color(display-p3 ...) with rgb()/rgba() + if (this.opt && this.opt.colorSpaceFallback === "srgb") { + try { + applyColorFallbackInlineStyles(source); + } catch (e) { + // Do not block rendering on fallback failures + } + } + this.prop.container.appendChild(source); this.prop.container.firstChild.appendChild( createElement("div", { @@ -412,7 +474,25 @@ import { globalObject } from "../libs/globalObject.js"; onRendered(canvas); this.prop.canvas = canvas; - document.body.removeChild(this.prop.overlay); + if (this.prop.overlay && document.body.contains(this.prop.overlay)) { + document.body.removeChild(this.prop.overlay); + } + this.prop.overlay = null; + }) + .catch(function toCanvas_error(err) { + try { + if ( + this.prop && + this.prop.overlay && + document.body.contains(this.prop.overlay) + ) { + document.body.removeChild(this.prop.overlay); + } + this.prop.overlay = null; + } catch (e) { + // ignore cleanup errors + } + throw err; }); }; @@ -505,7 +585,25 @@ import { globalObject } from "../libs/globalObject.js"; onRendered(canvas); this.prop.canvas = canvas; - document.body.removeChild(this.prop.overlay); + if (this.prop.overlay && document.body.contains(this.prop.overlay)) { + document.body.removeChild(this.prop.overlay); + } + this.prop.overlay = null; + }) + .catch(function toContext2d_error(err) { + try { + if ( + this.prop && + this.prop.overlay && + document.body.contains(this.prop.overlay) + ) { + document.body.removeChild(this.prop.overlay); + } + this.prop.overlay = null; + } catch (e) { + // ignore cleanup errors + } + throw err; }); }; diff --git a/test/specs/html.spec.js b/test/specs/html.spec.js index 6cb94df4d..a54c6c314 100644 --- a/test/specs/html.spec.js +++ b/test/specs/html.spec.js @@ -403,4 +403,31 @@ describe("Module: html", () => { } comparePdf(doc.output(), "html-margin-page-break-slice.pdf", "html"); }); + + it("removes overlay when html2canvas throws an error", async () => { + if ( + (typeof isNode != "undefined" && isNode) || + navigator.userAgent.indexOf("Chrome") < 0 + ) { + return; + } + + const originalHtml2canvas = window.html2canvas; + window.html2canvas = function() { + return Promise.reject(new Error("simulated html2canvas failure")); + }; + + const doc = jsPDF({ floatPrecision: 2, unit: "pt" }); + let rejected = false; + try { + await doc.html("
"); + } catch (e) { + rejected = true; + } finally { + window.html2canvas = originalHtml2canvas; + } + + expect(rejected).toBeTrue(); + expect(document.querySelector(".html2pdf__overlay")).toBeNull(); + }); }); diff --git a/types/index.d.ts b/types/index.d.ts index 426850c1d..4d369b409 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -238,6 +238,13 @@ declare module "jspdf" { width?: number; windowWidth?: number; fontFaces?: HTMLFontFace[]; + /** + * Optional, off by default. When set to 'srgb', inline CSS Color 4 values like + * color(display-p3 ...) in style attributes are converted to approximate sRGB + * rgb/rgba(...) before rendering. This is a best-effort, lossy fallback meant + * to avoid crashes in html2canvas for unsupported color(). + */ + colorSpaceFallback?: "srgb" | "none"; } //jsPDF plugin: viewerPreferences