Skip to content

Commit

Permalink
fix: support ital font families
Browse files Browse the repository at this point in the history
Fixes #300
  • Loading branch information
harlan-zw committed Jan 14, 2025
1 parent 2ba98ff commit 8c8cb97
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 22 deletions.
3 changes: 2 additions & 1 deletion docs/content/3.guides/5.custom-fonts.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export default defineNuxtConfig({
fonts: [
// will load the Noto Sans font from Google fonts
'Noto+Sans:400',
'Noto+Sans:700'
'Noto+Sans:700',
'Work+Sans:ital:400'
]
}
})
Expand Down
16 changes: 9 additions & 7 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,38 +323,40 @@ export default defineNuxtModule<ModuleOptions>({
})
config.fonts = (await Promise.all(normaliseFontInput(config.fonts)
.map(async (f) => {
const fontKey = `${f.name}:${f.style}:${f.weight}`
const fontFileBase = fontKey.replaceAll(':', '-')
if (!f.key && !f.path) {
if (preset === 'stackblitz') {
logger.warn(`The ${f.name}:${f.weight} font was skipped because remote fonts are not available in StackBlitz, please use a local font.`)
logger.warn(`The ${fontKey} font was skipped because remote fonts are not available in StackBlitz, please use a local font.`)
return false
}
if (await downloadFont(f, fontStorage, config.googleFontMirror)) {
// move file to serverFontsDir
f.key = `nuxt-og-image:fonts:${f.name}-${f.weight}.ttf.base64`
f.key = `nuxt-og-image:fonts:${fontFileBase}.ttf.base64`
}
else {
logger.warn(`Failed to download font ${f.name}:${f.weight}. You may be offline or behind a firewall blocking Google. Consider setting \`googleFontMirror: true\`.`)
logger.warn(`Failed to download font ${fontKey}. You may be offline or behind a firewall blocking Google. Consider setting \`googleFontMirror: true\`.`)
return false
}
}
else if (f.path) {
// validate the extension, can only be woff, ttf or otf
const extension = basename(f.path.replace('.base64', '')).split('.').pop()!
if (!['woff', 'ttf', 'otf'].includes(extension)) {
logger.warn(`The ${f.name}:${f.weight} font was skipped because the file extension ${extension} is not supported. Only woff, ttf and otf are supported.`)
logger.warn(`The ${fontKey} font was skipped because the file extension ${extension} is not supported. Only woff, ttf and otf are supported.`)
return false
}
// resolve relative paths from public dir
// move to assets folder as base64 and set key
if (!f.absolutePath)
f.path = resolve(publicDirAbs, withoutLeadingSlash(f.path))
if (!existsSync(f.path)) {
logger.warn(`The ${f.name}:${f.weight} font was skipped because the file does not exist at path ${f.path}.`)
logger.warn(`The ${fontKey} font was skipped because the file does not exist at path ${f.path}.`)
return false
}
const fontData = await readFile(f.path, f.path.endsWith('.base64') ? 'utf-8' : 'base64')
f.key = `nuxt-og-image:fonts:${f.name}-${f.weight}.${extension}.base64`
await fontStorage.setItem(`${f.name}-${f.weight}.${extension}.base64`, fontData)
f.key = `nuxt-og-image:fonts:${fontFileBase}.${extension}.base64`
await fontStorage.setItem(`${fontFileBase}.${extension}.base64`, fontData)
delete f.path
delete f.absolutePath
}
Expand Down
17 changes: 14 additions & 3 deletions src/pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,28 @@ import type { InputFontConfig, ResolvedFontConfig } from './runtime/types'
export function normaliseFontInput(fonts: InputFontConfig[]): ResolvedFontConfig[] {
return fonts.map((f) => {
if (typeof f === 'string') {
const [name, weight] = f.split(':')
const vals = f.split(':')
const includesStyle = vals.length === 3
let name, weight, style
if (includesStyle) {
name = vals[0]
style = vals[1]
weight = vals[2]
}
else {
name = vals[0]
weight = vals[1]
}
return <ResolvedFontConfig> {
cacheKey: f,
name,
weight: weight || 400,
style: 'normal',
style: style || 'normal',
path: undefined,
}
}
return <ResolvedFontConfig> {
cacheKey: f.key || `${f.name}:${f.weight}`,
cacheKey: f.key || `${f.name}:${f.style}:${f.weight}`,
style: 'normal',
weight: 400,
...f,
Expand Down
17 changes: 14 additions & 3 deletions src/runtime/pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,28 @@ export function separateProps(options: OgImageOptions | undefined, ignoreKeys: s
export function normaliseFontInput(fonts: InputFontConfig[]): ResolvedFontConfig[] {
return fonts.map((f) => {
if (typeof f === 'string') {
const [name, weight] = f.split(':')
const vals = f.split(':')
const includesStyle = vals.length === 3
let name, weight, style
if (includesStyle) {
name = vals[0]
style = vals[1]
weight = vals[2]
}
else {
name = vals[0]
weight = vals[1]
}
return <ResolvedFontConfig> {
cacheKey: f,
name,
weight: weight || 400,
style: 'normal',
style: style || 'normal',
path: undefined,
}
}
return <ResolvedFontConfig> {
cacheKey: f.key || `${f.name}:${f.weight}`,
cacheKey: f.key || `${f.name}:${f.style}:${f.weight}`,
style: 'normal',
weight: 400,
...f,
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/server/og-image/templates/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function html(ctx: OgImageRenderEventContext) {
const normalisedFonts = normaliseFontInput([...options.fonts || [], ...fonts])
const firstFont = normalisedFonts[0] as FontConfig
if (firstFont)
defaultFontFamily = firstFont.name
defaultFontFamily = firstFont.name.replaceAll('+', ' ')

await applyEmojis(ctx, island)
let html = island.html
Expand All @@ -37,7 +37,7 @@ export async function html(ctx: OgImageRenderEventContext) {
style: [
{
// default font is the first font family
innerHTML: `body { font-family: \'${defaultFontFamily.replace('+', ' ')}\', sans-serif; }`,
innerHTML: `body { font-family: \'${defaultFontFamily}\', sans-serif; }`,
},
{
innerHTML: `body {
Expand Down Expand Up @@ -73,7 +73,7 @@ svg[data-emoji] {
.map((font) => {
return `
@font-face {
font-family: '${font.name}';
font-family: '${font.name.replaceAll('+', ' ')}';
font-style: normal;
font-weight: ${font.weight};
src: url('/__og-image__/font/${font.key}') format('truetype');
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,15 @@ export interface OgImageOptions<T extends keyof OgImageComponents = 'NuxtSeo'> {

export interface FontConfig {
name: string
style?: string
style?: 'normal' | 'ital'
weight?: string | number
path?: string
key?: string
absolutePath?: boolean
}
export interface ResolvedFontConfig extends FontConfig { cacheKey: string, data?: BufferSource }

export type InputFontConfig = (`${string}:${number}` | string | FontConfig)
export type InputFontConfig = (`${string}:${number}` | `${string}:${'normal' | 'ital'}:${number}` | string | FontConfig)

export interface RuntimeCompatibilitySchema {
chromium: 'chrome-launcher' | 'on-demand' | 'playwright' | false
Expand Down
6 changes: 3 additions & 3 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ export async function checkPlaywrightDependency() {
}

export async function downloadFont(font: ResolvedFontConfig, storage: Storage, mirror?: true | string) {
const { name, weight } = font
const key = `${name}-${weight}.ttf.base64`
const { name, weight, style } = font
const key = `${name}-${style}-${weight}.ttf.base64`
if (await storage.hasItem(key))
return true

const host = typeof mirror === 'undefined' ? 'fonts.googleapis.com' : mirror === true ? 'fonts.font.im' : mirror
// using H3Event $fetch will cause the request headers not to be sent
const css = await $fetch(`https://${host}/css2?family=${name}:wght@${weight}`, {
const css = await $fetch(`https://${host}/css2?family=${name}:${style === 'ital' ? `ital,wght@1,${weight}` : `wght@${weight}`}`, {
timeout: 10 * 1000, // 10 second timeout
headers: {
// Make sure it returns TTF.
Expand Down

0 comments on commit 8c8cb97

Please sign in to comment.