Add option to filter stylesheets by origin to prevent CORS errors
Expected Behavior
The library should provide an option to filter or exclude specific stylesheets by origin/URL when processing fonts, allowing users to skip problematic cross-origin stylesheets while still embedding necessary fonts.
Current Behavior
When converting HTML to images, the library attempts to access all stylesheets in the document, including those from different origins. This causes CORS errors when stylesheets don't have appropriate CORS headers:
SecurityError: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules
The error occurs in the embed-webfonts.ts file when trying to extract font information from cross-origin stylesheets.
Possible Solution
Add a new option to filter stylesheets by origin:
const dataUrl = await toCanvas(element, {
// New option to filter stylesheets
stylesheetFilter: (sheet) => {
// Skip stylesheets from specific domains
return !sheet.href || !sheet.href.includes('problematic-domain.com');
}
});
Steps To Reproduce
- Include a third-party widget/script in your page that loads its own stylesheet (e.g., a chat widget, analytics tool)
- Try to convert an element to an image using html-to-image
- Observe CORS errors in the console
Error Message & Stack Trace
Error while reading CSS rules from https://cdn.example.com/styles.css SecurityError: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules
at embed-webfonts.ts:172:39
at Array.forEach (<anonymous>)
at embed-webfonts.ts:169:17
at async parseWebFontRules (embed-webfonts.ts:200:20)
at async getWebFontCSS (embed-webfonts.ts:232:17)
at async embedWebFonts (embed-webfonts.ts:259:9)
at async toSvg (index.ts:21:3)
at async toCanvas (index.ts:33:15)
Additional Context
I'm building an application that exports HTML elements as PDFs. The app uses Google Fonts for styling but also includes third-party widgets that load their own stylesheets. Current workarounds include: • Temporarily disabling problematic stylesheets • Using skipFonts: true (which prevents all font embedding) • Complex DOM manipulation These workarounds are not ideal and a native solution would greatly improve usability.
Your Environment
- OS: macOS
- Browser: chrome
👋 @RicSala
Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. To help make it easier for us to investigate your issue, please follow the contributing guidelines.
We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
+1 Also would like to see this added.
Hi, I'm also keen to have this feature.
I've been patch-packaging this package for my own need until we have feedback from the author.
diff --git a/node_modules/html-to-image/es/embed-webfonts.js b/node_modules/html-to-image/es/embed-webfonts.js
index f6b0d96..97313c5 100644
--- a/node_modules/html-to-image/es/embed-webfonts.js
+++ b/node_modules/html-to-image/es/embed-webfonts.js
@@ -78,12 +78,16 @@ async function getCSSRules(styleSheets, options) {
const deferreds = [];
// First loop inlines imports
styleSheets.forEach((sheet) => {
+ if ('filterFontUrl' in options && sheet.href && !options.filterFontUrl(sheet.href)) {
+ return;
+ }
if ('cssRules' in sheet) {
try {
toArray(sheet.cssRules || []).forEach((item, index) => {
@@ -125,6 +129,9 @@ async function getCSSRules(styleSheets, options) {
return Promise.all(deferreds).then(() => {
// Second loop parses rules
styleSheets.forEach((sheet) => {
+ if ('filterFontUrl' in options && sheet.href && !options.filterFontUrl(sheet.href)) {
+ return;
+ }
if ('cssRules' in sheet) {
try {
toArray(sheet.cssRules || []).forEach((item) => {
diff --git a/node_modules/html-to-image/lib/types.d.ts b/node_modules/html-to-image/lib/types.d.ts
index a832baf..3c49cc0 100644
--- a/node_modules/html-to-image/lib/types.d.ts
+++ b/node_modules/html-to-image/lib/types.d.ts
@@ -35,6 +35,11 @@ export interface Options {
* it's children as well.
*/
filter?: (domNode: HTMLElement) => boolean;
+ /**
+ * A function to filter font URLs when embedding fonts. Should return
+ * `true` if the font should be embedded, `false` otherwise.
+ */
+ filterFontUrl?: (fontUrl: string) => boolean;
/**
* A number between `0` and `1` indicating image quality (e.g. 0.92 => 92%)
* of the JPEG image.
Also note that you may need this as well for firefox/safari support
diff --git a/node_modules/html-to-image/es/embed-webfonts.js b/node_modules/html-to-image/es/embed-webfonts.js
index f6b0d96..97313c5 100644
--- a/node_modules/html-to-image/es/embed-webfonts.js
+++ b/node_modules/html-to-image/es/embed-webfonts.js
@@ -175,7 +182,7 @@ export async function getWebFontCSS(node, options) {
const rules = await parseWebFontRules(node, options);
const usedFonts = getUsedFonts(node);
const cssTexts = await Promise.all(rules
- .filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily)))
+ .filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily || rule.style.getPropertyValue('font-family'))))
.map((rule) => {
const baseUrl = rule.parentStyleSheet
? rule.parentStyleSheet.href