diff --git a/src/elements/Document.js b/src/elements/Document.js index 694ea9aa2..4ea76585a 100644 --- a/src/elements/Document.js +++ b/src/elements/Document.js @@ -47,41 +47,29 @@ class Document { this.root.instance.info.Producer = producer || 'react-pdf'; } - async loadFonts() { + async loadFontsAndEmojis() { const promises = []; - const listToExplore = this.children.slice(0); + const listToExplore = this.children.map(node => [node, {}]); while (listToExplore.length > 0) { - const node = listToExplore.shift(); - - if (node.style && node.style.fontFamily) { - promises.push(Font.load(node.style, this.root.instance)); - } - - if (node.children) { - node.children.forEach(childNode => { - listToExplore.push(childNode); - }); - } - } - - await Promise.all(promises); - } - - async loadEmojis() { - const promises = []; - const listToExplore = this.children.slice(0); - - while (listToExplore.length > 0) { - const node = listToExplore.shift(); + const [node, { parentStyle = {} }] = listToExplore.shift(); if (typeof node === 'string') { - promises.push(...fetchEmojis(node)); + promises.push( + ...fetchEmojis(node), + Font.load(parentStyle, this.root.instance, node), + ); } else if (typeof node.value === 'string') { - promises.push(...fetchEmojis(node.value)); + promises.push( + ...fetchEmojis(node.value), + Font.load(node.style || parentStyle, this.root.instance, node.value), + ); } else if (node.children) { node.children.forEach(childNode => { - listToExplore.push(childNode); + listToExplore.push([ + childNode, + { parentStyle: { ...parentStyle, ...node.style } }, + ]); }); } } @@ -111,7 +99,7 @@ class Document { } async loadAssets() { - await Promise.all([this.loadFonts(), this.loadImages()]); + await Promise.all([this.loadFontsAndEmojis(), this.loadImages()]); } applyProps() { @@ -172,7 +160,6 @@ class Document { try { this.addMetaData(); this.applyProps(); - await this.loadEmojis(); await this.loadAssets(); await this.renderPages(); this.root.instance.end(); diff --git a/src/font/font.js b/src/font/font.js index e7dbe3a6c..caef9a0e5 100644 --- a/src/font/font.js +++ b/src/font/font.js @@ -21,11 +21,12 @@ const throwInvalidUrl = src => { }; class FontSource { - constructor(src, fontFamily, fontStyle, fontWeight, options) { + constructor(src, fontFamily, fontStyle, fontWeight, unicodeRange, options) { this.src = src; this.fontFamily = fontFamily; this.fontStyle = fontStyle || 'normal'; this.fontWeight = processFontWeight(fontWeight) || 400; + this.unicodeRange = unicodeRange instanceof RegExp ? unicodeRange : /./; this.data = null; this.loading = false; @@ -63,9 +64,16 @@ class Font { this.sources = []; } - register({ src, fontWeight, fontStyle, ...options }) { + register({ src, fontWeight, fontStyle, unicodeRange, ...options }) { this.sources.push( - new FontSource(src, this.fontFamily, fontStyle, fontWeight, options), + new FontSource( + src, + this.fontFamily, + fontStyle, + fontWeight, + unicodeRange, + options, + ), ); } diff --git a/src/font/index.js b/src/font/index.js index 34e185108..353b687c4 100644 --- a/src/font/index.js +++ b/src/font/index.js @@ -53,18 +53,31 @@ const getFont = descriptor => { return fonts[fontFamily].resolve(descriptor); }; -const load = async function(descriptor, doc) { - const { fontFamily } = descriptor; - const isStandard = standardFonts.includes(fontFamily); - - if (isStandard) return; - - const font = getFont(descriptor); - - // We cache the font to avoid fetching it many times - if (!font.data && !font.loading) { - await font.load(); +const load = function({ fontFamily, ...descriptor }, doc, text) { + const fontFamilies = + typeof fontFamily === 'string' + ? fontFamily.split(',').map(family => family.trim()) + : [...(fontFamily || [])]; + const promises = []; + + for (const family of fontFamilies) { + if (standardFonts.includes(family)) break; + + const font = getFont({ ...descriptor, fontFamily: family }); + + const textRequiresFont = + typeof text === 'string' ? text.search(font.unicodeRange) >= 0 : true; + + // We cache the font to avoid fetching it many times + if (textRequiresFont && !font.data && !font.loading) { + promises.push(font.load()); + if (font.unicodeRange.source === '.') { + break; + } + } } + + return Promise.all(promises); }; const reset = function() { diff --git a/tests/document.test.js b/tests/document.test.js index d599e1c49..6038cc6bc 100644 --- a/tests/document.test.js +++ b/tests/document.test.js @@ -74,11 +74,25 @@ describe('Document', () => { test('Should trigger available fonts loading', async () => { const doc = new Document(dummyRoot, {}); + const page1 = new Page(dummyRoot, { style: { fontFamily: 'Courier' } }); + const text1 = new Text(dummyRoot, {}); + const textInstance1 = new TextInstance(dummyRoot, 'sample text'); + const page2 = new Page(dummyRoot, { style: { fontFamily: 'Helvetica' } }); + const text2 = new Text(dummyRoot, {}); + const textInstance2 = new TextInstance(dummyRoot, 'sample text'); + + text1.layoutEngine = { layout: jest.fn() }; + text2.layoutEngine = { layout: jest.fn() }; doc.appendChild(page1); + page1.appendChild(text1); + text1.appendChild(textInstance1); + doc.appendChild(page2); + page2.appendChild(text2); + text2.appendChild(textInstance2); await doc.render(); diff --git a/tests/layout.test.js b/tests/layout.test.js index c445bdb67..543479b9d 100644 --- a/tests/layout.test.js +++ b/tests/layout.test.js @@ -18,7 +18,6 @@ const testImage = fs.readFileSync(path.join(__dirname, 'assets/test.jpg')); const renderDocument = async doc => { doc.addMetaData(); doc.applyProps(); - await doc.loadEmojis(); await doc.loadAssets(); const subpages = await doc.renderPages(); doc.root.instance.end();