Remove props in favor of using state for everything

This commit is contained in:
Laurenz 2021-06-11 11:30:18 +02:00
parent c28708aa19
commit 3330767c20
16 changed files with 197 additions and 267 deletions

View File

@ -1,4 +1,5 @@
use std::mem; use std::mem;
use std::rc::Rc;
use super::{Exec, ExecWithMap, FontFamily, State}; use super::{Exec, ExecWithMap, FontFamily, State};
use crate::diag::{Diag, DiagSet, Pass}; use crate::diag::{Diag, DiagSet, Pass};
@ -43,8 +44,11 @@ impl ExecContext {
/// Set the font to monospace. /// Set the font to monospace.
pub fn set_monospace(&mut self) { pub fn set_monospace(&mut self) {
let families = self.state.font.families_mut(); self.state
families.list.insert(0, FontFamily::Monospace); .font_mut()
.families_mut()
.list
.insert(0, FontFamily::Monospace);
} }
/// Execute a template and return the result as a stack node. /// Execute a template and return the result as a stack node.
@ -108,8 +112,7 @@ impl ExecContext {
/// Apply a forced paragraph break. /// Apply a forced paragraph break.
pub fn parbreak(&mut self) { pub fn parbreak(&mut self) {
let em = self.state.font.resolve_size(); let amount = self.state.par.spacing.resolve(self.state.font.size);
let amount = self.state.par.spacing.resolve(em);
self.stack.parbreak(&self.state); self.stack.parbreak(&self.state);
self.stack.push_soft(StackChild::Spacing(amount)); self.stack.push_soft(StackChild::Spacing(amount));
} }
@ -133,9 +136,11 @@ impl ExecContext {
} }
fn make_text_node(&self, text: impl Into<String>) -> ParChild { fn make_text_node(&self, text: impl Into<String>) -> ParChild {
let align = self.state.aligns.cross; ParChild::Text(
let props = self.state.font.resolve_props(); text.into(),
ParChild::Text(text.into(), props, align) self.state.aligns.cross,
Rc::clone(&self.state.font),
)
} }
} }
@ -217,11 +222,10 @@ struct ParBuilder {
impl ParBuilder { impl ParBuilder {
fn new(state: &State) -> Self { fn new(state: &State) -> Self {
let em = state.font.resolve_size();
Self { Self {
aligns: state.aligns, aligns: state.aligns,
dir: state.lang.dir, dir: state.lang.dir,
line_spacing: state.par.leading.resolve(em), line_spacing: state.par.leading.resolve(state.font.size),
children: vec![], children: vec![],
last: Last::None, last: Last::None,
} }

View File

@ -55,8 +55,8 @@ impl ExecWithMap for syntax::Node {
Self::Space => ctx.push_word_space(), Self::Space => ctx.push_word_space(),
Self::Linebreak(_) => ctx.linebreak(), Self::Linebreak(_) => ctx.linebreak(),
Self::Parbreak(_) => ctx.parbreak(), Self::Parbreak(_) => ctx.parbreak(),
Self::Strong(_) => ctx.state.font.strong ^= true, Self::Strong(_) => ctx.state.font_mut().strong ^= true,
Self::Emph(_) => ctx.state.font.emph ^= true, Self::Emph(_) => ctx.state.font_mut().emph ^= true,
Self::Raw(raw) => raw.exec(ctx), Self::Raw(raw) => raw.exec(ctx),
Self::Heading(heading) => heading.exec_with_map(ctx, map), Self::Heading(heading) => heading.exec_with_map(ctx, map),
Self::List(list) => list.exec_with_map(ctx, map), Self::List(list) => list.exec_with_map(ctx, map),
@ -85,10 +85,11 @@ impl Exec for syntax::RawNode {
impl ExecWithMap for syntax::HeadingNode { impl ExecWithMap for syntax::HeadingNode {
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) { fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let font = ctx.state.font_mut();
let upscale = 1.6 - 0.1 * self.level as f64; let upscale = 1.6 - 0.1 * self.level as f64;
ctx.state.font.scale *= upscale; font.size *= upscale;
ctx.state.font.strong = true; font.strong = true;
self.body.exec_with_map(ctx, map); self.body.exec_with_map(ctx, map);
@ -109,7 +110,7 @@ impl ExecWithMap for syntax::ListNode {
aspect: None, aspect: None,
children: vec![ children: vec![
StackChild::Any(bullet.into(), Gen::default()), StackChild::Any(bullet.into(), Gen::default()),
StackChild::Spacing(ctx.state.font.resolve_size() / 2.0), StackChild::Spacing(ctx.state.font.size / 2.0),
StackChild::Any(body.into(), Gen::default()), StackChild::Any(body.into(), Gen::default()),
], ],
}; };
@ -139,7 +140,7 @@ impl Exec for Value {
let prev = Rc::clone(&ctx.state.font.families); let prev = Rc::clone(&ctx.state.font.families);
ctx.set_monospace(); ctx.set_monospace();
ctx.push_text(pretty(other)); ctx.push_text(pretty(other));
ctx.state.font.families = prev; ctx.state.font_mut().families = prev;
} }
} }
} }

View File

@ -8,7 +8,7 @@ use crate::layout::Fill;
use crate::paper::{Paper, PaperClass, PAPER_A4}; use crate::paper::{Paper, PaperClass, PAPER_A4};
/// The execution state. /// The execution state.
#[derive(Debug, Clone, PartialEq)] #[derive(Default, Debug, Clone, PartialEq, Hash)]
pub struct State { pub struct State {
/// The current language-related settings. /// The current language-related settings.
pub lang: LangState, pub lang: LangState,
@ -17,25 +17,20 @@ pub struct State {
/// The current paragraph settings. /// The current paragraph settings.
pub par: ParState, pub par: ParState,
/// The current font settings. /// The current font settings.
pub font: FontState, pub font: Rc<FontState>,
/// The current alignments of layouts in their parents. /// The current alignments of layouts in their parents.
pub aligns: Gen<Align>, pub aligns: Gen<Align>,
} }
impl Default for State { impl State {
fn default() -> Self { /// Access the `font` state mutably.
Self { pub fn font_mut(&mut self) -> &mut FontState {
lang: LangState::default(), Rc::make_mut(&mut self.font)
page: PageState::default(),
par: ParState::default(),
font: FontState::default(),
aligns: Gen::splat(Align::Start),
}
} }
} }
/// Defines language properties. /// Defines language properties.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub struct LangState { pub struct LangState {
/// The direction for text and other inline objects. /// The direction for text and other inline objects.
pub dir: Dir, pub dir: Dir,
@ -48,7 +43,7 @@ impl Default for LangState {
} }
/// Defines page properties. /// Defines page properties.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub struct PageState { pub struct PageState {
/// The class of this page. /// The class of this page.
pub class: PaperClass, pub class: PaperClass,
@ -88,7 +83,7 @@ impl Default for PageState {
} }
/// Defines paragraph properties. /// Defines paragraph properties.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub struct ParState { pub struct ParState {
/// The spacing between paragraphs (dependent on scaled font size). /// The spacing between paragraphs (dependent on scaled font size).
pub spacing: Linear, pub spacing: Linear,
@ -110,7 +105,7 @@ impl Default for ParState {
} }
/// Defines font properties. /// Defines font properties.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct FontState { pub struct FontState {
/// A list of font families with generic class definitions. /// A list of font families with generic class definitions.
pub families: Rc<FamilyList>, pub families: Rc<FamilyList>,
@ -118,8 +113,6 @@ pub struct FontState {
pub variant: FontVariant, pub variant: FontVariant,
/// The font size. /// The font size.
pub size: Length, pub size: Length,
/// The linear to apply on the base font size.
pub scale: Linear,
/// The top end of the text bounding box. /// The top end of the text bounding box.
pub top_edge: VerticalFontMetric, pub top_edge: VerticalFontMetric,
/// The bottom end of the text bounding box. /// The bottom end of the text bounding box.
@ -133,49 +126,14 @@ pub struct FontState {
/// whether the next `_` makes italic or non-italic. /// whether the next `_` makes italic or non-italic.
pub emph: bool, pub emph: bool,
/// The specifications for a strikethrough line, if any. /// The specifications for a strikethrough line, if any.
pub strikethrough: Option<LineState>, pub strikethrough: Option<Rc<LineState>>,
/// The specifications for a underline, if any. /// The specifications for a underline, if any.
pub underline: Option<LineState>, pub underline: Option<Rc<LineState>>,
/// The specifications for a overline line, if any. /// The specifications for a overline line, if any.
pub overline: Option<LineState>, pub overline: Option<Rc<LineState>>,
} }
impl FontState { impl FontState {
/// The resolved font size.
pub fn resolve_size(&self) -> Length {
self.scale.resolve(self.size)
}
/// Resolve font properties.
pub fn resolve_props(&self) -> FontProps {
let mut variant = self.variant;
if self.strong {
variant.weight = variant.weight.thicken(300);
}
if self.emph {
variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal,
FontStyle::Oblique => FontStyle::Normal,
}
}
let size = self.resolve_size();
FontProps {
families: Rc::clone(&self.families),
variant,
size,
top_edge: self.top_edge,
bottom_edge: self.bottom_edge,
strikethrough: self.strikethrough.map(|s| s.resolve_props(size, &self.fill)),
underline: self.underline.map(|s| s.resolve_props(size, &self.fill)),
overline: self.overline.map(|s| s.resolve_props(size, &self.fill)),
fill: self.fill,
}
}
/// Access the `families` mutably. /// Access the `families` mutably.
pub fn families_mut(&mut self) -> &mut FamilyList { pub fn families_mut(&mut self) -> &mut FamilyList {
Rc::make_mut(&mut self.families) Rc::make_mut(&mut self.families)
@ -194,7 +152,6 @@ impl Default for FontState {
size: Length::pt(11.0), size: Length::pt(11.0),
top_edge: VerticalFontMetric::CapHeight, top_edge: VerticalFontMetric::CapHeight,
bottom_edge: VerticalFontMetric::Baseline, bottom_edge: VerticalFontMetric::Baseline,
scale: Linear::one(),
fill: Fill::Color(Color::Rgba(RgbaColor::BLACK)), fill: Fill::Color(Color::Rgba(RgbaColor::BLACK)),
strong: false, strong: false,
emph: false, emph: false,
@ -205,11 +162,9 @@ impl Default for FontState {
} }
} }
/// Describes a line that could be positioned over or under text. /// Describes a line that could be positioned over, under or on top of text.
#[derive(Debug, Copy, Clone, PartialEq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub struct LineState { pub struct LineState {
/// Color of the line. Will default to text color if `None`.
pub fill: Option<Fill>,
/// Thickness of the line's stroke. Calling functions should attempt to /// Thickness of the line's stroke. Calling functions should attempt to
/// read this value from the appropriate font tables if this is `None`. /// read this value from the appropriate font tables if this is `None`.
pub strength: Option<Linear>, pub strength: Option<Linear>,
@ -219,40 +174,8 @@ pub struct LineState {
pub position: Option<Linear>, pub position: Option<Linear>,
/// Amount that the line will be longer or shorter than its associated text. /// Amount that the line will be longer or shorter than its associated text.
pub extent: Linear, pub extent: Linear,
} /// Color of the line. Will default to text color if `None`.
pub fill: Option<Fill>,
impl LineState {
pub fn resolve_props(&self, font_size: Length, fill: &Fill) -> LineProps {
LineProps {
fill: self.fill.unwrap_or_else(|| fill.clone()),
strength: self.strength.map(|s| s.resolve(font_size)),
position: self.position.map(|p| p.resolve(font_size)),
extent: self.extent.resolve(font_size),
}
}
}
/// Properties used for font selection and layout.
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct FontProps {
/// The list of font families to use for shaping.
pub families: Rc<FamilyList>,
/// Which variant of the font to use.
pub variant: FontVariant,
/// The font size.
pub size: Length,
/// What line to consider the top edge of text.
pub top_edge: VerticalFontMetric,
/// What line to consider the bottom edge of text.
pub bottom_edge: VerticalFontMetric,
/// The fill color of the text.
pub fill: Fill,
/// The specifications for a strikethrough line, if any.
pub strikethrough: Option<LineProps>,
/// The specifications for a underline, if any.
pub underline: Option<LineProps>,
/// The specifications for a overline line, if any.
pub overline: Option<LineProps>,
} }
/// Font family definitions. /// Font family definitions.
@ -319,19 +242,3 @@ impl Display for FontFamily {
}) })
} }
} }
/// Describes a line that could be positioned over or under text.
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
pub struct LineProps {
/// Color of the line.
pub fill: Fill,
/// Thickness of the line's stroke. Calling functions should attempt to
/// read this value from the appropriate font tables if this is `None`.
pub strength: Option<Length>,
/// Position of the line relative to the baseline. Calling functions should
/// attempt to read this value from the appropriate font tables if this is
/// `None`.
pub position: Option<Length>,
/// Amount that the line will be longer or shorter than its associated text.
pub extent: Length,
}

View File

@ -14,7 +14,7 @@ use ttf_parser::{name_id, GlyphId};
use crate::cache::Cache; use crate::cache::Cache;
use crate::color::Color; use crate::color::Color;
use crate::font::{Em, FaceId, VerticalFontMetric}; use crate::font::{Em, FaceId};
use crate::geom::{self, Length, Size}; use crate::geom::{self, Length, Size};
use crate::image::{Image, ImageId}; use crate::image::{Image, ImageId};
use crate::layout::{Element, Fill, Frame, Shape}; use crate::layout::{Element, Fill, Frame, Shape};
@ -256,9 +256,9 @@ impl<'a> PdfExporter<'a> {
); );
let italic_angle = ttf.italic_angle().unwrap_or(0.0); let italic_angle = ttf.italic_angle().unwrap_or(0.0);
let ascender = face.vertical_metric(VerticalFontMetric::Ascender).to_pdf(); let ascender = face.ascender.to_pdf();
let descender = face.vertical_metric(VerticalFontMetric::Descender).to_pdf(); let descender = face.descender.to_pdf();
let cap_height = face.vertical_metric(VerticalFontMetric::CapHeight).to_pdf(); let cap_height = face.cap_height.to_pdf();
let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0); let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
// Write the base font object referencing the CID font. // Write the base font object referencing the CID font.

View File

@ -15,10 +15,19 @@ pub struct Face {
index: u32, index: u32,
ttf: rustybuzz::Face<'static>, ttf: rustybuzz::Face<'static>,
units_per_em: f64, units_per_em: f64,
ascender: Em, pub ascender: Em,
cap_height: Em, pub cap_height: Em,
x_height: Em, pub x_height: Em,
descender: Em, pub descender: Em,
pub strikethrough: LineMetrics,
pub underline: LineMetrics,
pub overline: LineMetrics,
}
/// Metrics for a decorative line.
pub struct LineMetrics {
pub strength: Em,
pub position: Em,
} }
impl Face { impl Face {
@ -35,22 +44,45 @@ impl Face {
let ttf = rustybuzz::Face::from_slice(slice, index)?; let ttf = rustybuzz::Face::from_slice(slice, index)?;
// Look up some metrics we may need often.
let units_per_em = f64::from(ttf.units_per_em()); let units_per_em = f64::from(ttf.units_per_em());
let ascender = ttf.typographic_ascender().unwrap_or(ttf.ascender()); let to_em = |units| Em::from_units(units, units_per_em);
let cap_height = ttf.capital_height().filter(|&h| h > 0).unwrap_or(ascender);
let x_height = ttf.x_height().filter(|&h| h > 0).unwrap_or(ascender); let ascender = to_em(ttf.typographic_ascender().unwrap_or(ttf.ascender()));
let descender = ttf.typographic_descender().unwrap_or(ttf.descender()); let cap_height = ttf.capital_height().filter(|&h| h > 0).map_or(ascender, to_em);
let x_height = ttf.x_height().filter(|&h| h > 0).map_or(ascender, to_em);
let descender = to_em(ttf.typographic_descender().unwrap_or(ttf.descender()));
let strikeout = ttf.strikeout_metrics();
let underline = ttf.underline_metrics();
let default = Em::new(0.06);
let strikethrough = LineMetrics {
strength: strikeout.or(underline).map_or(default, |s| to_em(s.thickness)),
position: strikeout.map_or(Em::new(0.25), |s| to_em(s.position)),
};
let underline = LineMetrics {
strength: underline.or(strikeout).map_or(default, |s| to_em(s.thickness)),
position: underline.map_or(Em::new(-0.2), |s| to_em(s.position)),
};
let overline = LineMetrics {
strength: underline.strength,
position: cap_height + Em::new(0.1),
};
Some(Self { Some(Self {
buffer, buffer,
index, index,
ttf, ttf,
units_per_em, units_per_em,
ascender: Em::from_units(ascender, units_per_em), ascender,
cap_height: Em::from_units(cap_height, units_per_em), cap_height,
x_height: Em::from_units(x_height, units_per_em), x_height,
descender: Em::from_units(descender, units_per_em), descender,
strikethrough,
underline,
overline,
}) })
} }

View File

@ -1,15 +1,18 @@
use super::*; use super::*;
use decorum::N64;
/// An angle. /// An angle.
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] #[derive(Default, Copy, Clone, PartialEq, PartialOrd, Hash)]
pub struct Angle { pub struct Angle {
/// The angle in raw units. /// The angle in raw units.
raw: f64, raw: N64,
} }
impl Angle { impl Angle {
/// The zero angle. /// The zero angle.
pub const ZERO: Self = Self { raw: 0.0 }; pub fn zero() -> Self {
Self { raw: N64::from(0.0) }
}
/// Create an angle from a number of radians. /// Create an angle from a number of radians.
pub fn rad(rad: f64) -> Self { pub fn rad(rad: f64) -> Self {
@ -23,7 +26,7 @@ impl Angle {
/// Create an angle from a number of raw units. /// Create an angle from a number of raw units.
pub fn raw(raw: f64) -> Self { pub fn raw(raw: f64) -> Self {
Self { raw } Self { raw: N64::from(raw) }
} }
/// Convert this to a number of radians. /// Convert this to a number of radians.
@ -38,17 +41,17 @@ impl Angle {
/// Get the value of this angle in raw units. /// Get the value of this angle in raw units.
pub fn to_raw(self) -> f64 { pub fn to_raw(self) -> f64 {
self.raw self.raw.into()
} }
/// Create an angle from a value in a unit. /// Create an angle from a value in a unit.
pub fn with_unit(val: f64, unit: AngularUnit) -> Self { pub fn with_unit(val: f64, unit: AngularUnit) -> Self {
Self { raw: val * unit.raw_scale() } Self { raw: N64::from(val * unit.raw_scale()) }
} }
/// Get the value of this length in unit. /// Get the value of this length in unit.
pub fn to_unit(self, unit: AngularUnit) -> f64 { pub fn to_unit(self, unit: AngularUnit) -> f64 {
self.raw / unit.raw_scale() self.to_raw() / unit.raw_scale()
} }
} }
@ -119,7 +122,7 @@ impl Div for Angle {
type Output = f64; type Output = f64;
fn div(self, other: Self) -> f64 { fn div(self, other: Self) -> f64 {
self.raw / other.raw self.to_raw() / other.to_raw()
} }
} }
@ -130,7 +133,7 @@ assign_impl!(Angle /= f64);
impl Sum for Angle { impl Sum for Angle {
fn sum<I: Iterator<Item = Angle>>(iter: I) -> Self { fn sum<I: Iterator<Item = Angle>>(iter: I) -> Self {
iter.fold(Angle::ZERO, Add::add) iter.fold(Angle::zero(), Add::add)
} }
} }
/// Different units of angular measurement. /// Different units of angular measurement.

View File

@ -3,7 +3,7 @@ use super::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A point in 2D. /// A point in 2D.
#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize, Hash)]
pub struct Point { pub struct Point {
/// The x coordinate. /// The x coordinate.
pub x: Length, pub x: Length,

View File

@ -3,7 +3,7 @@ use super::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A size in 2D. /// A size in 2D.
#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize, Hash)]
pub struct Size { pub struct Size {
/// The width. /// The width.
pub width: Length, pub width: Length,

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// A container with a horizontal and vertical component. /// A container with a horizontal and vertical component.
#[derive(Default, Copy, Clone, Eq, PartialEq)] #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Spec<T> { pub struct Spec<T> {
/// The horizontal component. /// The horizontal component.
pub horizontal: T, pub horizontal: T,

View File

@ -1,10 +1,11 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::rc::Rc;
use unicode_bidi::{BidiInfo, Level}; use unicode_bidi::{BidiInfo, Level};
use xi_unicode::LineBreakIterator; use xi_unicode::LineBreakIterator;
use super::*; use super::*;
use crate::exec::FontProps; use crate::exec::FontState;
use crate::util::{RangeExt, SliceExt}; use crate::util::{RangeExt, SliceExt};
type Range = std::ops::Range<usize>; type Range = std::ops::Range<usize>;
@ -26,7 +27,7 @@ pub enum ParChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(Length), Spacing(Length),
/// A run of text and how to align it in its line. /// A run of text and how to align it in its line.
Text(String, FontProps, Align), Text(String, Align, Rc<FontState>),
/// Any child node and how to align it in its line. /// Any child node and how to align it in its line.
Any(AnyNode, Align), Any(AnyNode, Align),
} }
@ -131,11 +132,11 @@ impl<'a> ParLayout<'a> {
items.push(ParItem::Spacing(amount)); items.push(ParItem::Spacing(amount));
ranges.push(range); ranges.push(range);
} }
ParChild::Text(_, ref props, align) => { ParChild::Text(_, align, ref state) => {
// TODO: Also split by language and script. // TODO: Also split by language and script.
for (subrange, dir) in split_runs(&bidi, range) { for (subrange, dir) in split_runs(&bidi, range) {
let text = &bidi.text[subrange.clone()]; let text = &bidi.text[subrange.clone()];
let shaped = shape(ctx, text, dir, props); let shaped = shape(ctx, text, dir, state);
items.push(ParItem::Text(shaped, align)); items.push(ParItem::Text(shaped, align));
ranges.push(subrange); ranges.push(subrange);
} }

View File

@ -1,12 +1,12 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::ops::{Add, Range}; use std::ops::Range;
use rustybuzz::UnicodeBuffer; use rustybuzz::UnicodeBuffer;
use super::{Element, Frame, Glyph, LayoutContext, Text}; use super::{Element, Frame, Glyph, LayoutContext, Text};
use crate::exec::FontProps; use crate::exec::{FontState, LineState};
use crate::font::{Em, Face, FaceId, VerticalFontMetric}; use crate::font::{Face, FaceId, FontStyle, LineMetrics};
use crate::geom::{Dir, Length, Point, Size}; use crate::geom::{Dir, Length, Point, Size};
use crate::layout::Shape; use crate::layout::Shape;
use crate::util::SliceExt; use crate::util::SliceExt;
@ -23,7 +23,7 @@ pub struct ShapedText<'a> {
/// The text direction. /// The text direction.
pub dir: Dir, pub dir: Dir,
/// The properties used for font selection. /// The properties used for font selection.
pub props: &'a FontProps, pub state: &'a FontState,
/// The font size. /// The font size.
pub size: Size, pub size: Size,
/// The baseline from the top of the frame. /// The baseline from the top of the frame.
@ -69,8 +69,8 @@ impl<'a> ShapedText<'a> {
let mut text = Text { let mut text = Text {
face_id, face_id,
size: self.props.size, size: self.state.size,
fill: self.props.fill, fill: self.state.fill,
glyphs: vec![], glyphs: vec![],
}; };
@ -85,7 +85,7 @@ impl<'a> ShapedText<'a> {
} }
frame.push(pos, Element::Text(text)); frame.push(pos, Element::Text(text));
decorate(ctx, &mut frame, &self.props, face_id, pos, width); decorate(ctx, &mut frame, pos, width, face_id, &self.state);
offset += width; offset += width;
} }
@ -101,17 +101,17 @@ impl<'a> ShapedText<'a> {
text_range: Range<usize>, text_range: Range<usize>,
) -> ShapedText<'a> { ) -> ShapedText<'a> {
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
let (size, baseline) = measure(ctx, glyphs, self.props); let (size, baseline) = measure(ctx, glyphs, self.state);
Self { Self {
text: &self.text[text_range], text: &self.text[text_range],
dir: self.dir, dir: self.dir,
props: self.props, state: self.state,
size, size,
baseline, baseline,
glyphs: Cow::Borrowed(glyphs), glyphs: Cow::Borrowed(glyphs),
} }
} else { } else {
shape(ctx, &self.text[text_range], self.dir, self.props) shape(ctx, &self.text[text_range], self.dir, self.state)
} }
} }
@ -185,20 +185,20 @@ pub fn shape<'a>(
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
text: &'a str, text: &'a str,
dir: Dir, dir: Dir,
props: &'a FontProps, state: &'a FontState,
) -> ShapedText<'a> { ) -> ShapedText<'a> {
let mut glyphs = vec![]; let mut glyphs = vec![];
let families = props.families.iter(); let families = state.families.iter();
if !text.is_empty() { if !text.is_empty() {
shape_segment(ctx, &mut glyphs, 0, text, dir, props, families, None); shape_segment(ctx, &mut glyphs, 0, text, dir, state, families, None);
} }
let (size, baseline) = measure(ctx, &glyphs, props); let (size, baseline) = measure(ctx, &glyphs, state);
ShapedText { ShapedText {
text, text,
dir, dir,
props, state,
size, size,
baseline, baseline,
glyphs: Cow::Owned(glyphs), glyphs: Cow::Owned(glyphs),
@ -212,7 +212,7 @@ fn shape_segment<'a>(
base: usize, base: usize,
text: &str, text: &str,
dir: Dir, dir: Dir,
props: &FontProps, state: &FontState,
mut families: impl Iterator<Item = &'a str> + Clone, mut families: impl Iterator<Item = &'a str> + Clone,
mut first_face: Option<FaceId>, mut first_face: Option<FaceId>,
) { ) {
@ -221,7 +221,21 @@ fn shape_segment<'a>(
// Try to load the next available font family. // Try to load the next available font family.
match families.next() { match families.next() {
Some(family) => { Some(family) => {
match ctx.cache.font.select(ctx.loader, family, props.variant) { let mut variant = state.variant;
if state.strong {
variant.weight = variant.weight.thicken(300);
}
if state.emph {
variant.style = match variant.style {
FontStyle::Normal => FontStyle::Italic,
FontStyle::Italic => FontStyle::Normal,
FontStyle::Oblique => FontStyle::Normal,
}
}
match ctx.cache.font.select(ctx.loader, family, variant) {
Some(id) => break (id, true), Some(id) => break (id, true),
None => {} None => {}
} }
@ -267,8 +281,8 @@ fn shape_segment<'a>(
glyphs.push(ShapedGlyph { glyphs.push(ShapedGlyph {
face_id, face_id,
glyph_id: info.codepoint as u16, glyph_id: info.codepoint as u16,
x_advance: face.to_em(pos[i].x_advance).to_length(props.size), x_advance: face.to_em(pos[i].x_advance).to_length(state.size),
x_offset: face.to_em(pos[i].x_offset).to_length(props.size), x_offset: face.to_em(pos[i].x_offset).to_length(state.size),
text_index: base + cluster, text_index: base + cluster,
safe_to_break: !info.unsafe_to_break(), safe_to_break: !info.unsafe_to_break(),
}); });
@ -319,7 +333,7 @@ fn shape_segment<'a>(
base + range.start, base + range.start,
&text[range], &text[range],
dir, dir,
props, state,
families.clone(), families.clone(),
first_face, first_face,
); );
@ -336,7 +350,7 @@ fn shape_segment<'a>(
fn measure( fn measure(
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
glyphs: &[ShapedGlyph], glyphs: &[ShapedGlyph],
props: &FontProps, state: &FontState,
) -> (Size, Length) { ) -> (Size, Length) {
let cache = &mut ctx.cache.font; let cache = &mut ctx.cache.font;
@ -344,15 +358,15 @@ fn measure(
let mut top = Length::zero(); let mut top = Length::zero();
let mut bottom = Length::zero(); let mut bottom = Length::zero();
let mut expand_vertical = |face: &Face| { let mut expand_vertical = |face: &Face| {
top.set_max(face.vertical_metric(props.top_edge).to_length(props.size)); top.set_max(face.vertical_metric(state.top_edge).to_length(state.size));
bottom.set_max(-face.vertical_metric(props.bottom_edge).to_length(props.size)); bottom.set_max(-face.vertical_metric(state.bottom_edge).to_length(state.size));
}; };
if glyphs.is_empty() { if glyphs.is_empty() {
// When there are no glyphs, we just use the vertical metrics of the // When there are no glyphs, we just use the vertical metrics of the
// first available font. // first available font.
for family in props.families.iter() { for family in state.families.iter() {
if let Some(face_id) = cache.select(ctx.loader, family, props.variant) { if let Some(face_id) = cache.select(ctx.loader, family, state.variant) {
expand_vertical(cache.get(face_id)); expand_vertical(cache.get(face_id));
break; break;
} }
@ -375,76 +389,44 @@ fn measure(
fn decorate( fn decorate(
ctx: &LayoutContext, ctx: &LayoutContext,
frame: &mut Frame, frame: &mut Frame,
props: &FontProps,
face_id: FaceId,
pos: Point, pos: Point,
width: Length, width: Length,
face_id: FaceId,
state: &FontState,
) { ) {
let mut apply = |strength, position, extent, fill| { let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| {
let metrics = metrics(&ctx.cache.font.get(face_id));
let strength = substate
.strength
.map(|s| s.resolve(state.size))
.unwrap_or(metrics.strength.to_length(state.size));
let position = substate
.position
.map(|s| s.resolve(state.size))
.unwrap_or(metrics.position.to_length(state.size));
let extent = substate.extent.resolve(state.size);
let fill = substate.fill.unwrap_or(state.fill);
let pos = Point::new(pos.x - extent, pos.y - position); let pos = Point::new(pos.x - extent, pos.y - position);
let target = Point::new(width + 2.0 * extent, Length::zero()); let target = Point::new(width + 2.0 * extent, Length::zero());
frame.push(pos, Element::Geometry(Shape::Line(target, strength), fill)); let shape = Shape::Line(target, strength);
let element = Element::Geometry(shape, fill);
frame.push(pos, element);
}; };
if let Some(strikethrough) = props.strikethrough { if let Some(strikethrough) = &state.strikethrough {
let face = ctx.cache.font.get(face_id); apply(strikethrough, |face| &face.strikethrough);
let strength = strikethrough.strength.unwrap_or_else(|| {
face.ttf()
.strikeout_metrics()
.or_else(|| face.ttf().underline_metrics())
.map_or(Em::new(0.06), |m| face.to_em(m.thickness))
.to_length(props.size)
});
let position = strikethrough.position.unwrap_or_else(|| {
face.ttf()
.strikeout_metrics()
.map_or(Em::new(0.25), |m| face.to_em(m.position))
.to_length(props.size)
});
apply(strength, position, strikethrough.extent, strikethrough.fill);
} }
if let Some(underline) = props.underline { if let Some(underline) = &state.underline {
let face = ctx.cache.font.get(face_id); apply(underline, |face| &face.underline);
let strength = underline.strength.unwrap_or_else(|| {
face.ttf()
.underline_metrics()
.or_else(|| face.ttf().strikeout_metrics())
.map_or(Em::new(0.06), |m| face.to_em(m.thickness))
.to_length(props.size)
});
let position = underline.position.unwrap_or_else(|| {
face.ttf()
.underline_metrics()
.map_or(Em::new(-0.2), |m| face.to_em(m.position))
.to_length(props.size)
});
apply(strength, position, underline.extent, underline.fill);
} }
if let Some(overline) = props.overline { if let Some(overline) = &state.overline {
let face = ctx.cache.font.get(face_id); apply(overline, |face| &face.overline);
let strength = overline.strength.unwrap_or_else(|| {
face.ttf()
.underline_metrics()
.or_else(|| face.ttf().strikeout_metrics())
.map_or(Em::new(0.06), |m| face.to_em(m.thickness))
.to_length(props.size)
});
let position = overline.position.unwrap_or_else(|| {
face.vertical_metric(VerticalFontMetric::CapHeight)
.add(Em::new(0.1))
.to_length(props.size)
});
apply(strength, position, overline.extent, overline.fill);
} }
} }

View File

@ -55,7 +55,7 @@ fn line_impl(
name: &str, name: &str,
ctx: &mut EvalContext, ctx: &mut EvalContext,
args: &mut FuncArgs, args: &mut FuncArgs,
substate: impl Fn(&mut FontState) -> &mut Option<LineState> + 'static, substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
) -> Value { ) -> Value {
let color = args.eat_named(ctx, "color"); let color = args.eat_named(ctx, "color");
let position = args.eat_named(ctx, "position"); let position = args.eat_named(ctx, "position");
@ -64,17 +64,19 @@ fn line_impl(
let body = args.eat::<TemplateValue>(ctx); let body = args.eat::<TemplateValue>(ctx);
// Suppress any existing strikethrough if strength is explicitly zero. // Suppress any existing strikethrough if strength is explicitly zero.
let state = strength.map_or(true, |s| !s.is_zero()).then(|| LineState { let state = strength.map_or(true, |s| !s.is_zero()).then(|| {
fill: color.map(Fill::Color), Rc::new(LineState {
strength, strength,
position, position,
extent, extent,
fill: color.map(Fill::Color),
})
}); });
Value::template(name, move |ctx| { Value::template(name, move |ctx| {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
*substate(&mut ctx.state.font) = state; *substate(ctx.state.font_mut()) = state.clone();
if let Some(body) = &body { if let Some(body) = &body {
body.exec(ctx); body.exec(ctx);

View File

@ -64,54 +64,50 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
Value::template("font", move |ctx| { Value::template("font", move |ctx| {
let snapshot = ctx.state.clone(); let snapshot = ctx.state.clone();
let font = ctx.state.font_mut();
if let Some(linear) = size { if let Some(linear) = size {
if linear.rel.is_zero() { font.size = linear.resolve(font.size);
ctx.state.font.size = linear.abs;
ctx.state.font.scale = Linear::one();
} else {
ctx.state.font.scale = linear;
}
} }
if !list.is_empty() { if !list.is_empty() {
ctx.state.font.families_mut().list = list.clone(); font.families_mut().list = list.clone();
} }
if let Some(style) = style { if let Some(style) = style {
ctx.state.font.variant.style = style; font.variant.style = style;
} }
if let Some(weight) = weight { if let Some(weight) = weight {
ctx.state.font.variant.weight = weight; font.variant.weight = weight;
} }
if let Some(stretch) = stretch { if let Some(stretch) = stretch {
ctx.state.font.variant.stretch = stretch; font.variant.stretch = stretch;
} }
if let Some(top_edge) = top_edge { if let Some(top_edge) = top_edge {
ctx.state.font.top_edge = top_edge; font.top_edge = top_edge;
} }
if let Some(bottom_edge) = bottom_edge { if let Some(bottom_edge) = bottom_edge {
ctx.state.font.bottom_edge = bottom_edge; font.bottom_edge = bottom_edge;
} }
if let Some(color) = color { if let Some(color) = color {
ctx.state.font.fill = Fill::Color(color); font.fill = Fill::Color(color);
} }
if let Some(FontFamilies(serif)) = &serif { if let Some(FontFamilies(serif)) = &serif {
ctx.state.font.families_mut().serif = serif.clone(); font.families_mut().serif = serif.clone();
} }
if let Some(FontFamilies(sans_serif)) = &sans_serif { if let Some(FontFamilies(sans_serif)) = &sans_serif {
ctx.state.font.families_mut().sans_serif = sans_serif.clone(); font.families_mut().sans_serif = sans_serif.clone();
} }
if let Some(FontFamilies(monospace)) = &monospace { if let Some(FontFamilies(monospace)) = &monospace {
ctx.state.font.families_mut().monospace = monospace.clone(); font.families_mut().monospace = monospace.clone();
} }
if let Some(body) = &body { if let Some(body) = &body {

View File

@ -34,6 +34,7 @@ pub use spacing::*;
pub use stack::*; pub use stack::*;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::rc::Rc;
use crate::color::RgbaColor; use crate::color::RgbaColor;
use crate::eval::{EvalContext, FuncArgs, Scope, TemplateValue, Value}; use crate::eval::{EvalContext, FuncArgs, Scope, TemplateValue, Value};

View File

@ -31,7 +31,8 @@ fn spacing_impl(
let spacing: Option<Linear> = args.eat_expect(ctx, "spacing"); let spacing: Option<Linear> = args.eat_expect(ctx, "spacing");
Value::template(name, move |ctx| { Value::template(name, move |ctx| {
if let Some(linear) = spacing { if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.resolve_size()); // TODO: Should this really always be font-size relative?
let amount = linear.resolve(ctx.state.font.size);
ctx.push_spacing(axis, amount); ctx.push_spacing(axis, amount);
} }
}) })

View File

@ -26,7 +26,7 @@ impl Paper {
} }
/// Defines default margins for a class of related papers. /// Defines default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaperClass { pub enum PaperClass {
Custom, Custom,
Base, Base,