Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
102 changes: 100 additions & 2 deletions src/modules/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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", {
Expand Down Expand Up @@ -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;
});
};

Expand Down Expand Up @@ -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;
});
};

Expand Down
27 changes: 27 additions & 0 deletions test/specs/html.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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("<div style='width:10px;height:10px;background:red'></div>");
} catch (e) {
rejected = true;
} finally {
window.html2canvas = originalHtml2canvas;
}

expect(rejected).toBeTrue();
expect(document.querySelector(".html2pdf__overlay")).toBeNull();
});
});
7 changes: 7 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down