Better font family definitions ✒

This commit is contained in:
Laurenz 2021-03-22 14:08:50 +01:00
parent 39f55481ed
commit 98336bfafb
12 changed files with 139 additions and 104 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -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.

View File

@ -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<FallbackTree>,
/// A list of font families with generic class definitions.
pub families: Rc<FamilyMap>,
/// 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<FontFamily>,
/// Definition of serif font families.
pub serif: Vec<String>,
/// Definition of sans-serif font families.
pub sans_serif: Vec<String>,
/// Definition of monospace font families used for raw text.
pub monospace: Vec<String>,
/// Base fonts that are tried if the list has no match.
pub base: Vec<String>,
}
impl FamilyMap {
/// Flat iterator over this map's family names.
pub fn iter(&self) -> impl Iterator<Item = &str> {
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,
})
}
}

View File

@ -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));
}
}

View File

@ -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<FallbackTree>,
/// The list of font families for shaping.
pub families: Rc<FamilyMap>,
/// The font variant,
pub variant: FontVariant,
/// The font size.

View File

@ -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::<Linear>(ctx);
let list: Vec<_> = args.filter::<FontFamily>(ctx).map(|f| f.to_string()).collect();
let list: Vec<_> = args.filter::<FontFamily>(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<FontFamily>);
/// 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<String>);
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! {

View File

@ -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};

View File

@ -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};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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

View File

@ -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,