diff --git a/app/api/fonts/route.ts b/app/api/fonts/route.ts index 1d69a55..67bfe97 100644 --- a/app/api/fonts/route.ts +++ b/app/api/fonts/route.ts @@ -49,7 +49,7 @@ export async function GET(request: Request) { const files = fs.readdirSync(FONTS_DIR); const list = files .filter(f => /\.(woff2?|ttf|otf)$/i.test(f)) - .filter(f => !/italic/i.test(f)) + .filter(f => !/italic|bold/i.test(f)) .sort(); return NextResponse.json(list); } catch { diff --git a/app/layout.tsx b/app/layout.tsx index 23df603..791629d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -22,15 +22,16 @@ function fontFormat(ext: string): string { } } -async function findItalicSibling(regularFilename: string): Promise { +async function findSibling(regularFilename: string, suffix: string): Promise { const m = regularFilename.match(/^(.*)(\.(?:woff2?|ttf|otf))$/i); if (!m) return null; const [, base, ext] = m; + const lower = suffix.toLowerCase(); const candidates = [ - `${base}-Italic${ext}`, `${base}-italic${ext}`, - `${base}_Italic${ext}`, `${base}_italic${ext}`, - `${base} Italic${ext}`, `${base} italic${ext}`, - `${base}Italic${ext}`, `${base}italic${ext}`, + `${base}-${suffix}${ext}`, `${base}-${lower}${ext}`, + `${base}_${suffix}${ext}`, `${base}_${lower}${ext}`, + `${base} ${suffix}${ext}`, `${base} ${lower}${ext}`, + `${base}${suffix}${ext}`, `${base}${lower}${ext}`, ]; try { const files = await fs.readdir(path.join(process.cwd(), "data", "fonts")); @@ -54,27 +55,27 @@ export default async function RootLayout({ let fontStyleCss = ""; if (chosenFont) { - const regularExt = path.extname(chosenFont); - const italicFile = await findItalicSibling(chosenFont); - const italicExt = italicFile ? path.extname(italicFile) : ""; - const regularUrl = `/api/fonts?name=${encodeURIComponent(chosenFont)}`; - const italicUrl = italicFile ? `/api/fonts?name=${encodeURIComponent(italicFile)}` : ""; - - fontStyleCss = ` -@font-face { - font-family: 'PortalFont'; - src: url('${regularUrl}') format('${fontFormat(regularExt)}'); - font-style: normal; - font-display: swap; -}${italicFile ? ` + const fontUrl = (name: string) => `/api/fonts?name=${encodeURIComponent(name)}`; + const faceBlock = (file: string, weight: 400 | 700, style: 'normal' | 'italic') => ` @font-face { font-family: 'PortalFont'; - src: url('${italicUrl}') format('${fontFormat(italicExt)}'); - font-style: italic; + src: url('${fontUrl(file)}') format('${fontFormat(path.extname(file))}'); + font-style: ${style}; + font-weight: ${weight}; font-display: swap; -}` : ""} -body { font-family: 'PortalFont', Arial, Helvetica, sans-serif; } -`; +}`; + + const italicFile = await findSibling(chosenFont, 'Italic'); + const boldFile = await findSibling(chosenFont, 'Bold'); + const boldItalicFile = await findSibling(chosenFont, 'BoldItalic'); + + fontStyleCss = [ + faceBlock(chosenFont, 400, 'normal'), + italicFile ? faceBlock(italicFile, 400, 'italic') : '', + boldFile ? faceBlock(boldFile, 700, 'normal') : '', + boldItalicFile ? faceBlock(boldItalicFile, 700, 'italic') : '', + `\nbody { font-family: 'PortalFont', Arial, Helvetica, sans-serif; }\n`, + ].join(''); } return ( diff --git a/data/fonts/Inter-Bold.woff2 b/data/fonts/Inter-Bold.woff2 new file mode 100644 index 0000000..d15208d Binary files /dev/null and b/data/fonts/Inter-Bold.woff2 differ diff --git a/data/fonts/Inter-BoldItalic.woff2 b/data/fonts/Inter-BoldItalic.woff2 new file mode 100644 index 0000000..2f06c65 Binary files /dev/null and b/data/fonts/Inter-BoldItalic.woff2 differ diff --git a/data/fonts/Inter-Italic.woff2 b/data/fonts/Inter-Italic.woff2 new file mode 100644 index 0000000..2f06c65 Binary files /dev/null and b/data/fonts/Inter-Italic.woff2 differ diff --git a/data/fonts/Inter.woff2 b/data/fonts/Inter.woff2 new file mode 100644 index 0000000..d15208d Binary files /dev/null and b/data/fonts/Inter.woff2 differ diff --git a/data/fonts/Lora-Bold.woff2 b/data/fonts/Lora-Bold.woff2 new file mode 100644 index 0000000..918a477 Binary files /dev/null and b/data/fonts/Lora-Bold.woff2 differ diff --git a/data/fonts/Lora-BoldItalic.woff2 b/data/fonts/Lora-BoldItalic.woff2 new file mode 100644 index 0000000..856707d Binary files /dev/null and b/data/fonts/Lora-BoldItalic.woff2 differ diff --git a/data/fonts/Lora-Italic.woff2 b/data/fonts/Lora-Italic.woff2 new file mode 100644 index 0000000..856707d Binary files /dev/null and b/data/fonts/Lora-Italic.woff2 differ diff --git a/data/fonts/Lora.woff2 b/data/fonts/Lora.woff2 new file mode 100644 index 0000000..918a477 Binary files /dev/null and b/data/fonts/Lora.woff2 differ diff --git a/data/fonts/Merriweather-Bold.woff2 b/data/fonts/Merriweather-Bold.woff2 new file mode 100644 index 0000000..72d1433 Binary files /dev/null and b/data/fonts/Merriweather-Bold.woff2 differ diff --git a/data/fonts/Merriweather-BoldItalic.woff2 b/data/fonts/Merriweather-BoldItalic.woff2 new file mode 100644 index 0000000..62cb9a1 Binary files /dev/null and b/data/fonts/Merriweather-BoldItalic.woff2 differ diff --git a/data/fonts/Merriweather-Italic.woff2 b/data/fonts/Merriweather-Italic.woff2 new file mode 100644 index 0000000..62cb9a1 Binary files /dev/null and b/data/fonts/Merriweather-Italic.woff2 differ diff --git a/data/fonts/Merriweather.woff2 b/data/fonts/Merriweather.woff2 new file mode 100644 index 0000000..72d1433 Binary files /dev/null and b/data/fonts/Merriweather.woff2 differ diff --git a/data/fonts/OpenSans-Bold.woff2 b/data/fonts/OpenSans-Bold.woff2 new file mode 100644 index 0000000..b5d54e7 Binary files /dev/null and b/data/fonts/OpenSans-Bold.woff2 differ diff --git a/data/fonts/OpenSans-BoldItalic.woff2 b/data/fonts/OpenSans-BoldItalic.woff2 new file mode 100644 index 0000000..5de3fea Binary files /dev/null and b/data/fonts/OpenSans-BoldItalic.woff2 differ diff --git a/data/fonts/OpenSans-Italic.woff2 b/data/fonts/OpenSans-Italic.woff2 new file mode 100644 index 0000000..5de3fea Binary files /dev/null and b/data/fonts/OpenSans-Italic.woff2 differ diff --git a/data/fonts/OpenSans.woff2 b/data/fonts/OpenSans.woff2 new file mode 100644 index 0000000..b5d54e7 Binary files /dev/null and b/data/fonts/OpenSans.woff2 differ diff --git a/data/fonts/PlayfairDisplay-Bold.woff2 b/data/fonts/PlayfairDisplay-Bold.woff2 new file mode 100644 index 0000000..5a3fbbd Binary files /dev/null and b/data/fonts/PlayfairDisplay-Bold.woff2 differ diff --git a/data/fonts/PlayfairDisplay-BoldItalic.woff2 b/data/fonts/PlayfairDisplay-BoldItalic.woff2 new file mode 100644 index 0000000..8ae1f1f Binary files /dev/null and b/data/fonts/PlayfairDisplay-BoldItalic.woff2 differ diff --git a/data/fonts/PlayfairDisplay-Italic.woff2 b/data/fonts/PlayfairDisplay-Italic.woff2 new file mode 100644 index 0000000..8ae1f1f Binary files /dev/null and b/data/fonts/PlayfairDisplay-Italic.woff2 differ diff --git a/data/fonts/PlayfairDisplay.woff2 b/data/fonts/PlayfairDisplay.woff2 new file mode 100644 index 0000000..5a3fbbd Binary files /dev/null and b/data/fonts/PlayfairDisplay.woff2 differ diff --git a/data/fonts/Roboto-Bold.woff2 b/data/fonts/Roboto-Bold.woff2 new file mode 100644 index 0000000..9b0f141 Binary files /dev/null and b/data/fonts/Roboto-Bold.woff2 differ diff --git a/data/fonts/Roboto-BoldItalic.woff2 b/data/fonts/Roboto-BoldItalic.woff2 new file mode 100644 index 0000000..ec15ce7 Binary files /dev/null and b/data/fonts/Roboto-BoldItalic.woff2 differ diff --git a/data/fonts/Roboto-Italic.woff2 b/data/fonts/Roboto-Italic.woff2 new file mode 100644 index 0000000..ec15ce7 Binary files /dev/null and b/data/fonts/Roboto-Italic.woff2 differ diff --git a/data/fonts/Roboto.woff2 b/data/fonts/Roboto.woff2 new file mode 100644 index 0000000..9b0f141 Binary files /dev/null and b/data/fonts/Roboto.woff2 differ diff --git a/data/fonts/WorkSans-Bold.woff2 b/data/fonts/WorkSans-Bold.woff2 new file mode 100644 index 0000000..3a70b25 Binary files /dev/null and b/data/fonts/WorkSans-Bold.woff2 differ diff --git a/data/fonts/WorkSans-BoldItalic.woff2 b/data/fonts/WorkSans-BoldItalic.woff2 new file mode 100644 index 0000000..804eb12 Binary files /dev/null and b/data/fonts/WorkSans-BoldItalic.woff2 differ diff --git a/data/fonts/WorkSans-Italic.woff2 b/data/fonts/WorkSans-Italic.woff2 new file mode 100644 index 0000000..804eb12 Binary files /dev/null and b/data/fonts/WorkSans-Italic.woff2 differ diff --git a/data/fonts/WorkSans.woff2 b/data/fonts/WorkSans.woff2 new file mode 100644 index 0000000..3a70b25 Binary files /dev/null and b/data/fonts/WorkSans.woff2 differ diff --git a/scripts/download-fonts.ps1 b/scripts/download-fonts.ps1 new file mode 100644 index 0000000..c766be4 --- /dev/null +++ b/scripts/download-fonts.ps1 @@ -0,0 +1,50 @@ +$ErrorActionPreference = 'Stop' + +$ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" +$fontsDir = Join-Path $PSScriptRoot "..\data\fonts" +$fontsDir = (Resolve-Path $fontsDir).Path + +$families = @( + @{ name = 'Inter'; query = 'Inter' }, + @{ name = 'Lora'; query = 'Lora' }, + @{ name = 'Merriweather'; query = 'Merriweather' }, + @{ name = 'OpenSans'; query = 'Open+Sans' }, + @{ name = 'PlayfairDisplay'; query = 'Playfair+Display' }, + @{ name = 'Roboto'; query = 'Roboto' }, + @{ name = 'WorkSans'; query = 'Work+Sans' } +) + +foreach ($f in $families) { + $cssUrl = "https://fonts.googleapis.com/css2?family=$($f.query):ital,wght@0,400;0,700;1,400;1,700&display=swap" + Write-Host "=== $($f.name) ===" -ForegroundColor Cyan + Write-Host "CSS: $cssUrl" + + $css = (Invoke-WebRequest -Uri $cssUrl -UserAgent $ua -UseBasicParsing).Content + + $pattern = '/\*\s*latin\s*\*/\s*@font-face\s*\{([^}]+)\}' + $matches = [regex]::Matches($css, $pattern) + Write-Host "Found $($matches.Count) latin @font-face blocks" + + foreach ($m in $matches) { + $block = $m.Groups[1].Value + $weight = [regex]::Match($block, 'font-weight:\s*(\d+)').Groups[1].Value + $style = [regex]::Match($block, 'font-style:\s*(\w+)').Groups[1].Value + $url = [regex]::Match($block, 'url\(([^)]+\.woff2)\)').Groups[1].Value + + if (-not $url) { Write-Host " skip block: no woff2 url"; continue } + + $suffix = '' + if ($style -eq 'italic' -and $weight -eq '700') { $suffix = '-BoldItalic' } + elseif ($style -eq 'italic') { $suffix = '-Italic' } + elseif ($weight -eq '700') { $suffix = '-Bold' } + + $outFile = Join-Path $fontsDir "$($f.name)$suffix.woff2" + Invoke-WebRequest -Uri $url -OutFile $outFile -UserAgent $ua -UseBasicParsing + $size = (Get-Item $outFile).Length + Write-Host (" -> {0} ({1} bytes)" -f (Split-Path $outFile -Leaf), $size) + } +} + +Write-Host "`nDone." -ForegroundColor Green +Write-Host "Files in ${fontsDir}:" +Get-ChildItem $fontsDir -Filter '*.woff2' | Sort-Object Name | ForEach-Object { Write-Host (" {0,-30} {1,8} bytes" -f $_.Name, $_.Length) }