diff --git a/bench/src/bench.rs b/bench/src/bench.rs index 36b8b1376..c3758dc5d 100644 --- a/bench/src/bench.rs +++ b/bench/src/bench.rs @@ -1,5 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use fontdock::fs::FsIndex; +use fontdock::FsIndex; use typst::env::{Env, FsIndexExt, ResourceLoader}; use typst::eval::eval; diff --git a/src/env.rs b/src/env.rs index 3db71e087..75e2853a5 100644 --- a/src/env.rs +++ b/src/env.rs @@ -7,13 +7,13 @@ use std::fs; use std::io::Cursor; use std::path::{Path, PathBuf}; -use fontdock::{ContainsChar, FaceFromVec, FaceId, FontSource}; +use fontdock::{FaceFromVec, FaceId, FontSource}; use image::io::Reader as ImageReader; use image::{DynamicImage, GenericImageView, ImageFormat}; use ttf_parser::Face; #[cfg(feature = "fs")] -use fontdock::fs::{FsIndex, FsSource}; +use fontdock::{FsIndex, FsSource}; /// Encapsulates all environment dependencies (fonts, resources). #[derive(Debug)] @@ -83,12 +83,6 @@ impl FaceFromVec for FaceBuf { } } -impl ContainsChar for FaceBuf { - fn contains_char(&self, c: char) -> bool { - self.get().glyph_index(c).is_some() - } -} - /// Simplify font loader construction from an [`FsIndex`]. #[cfg(feature = "fs")] pub trait FsIndexExt { diff --git a/src/exec/context.rs b/src/exec/context.rs index f19f6561b..6ba5b25f5 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use fontdock::FontStyle; -use super::{Exec, State}; +use super::{Exec, FontFamily, State}; use crate::diag::{Diag, DiagSet, Pass}; use crate::env::Env; use crate::eval::TemplateValue; @@ -74,8 +74,7 @@ impl<'a> ExecContext<'a> { /// Set the font to monospace. pub fn set_monospace(&mut self) { let families = self.state.font.families_mut(); - families.list.insert(0, "monospace".to_string()); - families.flatten(); + families.list.insert(0, FontFamily::Monospace); } /// Push a layout node into the active paragraph. diff --git a/src/exec/state.rs b/src/exec/state.rs index f66694fd7..0322c4376 100644 --- a/src/exec/state.rs +++ b/src/exec/state.rs @@ -1,6 +1,7 @@ +use std::fmt::{self, Display, Formatter}; use std::rc::Rc; -use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; +use fontdock::{FontStretch, FontStyle, FontVariant, FontWeight}; use crate::color::{Color, RgbaColor}; use crate::geom::*; @@ -98,8 +99,8 @@ impl Default for ParState { /// Defines font properties. #[derive(Debug, Clone, PartialEq)] pub struct FontState { - /// A tree of font family names and generic class names. - pub families: Rc, + /// A list of font families with generic class definitions. + pub families: Rc, /// The selected font variant. pub variant: FontVariant, /// The font size. @@ -122,7 +123,7 @@ pub struct FontState { impl FontState { /// Access the `families` mutably. - pub fn families_mut(&mut self) -> &mut FallbackTree { + pub fn families_mut(&mut self) -> &mut FamilyMap { Rc::make_mut(&mut self.families) } @@ -135,12 +136,7 @@ impl FontState { impl Default for FontState { fn default() -> Self { Self { - // The default tree of font fallbacks. - families: Rc::new(fallback! { - list: [], - classes: { "monospace" => ["inconsolata"] }, - base: ["eb garamond", "twitter color emoji"], - }), + families: Rc::new(FamilyMap::default()), variant: FontVariant { style: FontStyle::Normal, weight: FontWeight::REGULAR, @@ -156,3 +152,68 @@ impl Default for FontState { } } } + +/// Font family definitions. +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct FamilyMap { + /// The user-defined list of font families. + pub list: Vec, + /// Definition of serif font families. + pub serif: Vec, + /// Definition of sans-serif font families. + pub sans_serif: Vec, + /// Definition of monospace font families used for raw text. + pub monospace: Vec, + /// Base fonts that are tried if the list has no match. + pub base: Vec, +} + +impl FamilyMap { + /// Flat iterator over this map's family names. + pub fn iter(&self) -> impl Iterator { + self.list + .iter() + .flat_map(move |family: &FontFamily| { + match family { + FontFamily::Named(name) => std::slice::from_ref(name), + FontFamily::Serif => &self.serif, + FontFamily::SansSerif => &self.sans_serif, + FontFamily::Monospace => &self.monospace, + } + }) + .chain(&self.base) + .map(String::as_str) + } +} + +impl Default for FamilyMap { + fn default() -> Self { + Self { + list: vec![FontFamily::Serif], + serif: vec!["eb garamond".into()], + sans_serif: vec![/* TODO */], + monospace: vec!["inconsolata".into()], + base: vec!["twitter color emoji".into()], + } + } +} + +/// A generic or named font family. +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum FontFamily { + Serif, + SansSerif, + Monospace, + Named(String), +} + +impl Display for FontFamily { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Serif => "serif", + Self::SansSerif => "sans-serif", + Self::Monospace => "monospace", + Self::Named(s) => s, + }) + } +} diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index df27e2871..fd10b41ed 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -6,10 +6,11 @@ use std::fmt::{self, Debug, Display, Formatter}; -use fontdock::{FaceId, FaceQuery, FallbackTree, FontVariant}; +use fontdock::{FaceId, FontVariant}; use ttf_parser::{Face, GlyphId}; use crate::env::FontLoader; +use crate::exec::FamilyMap; use crate::geom::{Dir, Length, Point, Size}; use crate::layout::{Element, Fill, Frame}; @@ -40,7 +41,7 @@ impl Shaped { glyphs: vec![], offsets: vec![], font_size, - color: color, + color, } } @@ -98,7 +99,7 @@ impl Display for VerticalFontMetric { pub fn shape( text: &str, dir: Dir, - fallback: &FallbackTree, + families: &FamilyMap, variant: FontVariant, font_size: Length, top_edge: VerticalFontMetric, @@ -122,31 +123,33 @@ pub fn shape( }; for c in chars { - let query = FaceQuery { fallback: fallback.iter(), variant, c }; - if let Some(id) = loader.query(query) { - let face = loader.face(id).get(); - let (glyph, glyph_width) = match lookup_glyph(face, c) { - Some(v) => v, - None => continue, - }; + for family in families.iter() { + if let Some(id) = loader.query(family, variant) { + let face = loader.face(id).get(); + let (glyph, glyph_width) = match lookup_glyph(face, c) { + Some(v) => v, + None => continue, + }; - let units_per_em = f64::from(face.units_per_em().unwrap_or(1000)); - let convert = |units| units / units_per_em * font_size; + let units_per_em = f64::from(face.units_per_em().unwrap_or(1000)); + let convert = |units| units / units_per_em * font_size; - // Flush the buffer and reset the metrics if we use a new font face. - if shaped.face != id { - place(&mut frame, shaped, width, top, bottom); + // Flush the buffer and reset the metrics if we use a new font face. + if shaped.face != id { + place(&mut frame, shaped, width, top, bottom); - shaped = Shaped::new(id, font_size, color); - width = Length::ZERO; - top = convert(f64::from(lookup_metric(face, top_edge))); - bottom = convert(f64::from(lookup_metric(face, bottom_edge))); + shaped = Shaped::new(id, font_size, color); + width = Length::ZERO; + top = convert(f64::from(lookup_metric(face, top_edge))); + bottom = convert(f64::from(lookup_metric(face, bottom_edge))); + } + + shaped.text.push(c); + shaped.glyphs.push(glyph); + shaped.offsets.push(width); + width += convert(f64::from(glyph_width)); + break; } - - shaped.text.push(c); - shaped.glyphs.push(glyph); - shaped.offsets.push(width); - width += convert(f64::from(glyph_width)); } } diff --git a/src/layout/text.rs b/src/layout/text.rs index 7f8f97cc6..2239afac1 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,9 +1,10 @@ use std::fmt::{self, Debug, Formatter}; use std::rc::Rc; -use fontdock::{FallbackTree, FontVariant}; +use fontdock::FontVariant; use super::*; +use crate::exec::FamilyMap; /// A consecutive, styled run of text. #[derive(Clone, PartialEq)] @@ -14,8 +15,8 @@ pub struct TextNode { pub dir: Dir, /// How to align this text node in its parent. pub aligns: LayoutAligns, - /// The families used for font fallback. - pub families: Rc, + /// The list of font families for shaping. + pub families: Rc, /// The font variant, pub variant: FontVariant, /// The font size. diff --git a/src/library/font.rs b/src/library/font.rs index ed2c0ef3c..6afc617b0 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -17,9 +17,9 @@ use super::*; /// - Top edge of the font: `top-edge`, of type `vertical-font-metric`. /// - Bottom edge of the font: `bottom-edge`, of type `vertical-font-metric`. /// - Color the glyphs: `color`, of type `color`. -/// - Serif family definition: `serif`, of type `font-familiy-list`. -/// - Sans-serif family definition: `sans-serif`, of type `font-familiy-list`. -/// - Monospace family definition: `monospace`, of type `font-familiy-list`. +/// - Serif family definition: `serif`, of type `font-family-definition`. +/// - Sans-serif family definition: `sans-serif`, of type `font-family-definition`. +/// - Monospace family definition: `monospace`, of type `font-family-definition`. /// /// # Return value /// A template that configures font properties. The effect is scoped to the body @@ -31,10 +31,9 @@ use super::*; /// - `sans-serif` /// - `monospace` /// - coerces from `string` -/// - Type `font-family-list` +/// - Type `font-family-definition` /// - coerces from `string` /// - coerces from `array` -/// - coerces from `font-family` /// - Type `font-style` /// - `normal` /// - `italic` @@ -58,7 +57,7 @@ use super::*; /// - `descender` pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let size = args.find::(ctx); - let list: Vec<_> = args.filter::(ctx).map(|f| f.to_string()).collect(); + let list: Vec<_> = args.filter::(ctx).collect(); let style = args.get(ctx, "style"); let weight = args.get(ctx, "weight"); let stretch = args.get(ctx, "stretch"); @@ -83,9 +82,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } if !list.is_empty() { - let families = ctx.state.font.families_mut(); - families.list = list.clone(); - families.flatten(); + ctx.state.font.families_mut().list = list.clone(); } if let Some(style) = style { @@ -112,17 +109,16 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { ctx.state.font.color = Fill::Color(color); } - for (variant, arg) in &[ - (FontFamily::Serif, &serif), - (FontFamily::SansSerif, &sans_serif), - (FontFamily::Monospace, &monospace), - ] { - if let Some(FontFamilies(list)) = arg { - let strings = list.into_iter().map(|f| f.to_string()).collect(); - let families = ctx.state.font.families_mut(); - families.update_class_list(variant.to_string(), strings); - families.flatten(); - } + if let Some(FontFamilies(serif)) = &serif { + ctx.state.font.families_mut().serif = serif.clone(); + } + + if let Some(FontFamilies(sans_serif)) = &sans_serif { + ctx.state.font.families_mut().sans_serif = sans_serif.clone(); + } + + if let Some(FontFamilies(monospace)) = &monospace { + ctx.state.font.families_mut().monospace = monospace.clone(); } if let Some(body) = &body { @@ -132,45 +128,19 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { }) } -/// A list of font families. +/// A list of font family names. #[derive(Debug, Clone, PartialEq)] -struct FontFamilies(Vec); - -/// A single font family. -#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub(super) enum FontFamily { - Serif, - SansSerif, - Monospace, - Named(String), -} - -impl FontFamily { - pub fn as_str(&self) -> &str { - match self { - Self::Serif => "serif", - Self::SansSerif => "sans-serif", - Self::Monospace => "monospace", - Self::Named(s) => s, - } - } -} - -impl Display for FontFamily { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self.as_str()) - } -} +struct FontFamilies(Vec); typify! { - FontFamilies: "font family or array of font families", - Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]), + FontFamilies: "string or array of strings", + Value::Str(string) => Self(vec![string.to_lowercase()]), Value::Array(values) => Self(values .into_iter() .filter_map(|v| v.cast().ok()) + .map(|string: String| string.to_lowercase()) .collect() ), - #(family: FontFamily) => Self(vec![family]), } typify! { diff --git a/src/library/mod.rs b/src/library/mod.rs index 62640ef48..58e62d566 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -31,7 +31,7 @@ use fontdock::{FontStyle, FontWeight}; use crate::eval::{AnyValue, FuncValue, Scope}; use crate::eval::{EvalContext, FuncArgs, TemplateValue, Value}; -use crate::exec::{Exec, ExecContext}; +use crate::exec::{Exec, ExecContext, FontFamily}; use crate::geom::*; use crate::layout::VerticalFontMetric; use crate::syntax::{Node, Spanned}; diff --git a/src/main.rs b/src/main.rs index 74ec743df..9d72440ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::{Path, PathBuf}; use anyhow::{anyhow, bail, Context}; -use fontdock::fs::FsIndex; +use fontdock::FsIndex; use typst::diag::Pass; use typst::env::{Env, FsIndexExt, ResourceLoader}; diff --git a/tests/ref/library/font.png b/tests/ref/library/font.png index 1cfbc4997..0e24bb5d3 100644 Binary files a/tests/ref/library/font.png and b/tests/ref/library/font.png differ diff --git a/tests/typ/library/font.typ b/tests/typ/library/font.typ index 3e818f197..13f711250 100644 --- a/tests/typ/library/font.typ +++ b/tests/typ/library/font.typ @@ -48,6 +48,13 @@ Emoji: 🐪, 🌋, 🏞 #try(cap-height, baseline) #try(x-height, baseline) +--- +// Test class definitions. +#font(sans-serif: "PT Sans") +#font(sans-serif)[Sans-serif.] \ +#font(monospace)[Monospace.] \ +#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] \ + --- // Ref: false @@ -56,7 +63,7 @@ Emoji: 🐪, 🌋, 🏞 // Error: 3:14-3:18 expected font style, found font weight // Error: 2:28-2:34 expected font weight, found string -// Error: 1:43-1:44 expected font family or array of font families, found integer +// Error: 1:43-1:44 expected string or array of strings, found integer #font(style: bold, weight: "thin", serif: 0) // Warning: 15-19 should be between 100 and 900 diff --git a/tests/typeset.rs b/tests/typeset.rs index 0a22c96be..5a59a129c 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -5,7 +5,7 @@ use std::fs; use std::path::Path; use std::rc::Rc; -use fontdock::fs::FsIndex; +use fontdock::FsIndex; use image::{GenericImageView, Rgba}; use tiny_skia::{ Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, Pattern, Pixmap, Rect,