/** * @name @joncasey/google-fonts * @version 0.5.1 * @author Jon Casey (https://github.com/joncasey) * @license MIT */ import fs, { existsSync, rmdirSync } from 'fs'; import http from 'http'; import https from 'https'; import { parse as parse$1 } from 'url'; const fetch = (url, init) => new Promise((resolve, reject) => { let m = http; let o = { ...parse$1(url), ...init }; if (o.protocol === 'https:') { m = https; o.rejectUnauthorized = false; } const req = m.request(o, res => resolve(Object.assign(res, fetchResponse))); req.on('error', reject); if (o.body) req.write(o.body); req.end(); }); const multiFetch = (url, inits) => Promise.all( inits.map(init => fetch(url, init)) ); const fetchResponse = { save (toFile) { return new Promise((resolve) => { let file = fs.createWriteStream(toFile); file.on('finish', resolve); this.pipe(file); }) }, text () { return new Promise((resolve) => { const a = []; this.on('data', d => a.push(d)); this.on('end', () => resolve(a.join(''))); }) } }; const baseURL = 'https://fonts.googleapis.com'; const fontTypes = [ 'AppleWebKit/0 Chrome/60.0.0.0', 'AppleWebKit/0 Chrome/30.0.0.0', ]; const fontVariants = '12345678'.replace(/(\d)/g, '$100,$100i,'); const fontWeightNames = { 100: 'Thin', 200: 'ExtraLight', 300: 'Light', 400: 'Regular', 500: 'Medium', 600: 'SemiBold', 700: 'Bold', 800: 'ExtraBold' }; const isMaterialIcons = /^(Material )?Icons$/i; const isVariableFonts = /wght@/; var util = /*#__PURE__*/Object.freeze({ __proto__: null, baseURL: baseURL, fontTypes: fontTypes, fontVariants: fontVariants, fontWeightNames: fontWeightNames, isMaterialIcons: isMaterialIcons, isVariableFonts: isVariableFonts }); function parseCSS (css) { const fontProps = ['family', 'weight', 'style']; const fontFaces = {}; let cssText = css.replace( /(?:\/\*[^\*]+\*\/\s*)?@font-face \{([^\}]+)\}\s*/gm, (_, fontFace) => { const font = parseFontFace(fontFace); const id = [font.family, font.weight, font.style]; const f = fontFaces[id] || (fontFaces[id] = { src: {} }); fontProps.forEach(prop => f[prop] = font[prop]); if (!f.src.local) { f.src.local = font.local; } f.src[font.src.format] = font.src.url; return '' }).trim(); return { fontFaces, cssText, } } function parseFontFace (cssText) { const props = { src: {} }; const srcFormat = /format\('([^)]+)'\)/; const srcLocal = /local\('([^']+)'\), /g; const srcValue = /url\(([^)]+)\)/; cssText.replace(/ ([^:]+): ([^;]+);/g, (_, name, value) => { if (name === 'src') { value = value.replace(srcLocal, (_, localName) => { props.local = localName; return '' }); value = { url: value.match(srcValue)[1], format: (value.match(srcFormat) || ['', 'embedded-opentype'])[1], rawValue: value, toString () { return this.rawValue } }; } else { name = name.replace('font-', ''); value = value.replace(/'/g, ''); } props[name] = value; }); if (!props.local && props.family === 'Material Icons') { props.local = 'MaterialIcons'; } if (!props.local) { props.local = ( props.family.replace(/ /g, '') + '-' + (fontWeightNames[props.weight] || '') + (props.style.startsWith('i') ? 'Italic' : '') ); } return props } var parse = /*#__PURE__*/Object.freeze({ __proto__: null, parseCSS: parseCSS, parseFontFace: parseFontFace }); const fixSrc = font => Object.keys(font.src) .filter(format => format !== 'local') .map(format => { let remote = font.src[format]; let ext = remote.split('.').pop(); // done this way, for TTF - not really needed anymore let local = `${font.src.local}.${ext}`; return `url('${local}') format('${format}')` }) .join(',\n '); var fontsToCSS = (fonts, cssText = '') => fonts.map(font => ` @font-face { font-family: '${font.family}'; font-weight: ${font.weight}; font-style: ${font.style}; src: ${fixSrc(font)}; } `).concat(cssText).join(''); var getFont = async (fontFamily, types = fontTypes) => { const inits = [].concat(types) .map(type => ({ headers: { 'User-Agent': type } })); let family = fontFamily.trim().replace(/:$|:\|/g, `:${fontVariants}|`); let path = `/css?family=${family}`; if (isVariableFonts.test(family)) { path = `/css2?family=${family}`; } if (isMaterialIcons.test(family)) { path = `/icon?family=Material+Icons`; } let responses = await multiFetch(`${baseURL}${path}`, inits); let textValues = await Promise.all(responses.map(res => res.text())); let parsed = parseCSS(textValues.join('\n')); let fonts = Object.values(parsed.fontFaces); let css = fontsToCSS(fonts, parsed.cssText); return { css, family: fonts[0].family, fonts, raw: textValues, } }; const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const lower = 'abcdefghijklmnopqrstuvwxyz'; const extra = '0123456789,.!?'; const fontWeights = '12345678'.split('').map(v => ` .fw${v}00 { font-weight: ${v}00; }`); const fontToParagraph = font => `
${upper}
${lower}
${extra}