diff --git a/fonts/PTSans-Bold.ttf b/fonts/PTSans-Bold.ttf new file mode 100644 index 00000000..7f2bddb5 Binary files /dev/null and b/fonts/PTSans-Bold.ttf differ diff --git a/fonts/PTSans-BoldItalic.ttf b/fonts/PTSans-BoldItalic.ttf new file mode 100644 index 00000000..c4b6a521 Binary files /dev/null and b/fonts/PTSans-BoldItalic.ttf differ diff --git a/fonts/PTSans-Italic.ttf b/fonts/PTSans-Italic.ttf new file mode 100644 index 00000000..130fa710 Binary files /dev/null and b/fonts/PTSans-Italic.ttf differ diff --git a/src/env.rs b/src/env.rs index 62a0ab3e..a3e14715 100644 --- a/src/env.rs +++ b/src/env.rs @@ -60,7 +60,7 @@ impl ResourceLoader { } }; - Some((id, self.get_loaded(id))) + Some((id, self.loaded(id))) } /// Retrieve a previously loaded resource by its id. @@ -68,7 +68,7 @@ impl ResourceLoader { /// # Panics /// This panics if no resource with this id was loaded. #[track_caller] - pub fn get_loaded(&self, id: ResourceId) -> &R { + pub fn loaded(&self, id: ResourceId) -> &R { self.entries[id.0].downcast_ref().expect("bad resource type") } } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index a9c94dc9..5a2aa0cc 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -51,7 +51,7 @@ impl<'a> PdfExporter<'a> { match element { LayoutElement::Text(shaped) => fonts.insert(shaped.face), LayoutElement::Image(image) => { - let img = env.resources.get_loaded::(image.res); + let img = env.resources.loaded::(image.res); if img.buf.color().has_alpha() { alpha_masks += 1; } @@ -181,7 +181,7 @@ impl<'a> PdfExporter<'a> { fn write_fonts(&mut self) { for (refs, face_id) in self.refs.fonts().zip(self.fonts.layout_indices()) { - let owned_face = self.env.fonts.get_loaded(face_id); + let owned_face = self.env.fonts.face(face_id); let face = owned_face.get(); let name = face @@ -292,7 +292,7 @@ impl<'a> PdfExporter<'a> { let mut masks_seen = 0; for (id, resource) in self.refs.images().zip(self.images.layout_indices()) { - let img = self.env.resources.get_loaded::(resource); + let img = self.env.resources.loaded::(resource); let (width, height) = img.buf.dimensions(); // Add the primary image. diff --git a/src/library/layout.rs b/src/library/layout.rs index 36a20821..23066fdc 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -21,10 +21,10 @@ use crate::prelude::*; pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { let snapshot = ctx.state.clone(); let body = args.find::(); - let first = args.get::<_, Spanned>(ctx, 0); - let second = args.get::<_, Spanned>(ctx, 1); - let hor = args.get::<_, Spanned>(ctx, "horizontal"); - let ver = args.get::<_, Spanned>(ctx, "vertical"); + let first = args.get::<_, Spanned>(ctx, 0); + let second = args.get::<_, Spanned>(ctx, 1); + let hor = args.get::<_, Spanned>(ctx, "horizontal"); + let ver = args.get::<_, Spanned>(ctx, "vertical"); args.done(ctx); let prev_main = ctx.state.align.main; @@ -58,7 +58,7 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { } else { // We don't know the axis: This has to be a `center` alignment for a // positional argument. - debug_assert_eq!(arg, AlignArg::Center); + debug_assert_eq!(arg, SpecAlign::Center); if had.main && had.cross { ctx.diag(error!(span, "duplicate alignment")); @@ -108,7 +108,7 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value { /// An argument to `[align]`. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -enum AlignArg { +enum SpecAlign { Left, Right, Top, @@ -116,7 +116,7 @@ enum AlignArg { Center, } -convert_ident!(AlignArg, "alignment", |v| match v { +convert_ident!(SpecAlign, "alignment", |v| match v { "left" => Some(Self::Left), "right" => Some(Self::Right), "top" => Some(Self::Top), @@ -125,7 +125,7 @@ convert_ident!(AlignArg, "alignment", |v| match v { _ => None, }); -impl AlignArg { +impl SpecAlign { /// The specific axis this alignment refers to. /// /// Returns `None` if this is `Center` since the axis is unknown. @@ -140,7 +140,7 @@ impl AlignArg { } } -impl Switch for AlignArg { +impl Switch for SpecAlign { type Other = Align; fn switch(self, flow: Flow) -> Self::Other { @@ -163,7 +163,7 @@ impl Switch for AlignArg { } } -impl Display for AlignArg { +impl Display for SpecAlign { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { Self::Left => "left", diff --git a/src/library/style.rs b/src/library/style.rs index 804e9424..c641a598 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -71,13 +71,6 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { } } - let mut needs_flattening = false; - let list: Vec<_> = args.find_all::().map(|s| s.to_lowercase()).collect(); - if !list.is_empty() { - Rc::make_mut(&mut ctx.state.font.families).list = list; - needs_flattening = true; - } - if let Some(style) = args.get::<_, FontStyle>(ctx, "style") { ctx.state.font.variant.style = style; } @@ -90,6 +83,13 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { ctx.state.font.variant.stretch = stretch; } + let mut needs_flattening = false; + let list: Vec<_> = args.find_all::().map(|s| s.to_lowercase()).collect(); + if !list.is_empty() { + Rc::make_mut(&mut ctx.state.font.families).list = list; + needs_flattening = true; + } + for (class, dict) in args.find_all_str::>() { let fallback = Args(dict) .find_all::() @@ -100,12 +100,12 @@ pub fn font(mut args: Args, ctx: &mut EvalContext) -> Value { needs_flattening = true; } - args.done(ctx); - if needs_flattening { Rc::make_mut(&mut ctx.state.font.families).flatten(); } + args.done(ctx); + if let Some(body) = body { body.eval(ctx); ctx.state = snapshot; diff --git a/src/shaping.rs b/src/shaping.rs index efc24d07..c42cd0ac 100644 --- a/src/shaping.rs +++ b/src/shaping.rs @@ -82,8 +82,8 @@ pub fn shape( for c in chars { let query = FaceQuery { fallback: fallback.iter(), variant, c }; - if let Some((id, owned_face)) = loader.query(query) { - let face = owned_face.get(); + if let Some(id) = loader.query(query) { + let face = loader.face(id).get(); let (glyph, width) = match lookup_glyph(face, c, font_size) { Some(v) => v, None => continue, diff --git a/tests/ref/func-font-fallback.png b/tests/ref/func-font-fallback.png new file mode 100644 index 00000000..b7c565b4 Binary files /dev/null and b/tests/ref/func-font-fallback.png differ diff --git a/tests/ref/func-font-properties.png b/tests/ref/func-font-properties.png new file mode 100644 index 00000000..3dab43f5 Binary files /dev/null and b/tests/ref/func-font-properties.png differ diff --git a/tests/typ/func-font-fallback.typ b/tests/typ/func-font-fallback.typ new file mode 100644 index 00000000..587baed7 --- /dev/null +++ b/tests/typ/func-font-fallback.typ @@ -0,0 +1,18 @@ +// Test font fallback. + +// Source Sans Pro + Segoe UI Emoji. +Emoji: 🏀 + +// CMU Serif + Noto Emoji. +[font: "CMU Serif", "Noto Emoji"][Emoji: 🏀] + +// Class definitions. +[font: math=("CMU Serif", "Latin Modern Math", "Noto Emoji")] +[font: math][Math: ∫ α + β ➗ 3] + +// Class redefinition. +[font: sans-serif=("Noto Emoji",)] +[font: sans-serif=("Archivo", sans-serif)] +New sans-serif. 🚀 + +// TODO: Add tests for other scripts. diff --git a/tests/typ/func-font-properties.typ b/tests/typ/func-font-properties.typ new file mode 100644 index 00000000..78093e98 --- /dev/null +++ b/tests/typ/func-font-properties.typ @@ -0,0 +1,20 @@ +// Test configuring font properties. + +[font: "PT Sans", 10pt] + +// Set same font size in three different ways. +[font: 20pt][A] +[font: 200%][A] +[font: 15pt + 50%][A] + +// Do nothing. +[font][Normal] + +// Set style (is available). +[font: style=italic][Italic] + +// Set weight (is available). +[font: weight=bold][Bold] + +// Set stretch (not available, matching closest). +[font: stretch=ultra-condensed][Condensed] diff --git a/tests/typeset.rs b/tests/typeset.rs index 01576cf0..ec2ad62d 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -278,7 +278,7 @@ fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas { } fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) { - let face = env.fonts.get_loaded(shaped.face).get(); + let face = env.fonts.face(shaped.face).get(); for (&glyph, &offset) in shaped.glyphs.iter().zip(&shaped.offsets) { let units_per_em = face.units_per_em().unwrap_or(1000); @@ -304,7 +304,7 @@ fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) { } fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, img: &ImageElement) { - let buf = &env.resources.get_loaded::(img.res).buf; + let buf = &env.resources.loaded::(img.res).buf; let mut pixmap = Pixmap::new(buf.width(), buf.height()).unwrap(); for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) {