diff --git a/consts/defaults.ts b/consts/defaults.ts
index da455bd..fcc2f36 100644
--- a/consts/defaults.ts
+++ b/consts/defaults.ts
@@ -1,2 +1,38 @@
export const MODE_DEFAULT = "async";
export const SCOPE_DEFAULT = "body";
+export const TIMEOUT_DEFAULT = 30000;
+export const INTERVAL_DEFAULT = 300;
+
+export const WEIGHTS = {
+ 1: "100",
+ 2: "200",
+ 3: "300",
+ 4: "400",
+ 5: "500",
+ 6: "600",
+ 7: "700",
+ 8: "800",
+ 9: "900",
+ 100: "1",
+ 200: "2",
+ 300: "3",
+ 400: "4",
+ 500: "5",
+ 600: "6",
+ 700: "7",
+ 800: "8",
+ 900: "9",
+ normal: "4",
+ bold: "7",
+}
+
+export const STYLES = {
+ n: "normal",
+ i: "italic",
+ o: "oblique",
+ normal: "n",
+ italic: "i",
+ oblique: "o",
+}
+
+export const VARIATION_MATCH = new RegExp("^(n|i)([1-9])$")
diff --git a/gatsby-browser.js b/gatsby-browser.js
index 4ba46ad..011381a 100644
--- a/gatsby-browser.js
+++ b/gatsby-browser.js
@@ -1,12 +1,19 @@
-import React from "react";
-import { AsyncFonts } from "./components";
-import { MODE_DEFAULT, SCOPE_DEFAULT } from "./consts";
-import { getFontFiles, getFontNames } from "./utils";
-import { fontListener } from "./utils/fontListener";
+import React from 'react';
+import { AsyncFonts } from './components';
+import { MODE_DEFAULT, SCOPE_DEFAULT, TIMEOUT_DEFAULT, INTERVAL_DEFAULT } from './consts';
+import { getFontFiles, getFontNames } from './utils';
+import { fontListener } from './utils/fontListener';
export const onClientEntry = (
_,
- { custom = [], web = [], enableListener = false, scope = SCOPE_DEFAULT }
+ {
+ custom = [],
+ web = [],
+ enableListener = false,
+ scope = SCOPE_DEFAULT,
+ timeout = TIMEOUT_DEFAULT,
+ interval = INTERVAL_DEFAULT,
+ }
) => {
if (!enableListener) {
return;
@@ -14,16 +21,13 @@ export const onClientEntry = (
const allFonts = [...custom, ...web];
const fontNames = getFontNames(allFonts);
- const listenerProps = { fontNames, scope };
+ const listenerProps = { fontNames, scope, timeout, interval };
fontListener(listenerProps);
};
-export const wrapRootElement = (
- { element },
- { custom = [], web = [], mode = MODE_DEFAULT }
-) => {
- if (mode !== "async") {
+export const wrapRootElement = ({ element }, { custom = [], web = [], mode = MODE_DEFAULT }) => {
+ if (mode !== 'async') {
return element;
}
diff --git a/utils/fontListener.ts b/utils/fontListener.ts
index 0b7e719..091b6ec 100644
--- a/utils/fontListener.ts
+++ b/utils/fontListener.ts
@@ -1,46 +1,71 @@
-import { kebabCase } from "../utils";
+import { info, warn } from './logger';
+import { convertToFVD, parseFontInfo } from './parseFontInfo';
-declare var document: { fonts: any };
+export type FontInfo = { fontName: string; fontStyle: string; fontWeight: string }
+
+export const fontListener = ({ fontNames, scope, timeout, interval }) => {
-export const fontListener = ({ fontNames, scope }) => {
const hasFonts = fontNames && Boolean(fontNames.length);
const targetElement = scope === "html" ? "documentElement" : "body";
const apiAvailable = "fonts" in document;
+ let parsedFont: FontInfo[] = [];
+
function handleLoadComplete() {
addClassName("all");
}
- function handleFontLoad(fontFaces: FontFace[]) {
- fontFaces.forEach((fontFace) => {
- addClassName(fontFace.family);
- })
+ function handleFontLoad(fontInfo: FontInfo) {
+ const fvd = convertToFVD(fontInfo)
+ addClassName(fvd);
}
- function fontMapper(fontName) {
- return document.fonts
- .load(`1rem ${fontName}`)
- .then(handleFontLoad)
- .catch(errorFallback);
+ function fontMapper(fontDetail: FontInfo) {
+ const fontFace = [fontDetail.fontStyle, fontDetail.fontWeight, '1rem', fontDetail.fontName].join(' ')
+ const startTime = Date.now();
+
+ return new Promise((resolve, reject) => {
+ const recursiveFn = () => {
+ const currTime = Date.now();
+
+ if ((currTime - startTime) >= timeout) {
+ reject('font listener timeout ' + fontFace);
+ } else {
+ document.fonts.load(fontFace).then((fonts) => {
+ if (fonts.length >= 1) {
+ handleFontLoad(fontDetail);
+ resolve(true);
+ } else {
+ setTimeout(recursiveFn, interval);
+ }
+ }).catch((err) => {
+ reject(err);
+ });
+ }
+ };
+ recursiveFn()
+ });
+
}
function loadFonts() {
- const fonts = fontNames.map(fontMapper);
+ const fonts = parsedFont.map(fontMapper);
Promise.all(fonts).then(handleLoadComplete).catch(errorFallback);
}
- function errorFallback() {
- fontNames.forEach(addClassName);
+ function errorFallback(e) {
+ warn('error in omni font loader', e)
+ parsedFont.forEach((fontInfo) => addClassName(convertToFVD(fontInfo)));
}
function handleApiError(error) {
- console.info(`document.fonts API error: ${error}`);
- console.info(`Replacing fonts instantly. FOUT handling failed.`);
- errorFallback();
+ info(`document.fonts API error: ${error}`);
+ info(`Replacing fonts instantly. FOUT handling failed.`);
+ errorFallback(error);
}
function addClassName(fontName: string) {
- document[targetElement].classList.add(`wf-${kebabCase(fontName)}`);
+ document[targetElement].classList.add(`wf-${fontName}`);
}
if (!apiAvailable) {
@@ -49,6 +74,7 @@ export const fontListener = ({ fontNames, scope }) => {
}
if (hasFonts && apiAvailable) {
+ parsedFont = parseFontInfo(fontNames)
loadFonts();
}
};
diff --git a/utils/getFontNames.ts b/utils/getFontNames.ts
index ff17e22..2e02645 100644
--- a/utils/getFontNames.ts
+++ b/utils/getFontNames.ts
@@ -1,5 +1,5 @@
-export const getFontNames = (allFonts: { name: string }[]) => {
- const fontNames = []
+export const getFontNames = (allFonts: { name: string | string[] }[]) => {
+ const fontNames: string[] = []
allFonts.forEach(({ name }) =>
Array.isArray(name) ? fontNames.push(...name) : fontNames.push(name)
)
diff --git a/utils/logger.ts b/utils/logger.ts
new file mode 100644
index 0000000..3b0b7a7
--- /dev/null
+++ b/utils/logger.ts
@@ -0,0 +1,20 @@
+
+const warn = (function (environment) {
+ if (environment === "production") {
+ return () => { }
+ }
+ return (...args) => {
+ console.warn(...args)
+ }
+})(process.env.NODE_ENV);
+
+const info = (function (environment) {
+ if (environment === "production") {
+ return () => { }
+ }
+ return (...args) => {
+ console.info(...args)
+ }
+})(process.env.NODE_ENV);
+
+export { warn, info }
\ No newline at end of file
diff --git a/utils/parseFontInfo.ts b/utils/parseFontInfo.ts
new file mode 100644
index 0000000..a34ed03
--- /dev/null
+++ b/utils/parseFontInfo.ts
@@ -0,0 +1,87 @@
+import { kebabCase } from "../utils";
+import { FontInfo } from './fontListener';
+import { VARIATION_MATCH, WEIGHTS, STYLES } from '../consts';
+
+export const parseFontInfo = (fontFamilies: string[]) => {
+ const length = fontFamilies.length
+
+ const parsedFonts: FontInfo[] = []
+ for (let i = 0; i < length; i++) {
+ const elements = fontFamilies[i].split(":")
+ const fontFamily = elements[0].replace(/\+/g, " ")
+ let variations = [{ fontStyle: '', fontWeight: '' }]
+
+ if (elements.length >= 2) {
+ const fvds = parseVariations(elements[1])
+
+ if (fvds.length > 0) {
+ variations = fvds
+ }
+ }
+
+ for (let j = 0; j < variations.length; j += 1) {
+ parsedFonts.push({ fontName: fontFamily, ...variations[j] })
+ }
+ }
+ return parsedFonts
+}
+
+const generateFontVariationDescription = (variation: string) => {
+ const normalizedVariation = variation.toLowerCase()
+ const groups = VARIATION_MATCH.exec(normalizedVariation)
+ if (groups == null) {
+ return ""
+ }
+ const styleMatch = normalizeStyle(groups[1])
+ const weightMatch = normalizeWeight(groups[2])
+ return (
+ {
+ fontStyle: styleMatch,
+ fontWeight: weightMatch
+ }
+ )
+}
+
+export const normalizeStyle = (parsedStyle: string): string => {
+ if (!parsedStyle) {
+ return ""
+ }
+ return STYLES[parsedStyle]
+}
+
+export const normalizeWeight = (parsedWeight: string | number): string => {
+ if (!parsedWeight) {
+ return ""
+ }
+ return WEIGHTS[parsedWeight]
+
+}
+
+const parseVariations = (variations: string) => {
+ let finalVariations: Omit[] = []
+
+ if (!variations) {
+ return finalVariations
+ }
+ const providedVariations = variations.split(",")
+ const length = providedVariations.length
+
+ for (let i = 0; i < length; i++) {
+ let variation = providedVariations[i]
+ const fvd = generateFontVariationDescription(variation)
+
+ if (fvd) {
+ finalVariations.push(fvd)
+ }
+ }
+ return finalVariations
+}
+
+
+export const convertToFVD = ({fontName, fontStyle, fontWeight}: FontInfo) => {
+ const weightVal = normalizeWeight(fontWeight)
+ const styleVal = normalizeStyle(fontStyle)
+ const styleWeight = styleVal + weightVal
+ const fontNameVal = kebabCase(fontName)
+ return styleWeight ? [fontNameVal, styleWeight].join('-') : fontNameVal
+}
\ No newline at end of file