From 100efdde0704d300f5c6848f3f422a0e66ba5c6c 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] 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 | 73 ++++++++++++++----- .../src/next_font/local/font_fallback.rs | 2 +- 4 files changed, 58 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..b4848e87f886 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,15 @@ use crate::{ #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub(super) struct FontMetricsMapEntry { + family_name: String, category: String, + cap_height: i32, ascent: i32, descent: i32, line_gap: u32, units_per_em: u32, - az_avg_width: f64, + x_height: i32, + x_width_avg: f64, } #[derive(Deserialize)] @@ -53,7 +58,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 +106,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 +148,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 +186,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 +206,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 +221,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 +241,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,