From f4cf7e8461227f42641c0cb69f984c4e9ada41b1 Mon Sep 17 00:00:00 2001 From: Tom Tang Date: Thu, 12 Sep 2024 06:00:39 +0800 Subject: [PATCH] feat: `googleicons` provider (#133) --- docs/content/1.get-started/4.providers.md | 4 + playground/pages/providers/googleicons.vue | 46 ++++++++++ src/module.ts | 2 + src/providers/googleicons.ts | 98 ++++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 playground/pages/providers/googleicons.vue create mode 100644 src/providers/googleicons.ts diff --git a/docs/content/1.get-started/4.providers.md b/docs/content/1.get-started/4.providers.md index 4ba92fb..8948010 100644 --- a/docs/content/1.get-started/4.providers.md +++ b/docs/content/1.get-started/4.providers.md @@ -17,6 +17,10 @@ Then, when you use a `font-family` in your CSS, we check to see whether it match [Google Fonts](https://fonts.google.com/) is one of the best known public font APIs. +### `googleicons` + +[Google Fonts Icons](https://fonts.google.com/icons) adds support for Material Symbols and Material Icons. + ### `bunny` [Bunny Fonts](https://fonts.bunny.net/) is provided by [bunny.net](https://bunny.net/) and is a drop-in Google Fonts compatible API, focusing on privacy. diff --git a/playground/pages/providers/googleicons.vue b/playground/pages/providers/googleicons.vue new file mode 100644 index 0000000..f4780e8 --- /dev/null +++ b/playground/pages/providers/googleicons.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/module.ts b/src/module.ts index eaf72cd..364387d 100644 --- a/src/module.ts +++ b/src/module.ts @@ -6,6 +6,7 @@ import { join, relative } from 'pathe' import { withoutLeadingSlash } from 'ufo' import local from './providers/local' import google from './providers/google' +import googleicons from './providers/googleicons' import bunny from './providers/bunny' import fontshare from './providers/fontshare' import adobe from './providers/adobe' @@ -93,6 +94,7 @@ export default defineNuxtModule({ local, adobe, google, + googleicons, bunny, fontshare, fontsource, diff --git a/src/providers/googleicons.ts b/src/providers/googleicons.ts new file mode 100644 index 0000000..d511617 --- /dev/null +++ b/src/providers/googleicons.ts @@ -0,0 +1,98 @@ +import { hash } from 'ohash' + +import type { FontProvider } from '../types' +import { extractFontFaceData, addLocalFallbacks } from '../css/parse' +import { cachedData } from '../cache' +import { $fetch } from '../fetch' +import { logger } from '../logger' + +export default { + async setup() { + await initialiseFontMeta() + }, + async resolveFontFaces(fontFamily, defaults) { + if (!isGoogleIcon(fontFamily)) { + return + } + + return { + fonts: await cachedData(`googleicons:${fontFamily}-${hash(defaults)}-data.json`, () => getFontDetails(fontFamily), { + onError(err) { + logger.error(`Could not fetch metadata for \`${fontFamily}\` from \`googleicons\`.`, err) + return [] + }, + }), + } + }, +} satisfies FontProvider + +/** internal */ + +let fonts: string[] + +async function fetchFontMetadata() { + const response: { families: string[] } = JSON.parse((await $fetch( + 'https://fonts.google.com/metadata/icons?key=material_symbols&incomplete=true', + )).split('\n').slice(1).join('\n')) // remove the first line which makes it an invalid JSON + + return response.families +} + +async function initialiseFontMeta() { + fonts = await cachedData('googleicons:meta.json', fetchFontMetadata, { + onError() { + logger.error('Could not download `googleicons` font metadata. `@nuxt/fonts` will not be able to inject `@font-face` rules for googleicons.') + return [] + }, + }) +} + +function isGoogleIcon(family: string) { + return fonts.includes(family) +} + +async function getFontDetails(family: string) { + let css = '' + + if (family.includes('Icons')) { + css += await $fetch('/css2', { + baseURL: 'https://fonts.googleapis.com/icon', + query: { + family: family, + }, + }) + } + + for (const extension in userAgents) { + // Legacy Material Icons + if (family.includes('Icons')) { + css += await $fetch('/icon', { + baseURL: 'https://fonts.googleapis.com', + headers: { 'user-agent': userAgents[extension as keyof typeof userAgents] }, + query: { + family: family, + }, + }) + } + // New Material Symbols + else { + css += await $fetch('/css2', { + baseURL: 'https://fonts.googleapis.com', + headers: { 'user-agent': userAgents[extension as keyof typeof userAgents] }, + query: { + family: family + ':' + 'opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200', + }, + }) + } + } + + return addLocalFallbacks(family, extractFontFaceData(css)) +} + +const userAgents = { + woff2: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', + ttf: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.54.16 (KHTML, like Gecko) Version/5.1.4 Safari/534.54.16', + // eot: 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)', + // woff: 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0', + // svg: 'Mozilla/4.0 (iPad; CPU OS 4_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/4.1 Mobile/9A405 Safari/7534.48.3', +}