diff --git a/lib/gatherStylesheetsWithPredicates.js b/lib/gatherStylesheetsWithPredicates.js
index 7325fa17..dd2e48dd 100644
--- a/lib/gatherStylesheetsWithPredicates.js
+++ b/lib/gatherStylesheetsWithPredicates.js
@@ -19,6 +19,7 @@ module.exports = function gatherStylesheetsWithPredicates(
type: {
$in: [
'HtmlStyle',
+ 'SvgStyle',
'CssImport',
'HtmlConditionalComment',
'HtmlNoscript',
diff --git a/lib/getCssRulesByProperty.js b/lib/getCssRulesByProperty.js
index 28adbd1f..36907f9b 100644
--- a/lib/getCssRulesByProperty.js
+++ b/lib/getCssRulesByProperty.js
@@ -41,7 +41,7 @@ function getCssRulesByProperty(properties, cssSource, existingPredicates) {
existingPredicates = existingPredicates || {};
const parseTree = postcss.parse(cssSource);
- let defaultNamespaceURI = 'http://www.w3.org/1999/xhtml';
+ let defaultNamespaceURI;
parseTree.walkAtRules('namespace', (rule) => {
const fragments = rule.params.split(/\s+/);
if (fragments.length === 1) {
diff --git a/lib/subfont.js b/lib/subfont.js
index c9f75749..a1d9eb59 100644
--- a/lib/subfont.js
+++ b/lib/subfont.js
@@ -212,7 +212,7 @@ module.exports = async function subfont(
isInline: false,
isLoaded: true,
type: {
- $in: ['Html', 'Css', 'JavaScript'],
+ $in: ['Html', 'Svg', 'Css', 'JavaScript'],
},
})) {
sumSizesBefore += asset.rawSrc.length;
@@ -233,7 +233,7 @@ module.exports = async function subfont(
isInline: false,
isLoaded: true,
type: {
- $in: ['Html', 'Css', 'JavaScript'],
+ $in: ['Html', 'Svg', 'Css', 'JavaScript'],
},
})) {
sumSizesAfter += asset.rawSrc.length;
@@ -370,7 +370,9 @@ module.exports = async function subfont(
}
}
log(
- `HTML/JS/CSS size increase: ${prettyBytes(sumSizesAfter - sumSizesBefore)}`
+ `HTML/SVG/JS/CSS size increase: ${prettyBytes(
+ sumSizesAfter - sumSizesBefore
+ )}`
);
log(`Total savings: ${prettyBytes(totalSavings)}`);
if (!dryRun) {
diff --git a/lib/subsetFonts.js b/lib/subsetFonts.js
index e41b31f1..ed7cb749 100644
--- a/lib/subsetFonts.js
+++ b/lib/subsetFonts.js
@@ -578,11 +578,14 @@ async function subsetFonts(
// Collect texts by page
const memoizedGetCssRulesByProperty = memoizeSync(getCssRulesByProperty);
- const htmlAssets = assetGraph.findAssets({ type: 'Html', isInline: false });
+ const htmlAssets = assetGraph.findAssets({
+ type: { $in: ['Html', 'Svg'] },
+ isInline: false,
+ });
const traversalRelationQuery = {
$or: [
{
- type: { $in: ['HtmlStyle', 'CssImport'] },
+ type: { $in: ['HtmlStyle', 'SvgStyle', 'CssImport'] },
},
{
to: {
@@ -672,7 +675,7 @@ async function subsetFonts(
htmlAsset
),
getCssRulesByProperty: memoizedGetCssRulesByProperty,
- htmlAsset,
+ asset: htmlAsset,
});
if (headlessBrowser) {
textByProps.push(...(await headlessBrowser.tracePage(htmlAsset)));
@@ -870,7 +873,7 @@ These glyphs are used on your site, but they don't exist in the font you applied
accumulatedFontFaceDeclarations,
} of htmlAssetTextsWithProps) {
const insertionPoint = assetGraph.findRelations({
- type: 'HtmlStyle',
+ type: `${htmlAsset.type}Style`,
from: htmlAsset,
})[0];
const subsetFontUsages = fontUsages.filter(
@@ -1051,21 +1054,23 @@ These glyphs are used on your site, but they don't exist in the font you applied
// - https://caniuse.com/#search=woff2
// - https://caniuse.com/#search=preload
- htmlAsset.addRelation(
- {
- type: 'HtmlPreloadLink',
- hrefType,
- to: fontAsset,
- as: 'font',
- },
- 'before',
- insertionPoint
- );
+ if (htmlAsset.type === 'Html') {
+ htmlAsset.addRelation(
+ {
+ type: 'HtmlPreloadLink',
+ hrefType,
+ to: fontAsset,
+ as: 'font',
+ },
+ 'before',
+ insertionPoint
+ );
+ }
}
}
const cssRelation = htmlAsset.addRelation(
{
- type: 'HtmlStyle',
+ type: `${htmlAsset.type}Style`,
hrefType: inlineCss ? 'inline' : hrefType,
to: cssAsset,
},
@@ -1151,18 +1156,20 @@ These glyphs are used on your site, but they don't exist in the font you applied
cssAsset.url = cssAssetUrl;
}
- // Create a that asyncLoadStyleRelationWithFallback can convert to async with noscript fallback:
- const fallbackHtmlStyle = htmlAsset.addRelation({
- type: 'HtmlStyle',
- to: cssAsset,
- });
+ if (htmlAsset.type === 'Html') {
+ // Create a that asyncLoadStyleRelationWithFallback can convert to async with noscript fallback:
+ const fallbackHtmlStyle = htmlAsset.addRelation({
+ type: 'HtmlStyle',
+ to: cssAsset,
+ });
- asyncLoadStyleRelationWithFallback(
- htmlAsset,
- fallbackHtmlStyle,
- hrefType
- );
- relationsToRemove.add(fallbackHtmlStyle);
+ asyncLoadStyleRelationWithFallback(
+ htmlAsset,
+ fallbackHtmlStyle,
+ hrefType
+ );
+ relationsToRemove.add(fallbackHtmlStyle);
+ }
}
}
@@ -1200,11 +1207,13 @@ These glyphs are used on your site, but they don't exist in the font you applied
if (googleFontStylesheetRelation.type === 'CssImport') {
// Gather Html parents. Relevant if we are dealing with CSS @import relations
htmlParents = getParents(googleFontStylesheetRelation.to, {
- type: 'Html',
+ type: { $in: ['Html', 'Svg'] },
isInline: false,
isLoaded: true,
});
- } else if (googleFontStylesheetRelation.from.type === 'Html') {
+ } else if (
+ ['Html', 'Svg'].includes(googleFontStylesheetRelation.from.type)
+ ) {
htmlParents = [googleFontStylesheetRelation.from];
} else {
htmlParents = [];
@@ -1235,18 +1244,20 @@ These glyphs are used on your site, but they don't exist in the font you applied
}
const selfHostedFallbackRelation = htmlParent.addRelation(
{
- type: 'HtmlStyle',
+ type: `${htmlParent.type}Style`,
to: selfHostedGoogleFontsCssAsset,
hrefType,
},
'lastInBody'
);
relationsToRemove.add(selfHostedFallbackRelation);
- asyncLoadStyleRelationWithFallback(
- htmlParent,
- selfHostedFallbackRelation,
- hrefType
- );
+ if (htmlParent.type === 'Html') {
+ asyncLoadStyleRelationWithFallback(
+ htmlParent,
+ selfHostedFallbackRelation,
+ hrefType
+ );
+ }
}
relationsToRemove.add(googleFontStylesheetRelation);
}
@@ -1276,7 +1287,37 @@ These glyphs are used on your site, but they don't exist in the font you applied
}
let customPropertyDefinitions; // Avoid computing this unless necessary
- // Inject subset font name before original webfont
+ // Inject subset font name before original webfont in SVG font-family attributes
+ const svgAssets = assetGraph.findAssets({ type: 'Svg' });
+ for (const svgAsset of svgAssets) {
+ let changesMade = false;
+ for (const element of Array.from(
+ svgAsset.parseTree.querySelectorAll('[font-family]')
+ )) {
+ const fontFamilies = cssListHelpers.splitByCommas(
+ element.getAttribute('font-family')
+ );
+ for (let i = 0; i < fontFamilies.length; i += 1) {
+ const subsetFontFamily =
+ webfontNameMap[fontFamily.parse(fontFamilies[i])[0].toLowerCase()];
+ if (subsetFontFamily && !fontFamilies.includes(subsetFontFamily)) {
+ fontFamilies.splice(
+ i,
+ omitFallbacks ? 1 : 0,
+ cssQuoteIfNecessary(subsetFontFamily)
+ );
+ i += 1;
+ element.setAttribute('font-family', fontFamilies.join(', '));
+ changesMade = true;
+ }
+ }
+ }
+ if (changesMade) {
+ svgAsset.markDirty();
+ }
+ }
+
+ // Inject subset font name before original webfont in CSS
const cssAssets = assetGraph.findAssets({
type: 'Css',
isLoaded: true,
diff --git a/test/getCssRulesByProperty.js b/test/getCssRulesByProperty.js
index 8c76c46d..a68b893f 100644
--- a/test/getCssRulesByProperty.js
+++ b/test/getCssRulesByProperty.js
@@ -45,7 +45,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'color',
value: 'red',
@@ -54,7 +54,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h2',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'color',
value: 'blue',
@@ -76,7 +76,7 @@ describe('getCssRulesByProperty', function () {
{
selector: undefined,
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [1, 0, 0, 0],
prop: 'color',
value: 'red',
@@ -99,7 +99,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'color',
value: 'red',
@@ -108,7 +108,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'color',
value: 'blue',
@@ -135,7 +135,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -146,7 +146,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -170,7 +170,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -181,7 +181,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -192,7 +192,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -203,7 +203,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -227,7 +227,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -238,7 +238,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font-size',
value: '10px',
@@ -247,7 +247,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font',
value: '15px serif',
@@ -256,7 +256,7 @@ describe('getCssRulesByProperty', function () {
{
selector: 'h1',
predicates: {},
- namespaceURI: 'http://www.w3.org/1999/xhtml',
+ namespaceURI: undefined,
specificityArray: [0, 0, 0, 1],
prop: 'font-size',
value: '20px',
diff --git a/test/subsetFonts.js b/test/subsetFonts.js
index b7f2ed0a..9f02583b 100644
--- a/test/subsetFonts.js
+++ b/test/subsetFonts.js
@@ -3276,4 +3276,154 @@ describe('subsetFonts', function () {
]);
});
});
+
+ describe('with SVG using webfonts', function () {
+ describe('in a standalone SVG', function () {
+ it('should trace the correct characters and patch up the stylesheet', async function () {
+ const assetGraph = new AssetGraph({
+ root: pathModule.resolve(
+ __dirname,
+ '../testdata/subsetFonts/svg/img-element/'
+ ),
+ });
+ await assetGraph.loadAssets('index.html');
+ await assetGraph.populate({
+ followRelations: {
+ crossorigin: false,
+ },
+ });
+ const result = await subsetFonts(assetGraph);
+
+ expect(result, 'to satisfy', {
+ fontInfo: [
+ {
+ fontUsages: [
+ {
+ text: ' !,Hdelorw',
+ props: {
+ 'font-stretch': 'normal',
+ 'font-weight': '400',
+ 'font-style': 'normal',
+ 'font-family': 'Roboto',
+ src: expect.it('to contain', "format('woff')"),
+ },
+ },
+ ],
+ },
+ ],
+ });
+
+ const svgAsset = assetGraph.findAssets({ type: 'Svg' })[0];
+ expect(
+ svgAsset.text,
+ 'to contain',
+ '