Better font family definitions ✒
This commit is contained in:
parent
39f55481ed
commit
98336bfafb
@ -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;
|
||||
|
10
src/env.rs
10
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 {
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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! {
|
||||
|
@ -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};
|
||||
|
@ -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 |
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user