From 5fe51ad4dcc98620e035549a0ebdf66af7652e4f Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Fri, 28 Apr 2023 11:16:58 -0700 Subject: [PATCH 1/3] feat(next-core): support capsize for google font fallback --- packages/next-swc/crates/napi/Cargo.toml | 1 + .../next-core/src/next_font/font_fallback.rs | 6 +- .../src/next_font/google/font_fallback.rs | 76 ++++++++++++++----- .../src/next_font/local/font_fallback.rs | 2 +- 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/packages/next-swc/crates/napi/Cargo.toml b/packages/next-swc/crates/napi/Cargo.toml index 1448ecd91e2c..6e566709a23d 100644 --- a/packages/next-swc/crates/napi/Cargo.toml +++ b/packages/next-swc/crates/napi/Cargo.toml @@ -22,6 +22,7 @@ rustls-tls = ["next-dev/rustls-tls"] # Internal only. Enabled when building for the Next.js integration test suite. __internal_nextjs_integration_test = [ + "next-core/__internal_nextjs_integration_test", "next-dev/__internal_nextjs_integration_test", "next-dev/serializable" ] diff --git a/packages/next-swc/crates/next-core/src/next_font/font_fallback.rs b/packages/next-swc/crates/next-core/src/next_font/font_fallback.rs index eebb657adb72..f35e68fab74f 100644 --- a/packages/next-swc/crates/next-core/src/next_font/font_fallback.rs +++ b/packages/next-swc/crates/next-core/src/next_font/font_fallback.rs @@ -8,7 +8,7 @@ use turbo_binding::turbo::tasks::{ pub(crate) struct DefaultFallbackFont { pub name: String, - pub az_avg_width: f64, + pub x_width_avg: f64, pub units_per_em: u32, } @@ -16,14 +16,14 @@ pub(crate) struct DefaultFallbackFont { pub(crate) static DEFAULT_SANS_SERIF_FONT: Lazy = Lazy::new(|| DefaultFallbackFont { name: "Arial".to_owned(), - az_avg_width: 934.5116279069767, + x_width_avg: 934.5116279069767, units_per_em: 2048, }); pub(crate) static DEFAULT_SERIF_FONT: Lazy = Lazy::new(|| DefaultFallbackFont { name: "Times New Roman".to_owned(), - az_avg_width: 854.3953488372093, + x_width_avg: 854.3953488372093, units_per_em: 2048, }); diff --git a/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs b/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs index d7871c93a70a..afd7730de243 100644 --- a/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs +++ b/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use anyhow::{Context, Result}; +use once_cell::sync::Lazy; +use regex::Regex; use serde::{Deserialize, Serialize}; use turbo_binding::{turbo::tasks_fs::FileSystemPathVc, turbopack::core::issue::IssueSeverity}; use turbo_tasks::{ @@ -25,12 +27,18 @@ use crate::{ #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub(super) struct FontMetricsMapEntry { + #[allow(unused)] + family_name: String, category: String, + #[allow(unused)] + cap_height: i32, ascent: i32, descent: i32, line_gap: u32, units_per_em: u32, - az_avg_width: f64, + #[allow(unused)] + x_height: i32, + x_width_avg: f64, } #[derive(Deserialize)] @@ -53,7 +61,7 @@ pub(super) async fn get_font_fallback( Some(fallback) => FontFallback::Manual(StringsVc::cell(fallback.clone())).cell(), None => { let metrics_json = - load_next_json(context, "/dist/server/google-font-metrics.json").await; + load_next_json(context, "/dist/server/capsize-font-metrics.json").await; match metrics_json { Ok(metrics_json) => { let fallback = lookup_fallback( @@ -101,14 +109,38 @@ pub(super) async fn get_font_fallback( }) } +static FALLBACK_FONT_NAME: Lazy = Lazy::new(|| Regex::new(r"(?:^\w|[A-Z]|\b\w)").unwrap()); + +fn format_fallback_font_name(font_family: &str) -> String { + let mut fallback_name = FALLBACK_FONT_NAME + .replace(font_family, |caps: ®ex::Captures| { + caps.iter() + .enumerate() + .map(|(i, font_matches)| { + let font_matches = font_matches.unwrap().as_str(); + if i == 0 { + font_matches.to_lowercase() + } else { + font_matches.to_uppercase() + } + }) + .collect::>() + .join("") + }) + .to_string(); + fallback_name.retain(|c| !c.is_whitespace()); + fallback_name +} + fn lookup_fallback( font_family: &str, font_metrics_map: FontMetricsMap, adjust: bool, ) -> Result { + let font_family = format_fallback_font_name(font_family); let metrics = font_metrics_map .0 - .get(font_family) + .get(&font_family) .context("Font not found in metrics")?; let fallback = if metrics.category == "serif" { @@ -119,9 +151,9 @@ fn lookup_fallback( let metrics = if adjust { // Derived from - // https://github.com/vercel/next.js/blob/a3893bf69c83fb08e88c87bf8a21d987a0448c8e/packages/next/src/server/font-utils.ts#L121 - let main_font_avg_width = metrics.az_avg_width / metrics.units_per_em as f64; - let fallback_font_avg_width = fallback.az_avg_width / fallback.units_per_em as f64; + // https://github.com/vercel/next.js/blob/7bfd5829999b1d203e447d30de7e29108c31934a/packages/next/src/server/font-utils.ts#L131 + let main_font_avg_width = metrics.x_width_avg / metrics.units_per_em as f64; + let fallback_font_avg_width = fallback.x_width_avg / fallback.units_per_em as f64; let size_adjust = main_font_avg_width / fallback_font_avg_width; let ascent = metrics.ascent as f64 / (metrics.units_per_em as f64 * size_adjust); @@ -157,15 +189,17 @@ mod tests { let font_metrics: FontMetricsMap = parse_json_with_source_context( r#" { - "Inter": { + "inter": { + "familyName": "Inter", "category": "sans-serif", + "capHeight": 2048, "ascent": 2728, "descent": -680, "lineGap": 0, - "xAvgCharWidth": 1838, "unitsPerEm": 2816, - "azAvgWidth": 1383.0697674418604 - } + "xHeight": 1536, + "xWidthAvg": 1335 + } } "#, )?; @@ -175,10 +209,10 @@ mod tests { Fallback { font_family: "Arial".to_owned(), adjustment: Some(FontAdjustment { - ascent: 0.9000259575934895, - descent: -0.2243466463209578, + ascent: 0.9324334770490376, + descent: -0.23242476700635833, line_gap: 0.0, - size_adjust: 1.0763578448229054 + size_adjust: 1.0389481114147647 }) } ); @@ -190,15 +224,17 @@ mod tests { let font_metrics: FontMetricsMap = parse_json_with_source_context( r#" { - "Roboto Slab": { + "robotoSlab": { + "familyName": "Roboto Slab", "category": "serif", + "capHeight": 1456, "ascent": 2146, "descent": -555, "lineGap": 0, - "xAvgCharWidth": 1239, "unitsPerEm": 2048, - "azAvgWidth": 1005.6279069767442 - } + "xHeight": 1082, + "xWidthAvg": 969 + } } "#, )?; @@ -208,10 +244,10 @@ mod tests { Fallback { font_family: "Times New Roman".to_owned(), adjustment: Some(FontAdjustment { - ascent: 0.8902691493151913, - descent: -0.23024202137461844, + ascent: 0.9239210539440684, + descent: -0.23894510015794873, line_gap: 0.0, - size_adjust: 1.1770053621492147 + size_adjust: 1.134135387462914 }) } ); diff --git a/packages/next-swc/crates/next-core/src/next_font/local/font_fallback.rs b/packages/next-swc/crates/next-core/src/next_font/local/font_fallback.rs index 2801b9ea4b3e..4b35bb4f8f42 100644 --- a/packages/next-swc/crates/next-core/src/next_font/local/font_fallback.rs +++ b/packages/next-swc/crates/next-core/src/next_font/local/font_fallback.rs @@ -106,7 +106,7 @@ async fn get_font_adjustment( ))? .units_per_em as f64; - let fallback_avg_width = fallback_font.az_avg_width / fallback_font.units_per_em as f64; + let fallback_avg_width = fallback_font.x_width_avg / fallback_font.units_per_em as f64; let size_adjust = match az_avg_width { Some(az_avg_width) => az_avg_width as f64 / units_per_em / fallback_avg_width, None => 1.0, From a9a5d969cb8d09344936302c3d979b8df62067e5 Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Mon, 1 May 2023 09:27:22 -0700 Subject: [PATCH 2/3] test(next-dev): update snapshot --- .../next/font-google/at-next-font/input/pages/index.js | 2 +- .../next/font-google/basic/input/pages/index.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/at-next-font/input/pages/index.js b/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/at-next-font/input/pages/index.js index 01afb37bb8f5..5782364fcd6a 100644 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/at-next-font/input/pages/index.js +++ b/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/at-next-font/input/pages/index.js @@ -17,7 +17,7 @@ function runTests() { expect(interNoArgs).toEqual({ className: 'className__inter_34ab8b4d__7bdff866', style: { - fontFamily: "'__Inter_34ab8b', '__Inter_Fallback_34ab8b'", + fontFamily: "'__Inter_34ab8b'", fontStyle: 'normal', }, }) diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/basic/input/pages/index.js b/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/basic/input/pages/index.js index 8535c678f7f6..a9226077eaf3 100644 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/basic/input/pages/index.js +++ b/packages/next-swc/crates/next-dev-tests/tests/integration/next/font-google/basic/input/pages/index.js @@ -20,7 +20,7 @@ function runTests() { expect(interNoArgs).toEqual({ className: 'className__inter_34ab8b4d__7bdff866', style: { - fontFamily: "'__Inter_34ab8b', '__Inter_Fallback_34ab8b'", + fontFamily: "'__Inter_34ab8b'", fontStyle: 'normal', }, }) @@ -29,9 +29,7 @@ function runTests() { it('includes a rule styling the exported className', async () => { const matchingRule = await getRuleMatchingClassName(interNoArgs.className) expect(matchingRule).toBeTruthy() - expect(matchingRule.style.fontFamily).toEqual( - '__Inter_34ab8b, __Inter_Fallback_34ab8b' - ) + expect(matchingRule.style.fontFamily).toEqual('__Inter_34ab8b') expect(matchingRule.style.fontStyle).toEqual('normal') }) @@ -39,7 +37,7 @@ function runTests() { expect(interWithVariableName).toEqual({ className: 'className__inter_c6e282f1__e152ac0c', style: { - fontFamily: "'__Inter_c6e282', '__Inter_Fallback_c6e282'", + fontFamily: "'__Inter_c6e282'", fontStyle: 'normal', }, variable: 'variable__inter_c6e282f1__e152ac0c', @@ -49,7 +47,7 @@ function runTests() { interWithVariableName.variable ) expect(matchingRule.styleMap.get('--my-font').toString().trim()).toBe( - '"__Inter_c6e282", "__Inter_Fallback_c6e282"' + '"__Inter_c6e282"' ) }) } From f9ddf4b9bd695a26ca9b6d3e63d6bf8f4b7e7aba Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Mon, 1 May 2023 09:49:30 -0700 Subject: [PATCH 3/3] doc(font_fallback): add comments --- .../crates/next-core/src/next_font/google/font_fallback.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs b/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs index afd7730de243..36eb51ddff6b 100644 --- a/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs +++ b/packages/next-swc/crates/next-core/src/next_font/google/font_fallback.rs @@ -111,6 +111,7 @@ pub(super) async fn get_font_fallback( static FALLBACK_FONT_NAME: Lazy = Lazy::new(|| Regex::new(r"(?:^\w|[A-Z]|\b\w)").unwrap()); +// From https://github.com/vercel/next.js/blob/1628260b88ce3052ac307a1607b6e8470188ab83/packages/next/src/server/font-utils.ts#L101 fn format_fallback_font_name(font_family: &str) -> String { let mut fallback_name = FALLBACK_FONT_NAME .replace(font_family, |caps: ®ex::Captures| {