Implement automatic font fallback support for next/font/local (#47463)

Fixes WEB-787.

This implements automatic font fallbacks for `next/font/local`. It uses the `allsorts` library to query font metrics across woff and ttf fonts.

Test Plan: Now passes 24 Next.js integration tests, up from 14.
This commit is contained in:
Will Binns-Smith 2023-03-27 18:22:48 -07:00 committed by GitHub
parent 38953adb11
commit 56ebe97ae4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 558 additions and 8 deletions

View file

@ -48,6 +48,55 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "aliasable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allsorts"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e926a9819dcf2211da0c19f5ca06a8f5c883e3bdb5ccc51afead3a7d995f023"
dependencies = [
"bitflags 1.3.2",
"bitreader",
"brotli-decompressor",
"byteorder",
"encoding_rs",
"flate2",
"glyph-names",
"itertools",
"lazy_static",
"libc",
"log",
"num-traits",
"ouroboros",
"pathfinder_geometry",
"rustc-hash",
"tinyvec",
"ucd-trie",
"unicode-canonical-combining-class",
"unicode-general-category",
"unicode-joining-type",
]
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@ -486,6 +535,15 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
[[package]]
name = "bitreader"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d84ea71c85d1fe98fe67a9b9988b1695bc24c0b0d3bfb18d4c510f44b4b09941"
dependencies = [
"cfg-if 1.0.0",
]
[[package]] [[package]]
name = "blake3" name = "blake3"
version = "1.3.3" version = "1.3.3"
@ -523,6 +581,16 @@ dependencies = [
"futures-lite", "futures-lite",
] ]
[[package]]
name = "brotli-decompressor"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]] [[package]]
name = "browserslist-rs" name = "browserslist-rs"
version = "0.12.3" version = "0.12.3"
@ -1611,6 +1679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"libz-sys",
"miniz_oxide", "miniz_oxide",
] ]
@ -1906,6 +1975,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "glyph-names"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3531d702d6c1a3ba92a5fb55a404c7b8c476c8e7ca249951077afcbe4bc807f"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.16" version = "0.3.16"
@ -3076,6 +3151,7 @@ dependencies = [
name = "next-core" name = "next-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"allsorts",
"anyhow", "anyhow",
"auto-hash-map", "auto-hash-map",
"futures", "futures",
@ -3486,6 +3562,29 @@ version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
[[package]]
name = "ouroboros"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db"
dependencies = [
"aliasable",
"ouroboros_macro",
]
[[package]]
name = "ouroboros_macro"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7"
dependencies = [
"Inflector",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "output_vt100" name = "output_vt100"
version = "0.1.3" version = "0.1.3"
@ -3563,6 +3662,25 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "pathfinder_geometry"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
dependencies = [
"log",
"pathfinder_simd",
]
[[package]]
name = "pathfinder_simd"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
dependencies = [
"rustc_version 0.3.3",
]
[[package]] [[package]]
name = "patricia_tree" name = "patricia_tree"
version = "0.5.5" version = "0.5.5"
@ -4205,6 +4323,15 @@ dependencies = [
"semver 0.9.0", "semver 0.9.0",
] ]
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver 0.11.0",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@ -4362,7 +4489,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [ dependencies = [
"semver-parser", "semver-parser 0.7.0",
]
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser 0.10.2",
] ]
[[package]] [[package]]
@ -4380,6 +4516,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]] [[package]]
name = "sentry" name = "sentry"
version = "0.27.0" version = "0.27.0"
@ -7176,6 +7321,18 @@ version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-canonical-combining-class"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6925586af9268182c711e47c0853ed84131049efaca41776d0ca97f983865c32"
[[package]]
name = "unicode-general-category"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7"
[[package]] [[package]]
name = "unicode-id" name = "unicode-id"
version = "0.3.3" version = "0.3.3"
@ -7188,6 +7345,12 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-joining-type"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f8cb47ccb8bc750808755af3071da4a10dcd147b68fc874b7ae4b12543f6f5"
[[package]] [[package]]
name = "unicode-linebreak" name = "unicode-linebreak"
version = "0.1.4" version = "0.1.4"

View file

@ -91,6 +91,8 @@ chromiumoxide = { version = "0.4.0", features = [
# sync with chromiumoxide's tungstenite requirement. # sync with chromiumoxide's tungstenite requirement.
tungstenite = "0.17.3" tungstenite = "0.17.3"
# flate2_zlib requires zlib, use flate2_rust
allsorts = { version = "0.14.0", default_features = false, features = ["outline", "flate2_rust"] }
anyhow = "1.0.69" anyhow = "1.0.69"
assert_cmd = "2.0.8" assert_cmd = "2.0.8"
async-compression = { version = "0.3.13", default-features = false, features = [ async-compression = { version = "0.3.13", default-features = false, features = [

View file

@ -9,6 +9,7 @@ edition = "2021"
bench = false bench = false
[dependencies] [dependencies]
allsorts = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
auto-hash-map = { workspace = true } auto-hash-map = { workspace = true }
futures = { workspace = true } futures = { workspace = true }

View file

@ -1,14 +1,32 @@
use anyhow::Result; use allsorts::{
font_data::{DynamicFontTableProvider, FontData},
Font,
};
use anyhow::{bail, Context, Result};
use turbo_tasks::primitives::{StringVc, StringsVc, U32Vc}; use turbo_tasks::primitives::{StringVc, StringsVc, U32Vc};
use turbo_tasks_fs::{FileContent, FileSystemPathVc};
use super::{options::NextFontLocalOptionsVc, request::AdjustFontFallback}; use super::{
options::{FontDescriptor, FontDescriptors, FontWeight, NextFontLocalOptionsVc},
request::AdjustFontFallback,
};
use crate::next_font::{ use crate::next_font::{
font_fallback::{AutomaticFontFallback, FontFallback, FontFallbacksVc}, font_fallback::{
AutomaticFontFallback, DefaultFallbackFont, FontAdjustment, FontFallback, FontFallbacksVc,
DEFAULT_SANS_SERIF_FONT, DEFAULT_SERIF_FONT,
},
util::{get_scoped_font_family, FontFamilyType}, util::{get_scoped_font_family, FontFamilyType},
}; };
// From
// https://github.com/vercel/next.js/blob/7457be0c74e64b4d0617943ed27f4d557cc916be/packages/font/src/local/get-fallback-metrics-from-font-file.ts#L34
static AVG_CHARACTERS: &str = "aaabcdeeeefghiijklmnnoopqrrssttuvwxyz ";
static NORMAL_WEIGHT: f64 = 400.0;
static BOLD_WEIGHT: f64 = 700.0;
#[turbo_tasks::function] #[turbo_tasks::function]
pub(super) async fn get_font_fallbacks( pub(super) async fn get_font_fallbacks(
context: FileSystemPathVc,
options_vc: NextFontLocalOptionsVc, options_vc: NextFontLocalOptionsVc,
request_hash: U32Vc, request_hash: U32Vc,
) -> Result<FontFallbacksVc> { ) -> Result<FontFallbacksVc> {
@ -26,7 +44,9 @@ pub(super) async fn get_font_fallbacks(
AutomaticFontFallback { AutomaticFontFallback {
scoped_font_family, scoped_font_family,
local_font_family: StringVc::cell("Arial".to_owned()), local_font_family: StringVc::cell("Arial".to_owned()),
adjustment: None, adjustment: Some(
get_font_adjustment(context, options_vc, &DEFAULT_SANS_SERIF_FONT).await?,
),
} }
.cell(), .cell(),
) )
@ -37,7 +57,9 @@ pub(super) async fn get_font_fallbacks(
AutomaticFontFallback { AutomaticFontFallback {
scoped_font_family, scoped_font_family,
local_font_family: StringVc::cell("Times New Roman".to_owned()), local_font_family: StringVc::cell("Times New Roman".to_owned()),
adjustment: None, adjustment: Some(
get_font_adjustment(context, options_vc, &DEFAULT_SERIF_FONT).await?,
),
} }
.cell(), .cell(),
) )
@ -52,3 +74,365 @@ pub(super) async fn get_font_fallbacks(
Ok(FontFallbacksVc::cell(font_fallbacks)) Ok(FontFallbacksVc::cell(font_fallbacks))
} }
async fn get_font_adjustment(
context: FileSystemPathVc,
options: NextFontLocalOptionsVc,
fallback_font: &DefaultFallbackFont,
) -> Result<FontAdjustment> {
let options = &*options.await?;
let main_descriptor = pick_font_for_fallback_generation(&options.fonts)?;
let font_file = &*context.join(&main_descriptor.path).read().await?;
let font_file_rope = match font_file {
FileContent::NotFound => bail!("Expected font file content"),
FileContent::Content(file) => file.content(),
};
let font_file_binary = font_file_rope.to_bytes()?;
let scope = allsorts::binary::read::ReadScope::new(&font_file_binary);
let mut font = Font::new(scope.read::<FontData>()?.table_provider(0)?)?.context(format!(
"Unable to read font metrics from font file at {}",
&main_descriptor.path,
))?;
let az_avg_width = calc_average_width(&mut font);
let units_per_em = font
.head_table()?
.context(format!(
"Unable to read font scale from font file at {}",
&main_descriptor.path
))?
.units_per_em as f64;
let fallback_avg_width = fallback_font.az_avg_width / 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,
};
Ok(FontAdjustment {
ascent: font.hhea_table.ascender as f64 / (units_per_em * size_adjust),
descent: font.hhea_table.descender as f64 / (units_per_em * size_adjust),
line_gap: font.hhea_table.line_gap as f64 / (units_per_em * size_adjust),
size_adjust,
})
}
fn calc_average_width(font: &mut Font<DynamicFontTableProvider>) -> Option<f32> {
let has_all_glyphs = AVG_CHARACTERS.chars().all(|c| {
font.lookup_glyph_index(c, allsorts::font::MatchingPresentation::NotRequired, None)
.0
> 0
});
if !has_all_glyphs {
return None;
}
Some(
font.map_glyphs(
AVG_CHARACTERS,
allsorts::tag::LATN,
allsorts::font::MatchingPresentation::NotRequired,
)
.iter()
.map(|g| font.horizontal_advance(g.glyph_index).unwrap())
.sum::<u16>() as f32
/ AVG_CHARACTERS.len() as f32,
)
}
/// From https://github.com/vercel/next.js/blob/dbdf47cf617b8d7213ffe1ff28318ea8eb88c623/packages/font/src/local/pick-font-file-for-fallback-generation.ts#L59
///
/// If multiple font files are provided for a font family, we need to pick
/// one to use for the automatic fallback generation. This function returns
/// the font file that is most likely to be used for the bulk of the text on
/// a page.
///
/// There are some assumptions here about the text on a page when picking the
/// font file:
/// - Most of the text will have normal weight, use the one closest to 400
/// - Most of the text will have normal style, prefer normal over italic
/// - If two font files have the same distance from normal weight, the thinner
/// one will most likely be the bulk of the text
fn pick_font_for_fallback_generation(
font_descriptors: &FontDescriptors,
) -> Result<&FontDescriptor> {
match font_descriptors {
FontDescriptors::One(descriptor) => Ok(descriptor),
FontDescriptors::Many(descriptors) => {
let mut used_descriptor = descriptors
.first()
.context("At least one font is required")?;
for current_descriptor in descriptors.iter().skip(1) {
let used_font_distance = get_distance_from_normal_weight(&used_descriptor.weight)?;
let current_font_distance =
get_distance_from_normal_weight(&current_descriptor.weight)?;
// Prefer normal style if they have the same weight
if used_font_distance == current_font_distance
&& current_descriptor.style != Some("italic".to_owned())
{
used_descriptor = current_descriptor;
continue;
}
let abs_used_distance = used_font_distance.abs();
let abs_current_distance = current_font_distance.abs();
// Use closest absolute distance to normal weight
if abs_current_distance < abs_used_distance {
used_descriptor = current_descriptor;
continue;
}
// Prefer the thinner font if both have the same absolute
if abs_used_distance == abs_current_distance
&& current_font_distance < used_font_distance
{
used_descriptor = current_descriptor;
continue;
}
}
Ok(used_descriptor)
}
}
}
/// From https://github.com/vercel/next.js/blob/dbdf47cf617b8d7213ffe1ff28318ea8eb88c623/packages/font/src/local/pick-font-file-for-fallback-generation.ts#L18
///
/// Get the distance from normal (400) weight for the provided weight.
/// If it's not a variable font we can just return the distance.
/// If it's a variable font we need to compare its weight range to 400.
fn get_distance_from_normal_weight(weight: &Option<FontWeight>) -> Result<f64> {
let Some(weight) = weight else {
return Ok(0.0)
};
Ok(match weight {
FontWeight::Fixed(val) => parse_weight_string(val)? - NORMAL_WEIGHT,
FontWeight::Variable(start, end) => {
let start = parse_weight_string(start)?;
let end = parse_weight_string(end)?;
// Normal weight is within variable font range
if NORMAL_WEIGHT > start && NORMAL_WEIGHT < end {
0.0
} else {
let start_distance = start - NORMAL_WEIGHT;
let end_distance = end - NORMAL_WEIGHT;
if start_distance.abs() < end_distance.abs() {
start_distance
} else {
end_distance
}
}
}
})
}
/// From https://github.com/vercel/next.js/blob/dbdf47cf617b8d7213ffe1ff28318ea8eb88c623/packages/font/src/local/pick-font-file-for-fallback-generation.ts#L6
///
/// Convert the weight string to a number so it can be used for comparison.
/// Weights can be defined as a number, 'normal' or 'bold'. https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight
fn parse_weight_string(weight_str: &str) -> Result<f64> {
if weight_str == "normal" {
Ok(NORMAL_WEIGHT)
} else if weight_str == "bold" {
Ok(BOLD_WEIGHT)
} else {
match weight_str.parse::<f64>() {
Ok(parsed) => Ok(parsed),
Err(_) => {
bail!(
"Invalid weight value in src array: `{}`. Expected `normal`, `bold` or a \
number",
weight_str
)
}
}
}
}
// From https://github.com/vercel/next.js/blob/7457be0c74e64b4d0617943ed27f4d557cc916be/packages/font/src/local/pick-font-file-for-fallback-generation.test.ts
#[cfg(test)]
mod tests {
use anyhow::Result;
use crate::next_font::local::{
font_fallback::pick_font_for_fallback_generation,
options::{FontDescriptor, FontDescriptors, FontWeight},
};
fn generate_font_descriptor(weight: &FontWeight, style: &Option<String>) -> FontDescriptor {
FontDescriptor {
ext: "ttf".to_owned(),
path: "foo.ttf".to_owned(),
style: style.clone(),
weight: Some(weight.clone()),
}
}
#[test]
fn test_picks_weight_closest_to_400() -> Result<()> {
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(&FontWeight::Fixed("300".to_owned()), &None),
generate_font_descriptor(&FontWeight::Fixed("600".to_owned()), &None)
]))?,
&generate_font_descriptor(&FontWeight::Fixed("300".to_owned()), &None)
);
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(&FontWeight::Fixed("200".to_owned()), &None),
generate_font_descriptor(&FontWeight::Fixed("500".to_owned()), &None)
]))?,
&generate_font_descriptor(&FontWeight::Fixed("500".to_owned()), &None)
);
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(&FontWeight::Fixed("normal".to_owned()), &None),
generate_font_descriptor(&FontWeight::Fixed("700".to_owned()), &None)
]))?,
&generate_font_descriptor(&FontWeight::Fixed("normal".to_owned()), &None)
);
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(&FontWeight::Fixed("bold".to_owned()), &None),
generate_font_descriptor(&FontWeight::Fixed("900".to_owned()), &None)
]))?,
&generate_font_descriptor(&FontWeight::Fixed("bold".to_owned()), &None)
);
Ok(())
}
#[test]
fn test_picks_thinner_weight_if_same_distance_to_400() -> Result<()> {
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(&FontWeight::Fixed("300".to_owned()), &None),
generate_font_descriptor(&FontWeight::Fixed("500".to_owned()), &None)
]))?,
&generate_font_descriptor(&FontWeight::Fixed("300".to_owned()), &None)
);
Ok(())
}
#[test]
fn test_picks_variable_closest_to_400() -> Result<()> {
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(
&FontWeight::Variable("100".to_owned(), "300".to_owned()),
&None
),
generate_font_descriptor(
&FontWeight::Variable("600".to_owned(), "900".to_owned()),
&None
)
]))?,
&generate_font_descriptor(
&FontWeight::Variable("100".to_owned(), "300".to_owned()),
&None
)
);
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(
&FontWeight::Variable("100".to_owned(), "200".to_owned()),
&None
),
generate_font_descriptor(
&FontWeight::Variable("500".to_owned(), "800".to_owned()),
&None
)
]))?,
&generate_font_descriptor(
&FontWeight::Variable("500".to_owned(), "800".to_owned()),
&None
)
);
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(
&FontWeight::Variable("100".to_owned(), "900".to_owned()),
&None
),
generate_font_descriptor(
&FontWeight::Variable("300".to_owned(), "399".to_owned()),
&None
)
]))?,
&generate_font_descriptor(
&FontWeight::Variable("100".to_owned(), "900".to_owned()),
&None
)
);
Ok(())
}
#[test]
fn test_prefer_normal_over_italic() -> Result<()> {
assert_eq!(
pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(
&FontWeight::Fixed("400".to_owned()),
&Some("normal".to_owned())
),
generate_font_descriptor(
&FontWeight::Fixed("400".to_owned()),
&Some("italic".to_owned())
)
]))?,
&generate_font_descriptor(
&FontWeight::Fixed("400".to_owned()),
&Some("normal".to_owned())
)
);
Ok(())
}
#[test]
fn test_errors_on_invalid_weight() -> Result<()> {
match pick_font_for_fallback_generation(&FontDescriptors::Many(vec![
generate_font_descriptor(
&FontWeight::Variable("normal".to_owned(), "bold".to_owned()),
&None,
),
generate_font_descriptor(
&FontWeight::Variable("400".to_owned(), "bold".to_owned()),
&None,
),
generate_font_descriptor(
&FontWeight::Variable("normal".to_owned(), "700".to_owned()),
&None,
),
generate_font_descriptor(
&FontWeight::Variable("100".to_owned(), "abc".to_owned()),
&None,
),
])) {
Ok(_) => panic!(),
Err(err) => {
assert_eq!(
err.to_string(),
"Invalid weight value in src array: `abc`. Expected `normal`, `bold` or a \
number"
)
}
}
Ok(())
}
}

View file

@ -75,7 +75,7 @@ impl ImportMappingReplacement for NextFontLocalReplacer {
let request_hash = get_request_hash(*query_vc); let request_hash = get_request_hash(*query_vc);
let options_vc = font_options_from_query_map(*query_vc); let options_vc = font_options_from_query_map(*query_vc);
let font_fallbacks = get_font_fallbacks(options_vc, request_hash); let font_fallbacks = get_font_fallbacks(context, options_vc, request_hash);
let properties = let properties =
&*get_font_css_properties(options_vc, font_fallbacks, request_hash).await?; &*get_font_css_properties(options_vc, font_fallbacks, request_hash).await?;
let file_content = formatdoc!( let file_content = formatdoc!(
@ -164,7 +164,7 @@ impl ImportMappingReplacement for NextFontLocalCssModuleReplacer {
"/{}.module.css", "/{}.module.css",
get_request_id(options.font_family(), request_hash).await? get_request_id(options.font_family(), request_hash).await?
)); ));
let fallback = get_font_fallbacks(options, request_hash); let fallback = get_font_fallbacks(context, options, request_hash);
let stylesheet = build_stylesheet( let stylesheet = build_stylesheet(
font_options_from_query_map(*query_vc), font_options_from_query_map(*query_vc),