From 40b87d4066fe85cb3fde6cf84cd60d748273ae25 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 7 Dec 2021 16:36:39 +0100 Subject: [PATCH] Set Rules Episode II: Attack of the properties --- Cargo.toml | 5 +- src/eval/mod.rs | 22 +- src/eval/node.rs | 7 +- src/eval/styles.rs | 124 ++++++ src/frame.rs | 9 +- src/lib.rs | 26 +- src/library/align.rs | 28 +- src/library/deco.rs | 8 +- src/library/grid.rs | 7 +- src/library/mod.rs | 12 +- src/library/page.rs | 392 ++++++++++++++--- src/library/par.rs | 66 +-- src/library/placed.rs | 15 +- src/library/text.rs | 928 +++++++++++++++++++++++---------------- src/library/transform.rs | 1 + src/style/mod.rs | 419 ------------------ src/style/paper.rs | 233 ---------- src/util/mod.rs | 12 - tests/typeset.rs | 28 +- 19 files changed, 1119 insertions(+), 1223 deletions(-) create mode 100644 src/eval/styles.rs delete mode 100644 src/style/mod.rs delete mode 100644 src/style/paper.rs diff --git a/Cargo.toml b/Cargo.toml index 2778fd434..8b77f22d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,18 @@ debug = 0 opt-level = 2 [dependencies] -fxhash = "0.2.1" +fxhash = "0.2" image = { version = "0.23", default-features = false, features = ["png", "jpeg"] } itertools = "0.10" miniz_oxide = "0.4" +once_cell = "1" pdf-writer = "0.4" rustybuzz = "0.4" serde = { version = "1", features = ["derive", "rc"] } svg2pdf = { version = "0.1", default-features = false, features = ["text", "png", "jpeg"] } ttf-parser = "0.12" unicode-bidi = "0.3.5" -unicode-segmentation = "1.8" +unicode-segmentation = "1" unicode-xid = "0.2" usvg = { version = "0.19", default-features = false, features = ["text"] } xi-unicode = "0.3" diff --git a/src/eval/mod.rs b/src/eval/mod.rs index e0143f6ce..c1f0b0248 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -6,6 +6,8 @@ mod array; mod dict; #[macro_use] mod value; +#[macro_use] +mod styles; mod capture; mod function; mod node; @@ -18,6 +20,7 @@ pub use dict::*; pub use function::*; pub use node::*; pub use scope::*; +pub use styles::*; pub use value::*; use std::cell::RefMut; @@ -31,13 +34,12 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative, Spec}; use crate::image::ImageStore; -use crate::library::{GridNode, TrackSizing}; +use crate::library::{GridNode, TextNode, TrackSizing}; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; -use crate::style::Style; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; -use crate::util::{BoolExt, EcoString, RefMutExt}; +use crate::util::{EcoString, RefMutExt}; use crate::Context; /// Evaluate a parsed source file into a module. @@ -70,8 +72,8 @@ pub struct EvalContext<'a> { pub modules: HashMap, /// The active scopes. pub scopes: Scopes<'a>, - /// The active style. - pub style: Style, + /// The active styles. + pub styles: Styles, } impl<'a> EvalContext<'a> { @@ -84,7 +86,7 @@ impl<'a> EvalContext<'a> { route: vec![source], modules: HashMap::new(), scopes: Scopes::new(Some(&ctx.std)), - style: ctx.style.clone(), + styles: Styles::new(), } } @@ -158,14 +160,10 @@ impl Eval for Markup { type Output = Node; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - let snapshot = ctx.style.clone(); - let mut result = Node::new(); for piece in self.nodes() { result += piece.eval(ctx)?; } - - ctx.style = snapshot; Ok(result) } } @@ -179,11 +177,11 @@ impl Eval for MarkupNode { Self::Linebreak => Node::Linebreak, Self::Parbreak => Node::Parbreak, Self::Strong => { - ctx.style.text_mut().strong.flip(); + ctx.styles.set(TextNode::STRONG, !ctx.styles.get(TextNode::STRONG)); Node::new() } Self::Emph => { - ctx.style.text_mut().emph.flip(); + ctx.styles.set(TextNode::EMPH, !ctx.styles.get(TextNode::EMPH)); Node::new() } Self::Text(text) => Node::Text(text.clone()), diff --git a/src/eval/node.rs b/src/eval/node.rs index 58b294833..4d259e2d0 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -9,6 +9,7 @@ use crate::geom::SpecAxis; use crate::layout::{Layout, PackedNode}; use crate::library::{ Decoration, DocumentNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, Spacing, + TextNode, }; use crate::util::EcoString; @@ -158,10 +159,10 @@ impl NodePacker { fn walk(&mut self, node: Node) { match node { Node::Space => { - self.push_inline(ParChild::Text(' '.into())); + self.push_inline(ParChild::Text(TextNode(' '.into()))); } Node::Linebreak => { - self.push_inline(ParChild::Text('\n'.into())); + self.push_inline(ParChild::Text(TextNode('\n'.into()))); } Node::Parbreak => { self.parbreak(); @@ -170,7 +171,7 @@ impl NodePacker { self.pagebreak(); } Node::Text(text) => { - self.push_inline(ParChild::Text(text)); + self.push_inline(ParChild::Text(TextNode(text))); } Node::Spacing(axis, amount) => match axis { SpecAxis::Horizontal => self.push_inline(ParChild::Spacing(amount)), diff --git a/src/eval/styles.rs b/src/eval/styles.rs new file mode 100644 index 000000000..30822edbe --- /dev/null +++ b/src/eval/styles.rs @@ -0,0 +1,124 @@ +use std::any::{Any, TypeId}; +use std::collections::HashMap; +use std::fmt::{self, Debug, Formatter}; +use std::rc::Rc; + +// Possible optimizations: +// - Ref-count map for cheaper cloning and smaller footprint +// - Store map in `Option` to make empty maps non-allocating +// - Store small properties inline + +/// A map of style properties. +#[derive(Default, Clone)] +pub struct Styles { + map: HashMap>, +} + +impl Styles { + /// Create a new, empty style map. + pub fn new() -> Self { + Self { map: HashMap::new() } + } + + /// Set the value for a style property. + pub fn set(&mut self, _: P, value: P::Value) { + self.map.insert(TypeId::of::

(), Rc::new(value)); + } + + /// Get the value of a copyable style property. + /// + /// Returns the property's default value if the map does not contain an + /// entry for it. + pub fn get(&self, key: P) -> P::Value + where + P::Value: Copy, + { + self.get_inner(key).copied().unwrap_or_else(P::default) + } + + /// Get a reference to a style property. + /// + /// Returns a reference to the property's default value if the map does not + /// contain an entry for it. + pub fn get_ref(&self, key: P) -> &P::Value { + self.get_inner(key).unwrap_or_else(|| P::default_ref()) + } + + /// Get a reference to a style directly in this map. + fn get_inner(&self, _: P) -> Option<&P::Value> { + self.map + .get(&TypeId::of::

()) + .and_then(|boxed| boxed.downcast_ref()) + } +} + +impl Debug for Styles { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // TODO(set): Better debug printing possible? + f.pad("Styles(..)") + } +} + +/// Stylistic property keys. +pub trait Property: 'static { + /// The type of this property, for example, this could be + /// [`Length`](crate::geom::Length) for a `WIDTH` property. + type Value; + + /// The default value of the property. + fn default() -> Self::Value; + + /// A static reference to the default value of the property. + /// + /// This is automatically implemented through lazy-initialization in the + /// `properties!` macro. This way, expensive defaults don't need to be + /// recreated all the time. + fn default_ref() -> &'static Self::Value; +} + +macro_rules! set { + ($ctx:expr, $target:expr => $source:expr) => { + if let Some(v) = $source { + $ctx.styles.set($target, v); + } + }; +} + +macro_rules! properties { + ($node:ty, $( + $(#[$attr:meta])* + $name:ident: $type:ty = $default:expr + ),* $(,)?) => { + // TODO(set): Fix possible name clash. + mod properties { + use std::marker::PhantomData; + use super::*; + + $(#[allow(non_snake_case)] mod $name { + use $crate::eval::Property; + use once_cell::sync::Lazy; + use super::*; + + pub struct Key(pub PhantomData); + + impl Property for Key<$type> { + type Value = $type; + + fn default() -> Self::Value { + $default + } + + fn default_ref() -> &'static Self::Value { + static LAZY: Lazy<$type> = Lazy::new(|| $default); + &*LAZY + } + } + })* + + impl $node { + $($(#[$attr])* pub const $name: $name::Key<$type> + = $name::Key(PhantomData);)* + } + } + }; +} diff --git a/src/frame.rs b/src/frame.rs index b0442a06d..291259622 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -171,14 +171,19 @@ pub struct Text { pub face_id: FaceId, /// The font size. pub size: Length, - /// The width of the text run. - pub width: Length, /// Glyph color. pub fill: Paint, /// The glyphs. pub glyphs: Vec, } +impl Text { + /// The width of the text run. + pub fn width(&self) -> Length { + self.glyphs.iter().map(|g| g.x_advance.to_length(self.size)).sum() + } +} + /// A glyph in a run of shaped text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Glyph { diff --git a/src/lib.rs b/src/lib.rs index 096995b78..624526554 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,13 +44,12 @@ pub mod library; pub mod loading; pub mod parse; pub mod source; -pub mod style; pub mod syntax; use std::rc::Rc; use crate::diag::TypResult; -use crate::eval::{Module, Scope}; +use crate::eval::{Module, Scope, Styles}; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; @@ -59,7 +58,6 @@ use crate::layout::{EvictionPolicy, LayoutCache}; use crate::library::DocumentNode; use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; -use crate::style::Style; /// The core context which holds the loader, configuration and cached artifacts. pub struct Context { @@ -76,8 +74,8 @@ pub struct Context { pub layouts: LayoutCache, /// The standard library scope. std: Scope, - /// The default style. - style: Style, + /// The default styles. + styles: Styles, } impl Context { @@ -96,9 +94,9 @@ impl Context { &self.std } - /// A read-only reference to the style. - pub fn style(&self) -> &Style { - &self.style + /// A read-only reference to the styles. + pub fn styles(&self) -> &Styles { + &self.styles } /// Evaluate a source file and return the resulting module. @@ -136,7 +134,7 @@ impl Context { /// This struct is created by [`Context::builder`]. pub struct ContextBuilder { std: Option, - style: Option