diff options
Diffstat (limited to 'src')
33 files changed, 934 insertions, 692 deletions
diff --git a/src/eval/collapse.rs b/src/eval/collapse.rs index 0e91cae6..ef8a5255 100644 --- a/src/eval/collapse.rs +++ b/src/eval/collapse.rs @@ -3,7 +3,7 @@ use super::{StyleChain, StyleVec, StyleVecBuilder}; /// A wrapper around a [`StyleVecBuilder`] that allows to collapse items. pub struct CollapsingBuilder<'a, T> { builder: StyleVecBuilder<'a, T>, - staged: Vec<(T, StyleChain<'a>, bool)>, + staged: Vec<(T, StyleChain<'a>, Option<u8>)>, last: Last, } @@ -29,9 +29,21 @@ impl<'a, T: Merge> CollapsingBuilder<'a, T> { /// and to its right, with no destructive items or weak items in between to /// its left and no destructive items in between to its right. There may be /// ignorant items in between in both directions. - pub fn weak(&mut self, item: T, styles: StyleChain<'a>) { - if self.last == Last::Supportive { - self.staged.push((item, styles, true)); + pub fn weak(&mut self, item: T, strength: u8, styles: StyleChain<'a>) { + if self.last != Last::Destructive { + if self.last == Last::Weak { + if let Some(i) = self + .staged + .iter() + .position(|(.., prev)| prev.map_or(false, |p| p < strength)) + { + self.staged.remove(i); + } else { + return; + } + } + + self.staged.push((item, styles, Some(strength))); self.last = Last::Weak; } } @@ -52,7 +64,7 @@ impl<'a, T: Merge> CollapsingBuilder<'a, T> { /// Has no influence on other items. pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) { - self.staged.push((item, styles, false)); + self.staged.push((item, styles, None)); } /// Return the finish style vec and the common prefix chain. @@ -63,8 +75,8 @@ impl<'a, T: Merge> CollapsingBuilder<'a, T> { /// Push the staged items, filtering out weak items if `supportive` is false. fn flush(&mut self, supportive: bool) { - for (item, styles, weak) in self.staged.drain(..) { - if !weak || supportive { + for (item, styles, strength) in self.staged.drain(..) { + if supportive || strength.is_none() { push_merging(&mut self.builder, item, styles); } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 98b0152c..5a67555c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -14,6 +14,7 @@ mod collapse; mod func; mod ops; mod scope; +mod show; mod template; pub use array::*; @@ -23,6 +24,7 @@ pub use collapse::*; pub use dict::*; pub use func::*; pub use scope::*; +pub use show::*; pub use styles::*; pub use template::*; pub use value::*; @@ -33,34 +35,23 @@ use std::io; use std::mem; use std::path::PathBuf; -use once_cell::sync::Lazy; -use syntect::easy::HighlightLines; -use syntect::highlighting::{FontStyle, Highlighter, Style as SynStyle, Theme, ThemeSet}; -use syntect::parsing::SyntaxSet; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; -use crate::geom::{Angle, Color, Fractional, Length, Paint, Relative}; +use crate::geom::{Angle, Fractional, Length, Relative}; use crate::image::ImageStore; use crate::layout::Layout; -use crate::library::{self, DecoLine, TextNode}; +use crate::library::{self}; use crate::loading::Loader; -use crate::parse; use crate::source::{SourceId, SourceStore}; -use crate::syntax; use crate::syntax::ast::*; -use crate::syntax::{RedNode, Span, Spanned}; +use crate::syntax::{Span, Spanned}; use crate::util::{EcoString, RefMutExt}; use crate::Context; -static THEME: Lazy<Theme> = - Lazy::new(|| ThemeSet::load_defaults().themes.remove("InspiredGitHub").unwrap()); - -static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| SyntaxSet::load_defaults_newlines()); - /// An evaluated module, ready for importing or conversion to a root layout /// tree. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct Module { /// The top-level definitions that were bound in this module. pub scope: Scope, @@ -194,17 +185,13 @@ fn eval_markup( MarkupNode::Expr(Expr::Wrap(wrap)) => { let tail = eval_markup(ctx, nodes)?; ctx.scopes.def_mut(wrap.binding().take(), tail); - wrap.body().eval(ctx)?.show() + wrap.body().eval(ctx)?.display() } _ => node.eval(ctx)?, }); } - if seq.len() == 1 { - Ok(seq.into_iter().next().unwrap()) - } else { - Ok(Template::Sequence(seq)) - } + Ok(Template::sequence(seq)) } impl Eval for MarkupNode { @@ -223,7 +210,7 @@ impl Eval for MarkupNode { Self::Heading(heading) => heading.eval(ctx)?, Self::List(list) => list.eval(ctx)?, Self::Enum(enum_) => enum_.eval(ctx)?, - Self::Expr(expr) => expr.eval(ctx)?.show(), + Self::Expr(expr) => expr.eval(ctx)?.display(), }) } } @@ -232,7 +219,7 @@ impl Eval for StrongNode { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - Ok(self.body().eval(ctx)?.styled(TextNode::STRONG, true)) + Ok(Template::show(library::StrongNode(self.body().eval(ctx)?))) } } @@ -240,7 +227,7 @@ impl Eval for EmphNode { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - Ok(self.body().eval(ctx)?.styled(TextNode::EMPH, true)) + Ok(Template::show(library::EmphNode(self.body().eval(ctx)?))) } } @@ -248,104 +235,25 @@ impl Eval for RawNode { type Output = Template; fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> { - let code = self.highlighted(); - Ok(if self.block { Template::Block(code.pack()) } else { code }) - } -} - -impl RawNode { - /// Styled template for a code block, with optional syntax highlighting. - pub fn highlighted(&self) -> Template { - let mut seq: Vec<Template> = vec![]; - - let syntax = if let Some(syntax) = self - .lang - .as_ref() - .and_then(|token| SYNTAXES.find_syntax_by_token(&token)) - { - Some(syntax) - } else if matches!( - self.lang.as_ref().map(|s| s.to_ascii_lowercase()).as_deref(), - Some("typ" | "typst") - ) { - None - } else { - return Template::Text(self.text.clone()).monospaced(); - }; - - let foreground = THEME - .settings - .foreground - .map(Color::from) - .unwrap_or(Color::BLACK) - .into(); - - match syntax { - Some(syntax) => { - let mut highlighter = HighlightLines::new(syntax, &THEME); - for (i, line) in self.text.lines().enumerate() { - if i != 0 { - seq.push(Template::Linebreak); - } - - for (style, piece) in highlighter.highlight(line, &SYNTAXES) { - seq.push(style_piece(piece, foreground, style)); - } - } - } - None => { - let green = parse::parse(&self.text); - let red = RedNode::from_root(green, SourceId::from_raw(0)); - let highlighter = Highlighter::new(&THEME); - - syntax::highlight_syntect( - red.as_ref(), - &highlighter, - &mut |range, style| { - seq.push(style_piece(&self.text[range], foreground, style)); - }, - ) - } - } - - Template::Sequence(seq).monospaced() - } -} - -/// Style a piece of text with a syntect style. -fn style_piece(piece: &str, foreground: Paint, style: SynStyle) -> Template { - let mut styles = StyleMap::new(); - - let paint = style.foreground.into(); - if paint != foreground { - styles.set(TextNode::FILL, paint); - } - - if style.font_style.contains(FontStyle::BOLD) { - styles.set(TextNode::STRONG, true); - } - - if style.font_style.contains(FontStyle::ITALIC) { - styles.set(TextNode::EMPH, true); - } - - if style.font_style.contains(FontStyle::UNDERLINE) { - styles.set(TextNode::LINES, vec![DecoLine::Underline.into()]); + let template = Template::show(library::RawNode { + text: self.text.clone(), + block: self.block, + }); + Ok(match self.lang { + Some(_) => template.styled(library::RawNode::LANG, self.lang.clone()), + None => template, + }) } - - Template::Text(piece.into()).styled_with_map(styles) } impl Eval for MathNode { type Output = Template; fn eval(&self, _: &mut EvalContext) -> TypResult<Self::Output> { - let text = Template::Text(self.formula.trim().into()).monospaced(); - Ok(if self.display { - Template::Block(text.pack()) - } else { - text - }) + Ok(Template::show(library::MathNode { + formula: self.formula.clone(), + display: self.display, + })) } } @@ -353,8 +261,8 @@ impl Eval for HeadingNode { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - Ok(Template::block(library::HeadingNode { - child: self.body().eval(ctx)?.pack(), + Ok(Template::show(library::HeadingNode { + body: self.body().eval(ctx)?, level: self.level(), })) } @@ -364,9 +272,9 @@ impl Eval for ListNode { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - Ok(Template::block(library::ListNode { + Ok(Template::show(library::ListNode { child: self.body().eval(ctx)?.pack(), - kind: library::Unordered, + label: library::Unordered, })) } } @@ -375,9 +283,9 @@ impl Eval for EnumNode { type Output = Template; fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { - Ok(Template::block(library::ListNode { + Ok(Template::show(library::ListNode { child: self.body().eval(ctx)?.pack(), - kind: library::Ordered(self.number()), + label: library::Ordered(self.number()), })) } } diff --git a/src/eval/show.rs b/src/eval/show.rs new file mode 100644 index 00000000..6157485d --- /dev/null +++ b/src/eval/show.rs @@ -0,0 +1,96 @@ +use std::any::{Any, TypeId}; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; + +use super::{StyleChain, Template}; +use crate::util::Prehashed; + +/// A node that can be realized given some styles. +pub trait Show { + /// Realize the template in the given styles. + fn show(&self, styles: StyleChain) -> Template; + + /// Convert to a packed show node. + fn pack(self) -> ShowNode + where + Self: Debug + Hash + Sized + Sync + Send + 'static, + { + ShowNode::new(self) + } +} + +/// A type-erased showable node with a precomputed hash. +#[derive(Clone, Hash)] +pub struct ShowNode(Arc<Prehashed<dyn Bounds>>); + +impl ShowNode { + /// Pack any showable node. + pub fn new<T>(node: T) -> Self + where + T: Show + Debug + Hash + Sync + Send + 'static, + { + Self(Arc::new(Prehashed::new(node))) + } + + /// The type id of this node. + pub fn id(&self) -> TypeId { + self.0.as_any().type_id() + } +} + +impl Show for ShowNode { + fn show(&self, styles: StyleChain) -> Template { + self.0.show(styles) + } + + fn pack(self) -> ShowNode { + self + } +} + +impl Debug for ShowNode { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for ShowNode { + fn eq(&self, other: &Self) -> bool { + // We cast to thin pointers for comparison because we don't want to + // compare vtables (which can be different across codegen units). + std::ptr::eq( + Arc::as_ptr(&self.0) as *const (), + Arc::as_ptr(&other.0) as *const (), + ) + } +} + +trait Bounds: Show + Debug + Sync + Send + 'static { + fn as_any(&self) -> &dyn Any; + fn hash64(&self) -> u64; +} + +impl<T> Bounds for T +where + T: Show + Debug + Hash + Sync + Send + 'static, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn hash64(&self) -> u64 { + // Also hash the TypeId since nodes with different types but + // equal data should be different. + let mut state = fxhash::FxHasher64::default(); + self.type_id().hash(&mut state); + self.hash(&mut state); + state.finish() + } +} + +impl Hash for dyn Bounds { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.hash64()); + } +} diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 863dcc6f..2bf3f239 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -49,6 +49,11 @@ impl StyleMap { self } + /// Whether this map contains scoped styles. + pub fn has_scoped(&self) -> bool { + self.0.iter().any(|e| e.scoped) + } + /// Make `self` the first link of the style chain `outer`. /// /// The resulting style chain contains styles from `self` as well as @@ -136,20 +141,6 @@ impl<'a> StyleChain<'a> { self.links().count() } - /// Convert to an owned style map. - /// - /// Panics if the chain contains barrier links. - pub fn to_map(self) -> StyleMap { - let mut suffix = StyleMap::new(); - for link in self.links() { - match link { - Link::Map(map) => suffix.apply(map), - Link::Barrier(_) => panic!("chain contains barrier"), - } - } - suffix - } - /// Build a style map from the suffix (all links beyond the `len`) of the /// chain. /// @@ -170,6 +161,17 @@ impl<'a> StyleChain<'a> { pub fn pop(&mut self) { *self = self.outer.copied().unwrap_or_default(); } + + /// Return the chain, but without the last link if that one contains only + /// scoped styles. This is a hack. + pub(crate) fn unscoped(mut self, node: TypeId) -> Self { + if let Some(Link::Map(map)) = self.link { + if map.0.iter().all(|e| e.scoped && e.is_of_id(node)) { + self.pop(); + } + } + self + } } impl<'a> StyleChain<'a> { diff --git a/src/eval/template.rs b/src/eval/template.rs index a6693c84..84888b95 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -6,12 +6,15 @@ use std::ops::{Add, AddAssign}; use typed_arena::Arena; -use super::{CollapsingBuilder, Interruption, Property, StyleMap, StyleVecBuilder}; +use super::{ + CollapsingBuilder, Interruption, Property, Show, ShowNode, StyleMap, StyleVecBuilder, +}; use crate::diag::StrResult; -use crate::layout::{Layout, PackedNode}; +use crate::layout::{Layout, LayoutNode}; use crate::library::prelude::*; use crate::library::{ - FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind, TextNode, + DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind, + TextNode, Underline, }; use crate::util::EcoString; use crate::Context; @@ -52,7 +55,7 @@ pub enum Template { /// Plain text. Text(EcoString), /// An inline-level node. - Inline(PackedNode), + Inline(LayoutNode), /// A paragraph break. Parbreak, /// A column break. @@ -60,21 +63,23 @@ pub enum Template { /// Vertical spacing. Vertical(SpacingKind), /// A block-level node. - Block(PackedNode), + Block(LayoutNode), /// A page break. Pagebreak, /// A page node. Page(PageNode), + /// A node that can be realized with styles. + Show(ShowNode), /// A template with attached styles. - Styled(Box<Self>, StyleMap), + Styled(Arc<(Self, StyleMap)>), /// A sequence of multiple subtemplates. - Sequence(Vec<Self>), + Sequence(Arc<Vec<Self>>), } impl Template { /// Create an empty template. pub fn new() -> Self { - Self::Sequence(vec![]) + Self::sequence(vec![]) } /// Create a template from an inline-level node. @@ -93,42 +98,63 @@ impl Template { Self::Block(node.pack()) } - /// Layout this template into a collection of pages. - pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> { - let (mut ctx, styles) = LayoutContext::new(ctx); - let (pages, shared) = Builder::build_pages(self); - let styles = shared.chain(&styles); - pages - .iter() - .flat_map(|(page, map)| page.layout(&mut ctx, map.chain(&styles))) - .collect() + /// Create a template from a showable node. + pub fn show<T>(node: T) -> Self + where + T: Show + Debug + Hash + Sync + Send + 'static, + { + Self::Show(node.pack()) } /// Style this template with a single property. pub fn styled<P: Property>(mut self, key: P, value: P::Value) -> Self { - if let Self::Styled(_, map) = &mut self { - map.set(key, value); - self - } else { - self.styled_with_map(StyleMap::with(key, value)) + if let Self::Styled(styled) = &mut self { + if let Some((_, map)) = Arc::get_mut(styled) { + if !map.has_scoped() { + map.set(key, value); + } + return self; + } } + + self.styled_with_map(StyleMap::with(key, value)) } /// Style this template with a full style map. pub fn styled_with_map(mut self, styles: StyleMap) -> Self { if styles.is_empty() { - self - } else if let Self::Styled(_, map) = &mut self { - map.apply(&styles); - self - } else { - Self::Styled(Box::new(self), styles) + return self; } + + if let Self::Styled(styled) = &mut self { + if let Some((_, map)) = Arc::get_mut(styled) { + if !styles.has_scoped() && !map.has_scoped() { + map.apply(&styles); + return self; + } + } + } + + Self::Styled(Arc::new((self, styles))) } /// Style this template in monospace. pub fn monospaced(self) -> Self { - self.styled(TextNode::MONOSPACE, true) + self.styled(TextNode::MONOSPACED, true) + } + + /// Underline this template. + pub fn underlined(self) -> Self { + Self::show(DecoNode { kind: Underline, body: self }) + } + + /// Create a new sequence template. + pub fn sequence(seq: Vec<Self>) -> Self { + if seq.len() == 1 { + seq.into_iter().next().unwrap() + } else { + Self::Sequence(Arc::new(seq)) + } } /// Repeat this template `n` times. @@ -136,7 +162,24 @@ impl Template { let count = usize::try_from(n) .map_err(|_| format!("cannot repeat this template {} times", n))?; - Ok(Self::Sequence(vec![self.clone(); count])) + Ok(Self::sequence(vec![self.clone(); count])) + } + + /// Layout this template into a collection of pages. + pub fn layout(&self, ctx: &mut Context) -> Vec<Arc<Frame>> { + let style_arena = Arena::new(); + let template_arena = Arena::new(); + let (mut ctx, styles) = LayoutContext::new(ctx); + + let mut builder = Builder::new(&style_arena, &template_arena, true); + builder.process(self, styles); + builder.finish_page(true, false, styles); + + let (pages, shared) = builder.pages.unwrap().finish(); + pages + .iter() + .flat_map(|(page, map)| page.layout(&mut ctx, map.chain(&shared))) + .collect() } } @@ -168,11 +211,17 @@ impl Debug for Template { } Self::Pagebreak => f.pad("Pagebreak"), Self::Page(page) => page.fmt(f), - Self::Styled(sub, map) => { + Self::Show(node) => { + f.write_str("Show(")?; + node.fmt(f)?; + f.write_str(")") + } + Self::Styled(styled) => { + let (sub, map) = styled.as_ref(); map.fmt(f)?; sub.fmt(f) } - Self::Sequence(seq) => f.debug_list().entries(seq).finish(), + Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(), } } } @@ -183,20 +232,22 @@ impl Add for Template { fn add(self, rhs: Self) -> Self::Output { Self::Sequence(match (self, rhs) { (Self::Sequence(mut lhs), Self::Sequence(rhs)) => { - lhs.extend(rhs); + let mutable = Arc::make_mut(&mut lhs); + match Arc::try_unwrap(rhs) { + Ok(vec) => mutable.extend(vec), + Err(rc) => mutable.extend(rc.iter().cloned()), + } lhs } (Self::Sequence(mut lhs), rhs) => { - lhs.push(rhs); + Arc::make_mut(&mut lhs).push(rhs); lhs } (lhs, Self::Sequence(mut rhs)) => { - rhs.insert(0, lhs); + Arc::make_mut(&mut rhs).insert(0, lhs); rhs } - (lhs, rhs) => { - vec![lhs, rhs] - } + (lhs, rhs) => Arc::new(vec![lhs, rhs]), }) } } @@ -209,7 +260,7 @@ impl AddAssign for Template { impl Sum for Template { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self::Sequence(iter.collect()) + Self::sequence(iter.collect()) } } @@ -220,14 +271,21 @@ impl Layout for Template { regions: &Regions, styles: StyleChain, ) -> Vec<Constrained<Arc<Frame>>> { - let (flow, shared) = Builder::build_flow(self); - flow.layout(ctx, regions, shared.chain(&styles)) + let style_arena = Arena::new(); + let template_arena = Arena::new(); + + let mut builder = Builder::new(&style_arena, &template_arena, false); + builder.process(self, styles); + builder.finish_par(styles); + + let (flow, shared) = builder.flow.finish(); + FlowNode(flow).layout(ctx, regions, shared) } - fn pack(self) -> PackedNode { + fn pack(self) -> LayoutNode { match self { Template::Block(node) => node, - other => PackedNode::new(other), + other => LayoutNode::new(other), } } } @@ -235,7 +293,9 @@ impl Layout for Template { /// Builds a flow or page nodes from a template. struct Builder<'a> { /// An arena where intermediate style chains are stored. - arena: &'a Arena<StyleChain<'a>>, + style_arena: &'a Arena<StyleChain<'a>>, + /// An arena where intermediate templates are stored. + template_arena: &'a Arena<Template>, /// The already built page runs. pages: Option<StyleVecBuilder<'a, PageNode>>, /// The currently built flow. @@ -247,34 +307,15 @@ struct Builder<'a> { } impl<'a> Builder<'a> { - /// Build page runs from a template. - fn build_pages(template: &Template) -> (StyleVec<PageNode>, StyleMap) { - let arena = Arena::new(); - - let mut builder = Builder::prepare(&arena, true); - builder.process(template, StyleChain::default()); - builder.finish_page(true, false, StyleChain::default()); - - let (pages, shared) = builder.pages.unwrap().finish(); - (pages, shared.to_map()) - } - - /// Build a subflow from a template. - fn build_flow(template: &Template) -> (FlowNode, StyleMap) { - let arena = Arena::new(); - - let mut builder = Builder::prepare(&arena, false); - builder.process(template, StyleChain::default()); - builder.finish_par(); - - let (flow, shared) = builder.flow.finish(); - (FlowNode(flow), shared.to_map()) - } - /// Prepare the builder. - fn prepare(arena: &'a Arena<StyleChain<'a>>, top: bool) -> Self { + fn new( + style_arena: &'a Arena<StyleChain<'a>>, + template_arena: &'a Arena<Template>, + top: bool, + ) -> Self { Self { - arena, + style_arena, + template_arena, pages: top.then(|| StyleVecBuilder::new()), flow: CollapsingBuilder::new(), par: CollapsingBuilder::new(), @@ -286,7 +327,7 @@ impl<'a> Builder<'a> { fn process(&mut self, template: &'a Template, styles: StyleChain<'a>) { match template { Template::Space => { - self.par.weak(ParChild::Text(' '.into()), styles); + self.par.weak(ParChild::Text(' '.into()), 0, styles); } Template::Linebreak => { self.par.destructive(ParChild::Text('\n'.into()), styles); @@ -306,15 +347,15 @@ impl<'a> Builder<'a> { self.par.supportive(ParChild::Node(node.clone()), styles); } Template::Parbreak => { - self.finish_par(); - self.flow.weak(FlowChild::Parbreak, styles); + self.finish_par(styles); + self.flow.weak(FlowChild::Parbreak, 1, styles); } Template::Colbreak => { - self.finish_par(); + self.finish_par(styles); self.flow.destructive(FlowChild::Colbreak, styles); } Template::Vertical(kind) => { - self.finish_par(); + self.finish_par(styles); let child = FlowChild::Spacing(*kind); if kind.is_fractional() { self.flow.destructive(child, styles); @@ -323,13 +364,14 @@ impl<'a> Builder<'a> { } } Template::Block(node) => { - self.finish_par(); + self.finish_par(styles); let child = FlowChild::Node(node.clone()); if node.is::<PlaceNode>() { self.flow.ignorant(child, styles); } else { self.flow.supportive(child, styles); } + self.finish_par(styles); } Template::Pagebreak => { self.finish_page(true, true, styles); @@ -340,26 +382,32 @@ impl<'a> Builder<'a> { pages.push(page.clone(), styles); } } - Template::Styled(sub, map) => { + Template::Show(node) => { + let template = self.template_arena.alloc(node.show(styles)); + self.process(template, styles.unscoped(node.id())); + } + Template::Styled(styled) => { + let (sub, map) = styled.as_ref(); + let stored = self.style_arena.alloc(styles); + let styles = map.chain(stored); + let interruption = map.interruption(); match interruption { Some(Interruption::Page) => self.finish_page(false, true, styles), - Some(Interruption::Par) => self.finish_par(), + Some(Interruption::Par) => self.finish_par(styles), None => {} } - let outer = self.arena.alloc(styles); - let styles = map.chain(outer); self.process(sub, styles); match interruption { Some(Interruption::Page) => self.finish_page(true, false, styles), - Some(Interruption::Par) => self.finish_par(), + Some(Interruption::Par) => self.finish_par(styles), None => {} } } Template::Sequence(seq) => { - for sub in seq { + for sub in seq.iter() { self.process(sub, styles); } } @@ -367,17 +415,18 @@ impl<'a> Builder<'a> { } /// Finish the currently built paragraph. - fn finish_par(&mut self) { + fn finish_par(&mut self, styles: StyleChain<'a>) { let (par, shared) = mem::take(&mut self.par).finish(); if !par.is_empty() { let node = ParNode(par).pack(); self.flow.supportive(FlowChild::Node(node), shared); } + self.flow.weak(FlowChild::Leading, 0, styles); } /// Finish the currently built page run. fn finish_page(&mut self, keep_last: bool, keep_next: bool, styles: StyleChain<'a>) { - self.finish_par(); + self.finish_par(styles); if let Some(pages) = &mut self.pages { let (flow, shared) = mem::take(&mut self.flow).finish(); if !flow.is_empty() || (keep_last && self.keep_next) { diff --git a/src/eval/value.rs b/src/eval/value.rs index d0b822f1..2d37b34f 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -115,7 +115,7 @@ impl Value { } /// Return the display representation of the value. - pub fn show(self) -> Template { + pub fn display(self) -> Template { match self { Value::None => Template::new(), Value::Int(v) => Template::Text(format_eco!("{}", v)), diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 3cccee28..139d027a 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -10,7 +10,7 @@ pub use constraints::*; pub use incremental::*; pub use regions::*; -use std::any::Any; +use std::any::{Any, TypeId}; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; @@ -21,6 +21,7 @@ use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec}; use crate::image::ImageStore; use crate::library::{AlignNode, Move, PadNode, TransformNode}; +use crate::util::Prehashed; use crate::Context; /// A node that can be layouted into a sequence of regions. @@ -37,11 +38,11 @@ pub trait Layout { ) -> Vec<Constrained<Arc<Frame>>>; /// Convert to a packed node. - fn pack(self) -> PackedNode + fn pack(self) -> LayoutNode where Self: Debug + Hash + Sized + Sync + Send + 'static, { - PackedNode::new(self) + LayoutNode::new(self) } } @@ -75,31 +76,26 @@ impl<'a> LayoutContext<'a> { } /// A type-erased layouting node with a precomputed hash. -#[derive(Clone)] -pub struct PackedNode { - /// The type-erased node. - node: Arc<dyn Bounds>, - /// A precomputed hash for the node. - #[cfg(feature = "layout-cache")] - hash: u64, -} +#[derive(Clone, Hash)] +pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>); -impl PackedNode { +impl LayoutNode { /// Pack any layoutable node. pub fn new<T>(node: T) -> Self where T: Layout + Debug + Hash + Sync + Send + 'static, { - Self { - #[cfg(feature = "layout-cache")] - hash: node.hash64(), - node: Arc::new(node), - } + Self(Arc::new(Prehashed::new(node))) } /// Check whether the contained node is a specific layout node. pub fn is<T: 'static>(&self) -> bool { - self.node.as_any().is::<T>() + self.0.as_any().is::<T>() + } + + /// The type id of this node. + pub fn id(&self) -> TypeId { + self.0.as_any().type_id() } /// Try to downcast to a specific layout node. @@ -107,7 +103,7 @@ impl PackedNode { where T: Layout + Debug + Hash + 'static, { - self.node.as_any().downcast_ref() + self.0.as_any().downcast_ref() } /// Force a size for this node. @@ -165,7 +161,7 @@ impl PackedNode { } } -impl Layout for PackedNode { +impl Layout for LayoutNode { #[track_caller] fn layout( &self, @@ -173,7 +169,7 @@ impl Layout for PackedNode { regions: &Regions, styles: StyleChain, ) -> Vec<Constrained<Arc<Frame>>> { - let styles = styles.barred(self.node.as_any().type_id()); + let styles = styles.barred(self.id()); #[cfg(not(feature = "layout-cache"))] return self.node.layout(ctx, regions, styles); @@ -193,14 +189,14 @@ impl Layout for PackedNode { frames } else { ctx.level += 1; - let frames = self.node.layout(ctx, regions, styles); + let frames = self.0.layout(ctx, regions, styles); ctx.level -= 1; let entry = FramesEntry::new(frames.clone(), ctx.level); #[cfg(debug_assertions)] if !entry.check(regions) { - eprintln!("node: {:#?}", self.node); + eprintln!("node: {:#?}", self.0); eprintln!("regions: {regions:#?}"); eprintln!( "constraints: {:#?}", @@ -214,44 +210,34 @@ impl Layout for PackedNode { } } - fn pack(self) -> PackedNode { + fn pack(self) -> LayoutNode { self } } -impl Default for PackedNode { +impl Default for LayoutNode { fn default() -> Self { EmptyNode.pack() } } -impl Debug for PackedNode { +impl Debug for LayoutNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.node.fmt(f) + self.0.fmt(f) } } -impl PartialEq for PackedNode { +impl PartialEq for LayoutNode { fn eq(&self, other: &Self) -> bool { // We cast to thin pointers for comparison because we don't want to // compare vtables (which can be different across codegen units). std::ptr::eq( - Arc::as_ptr(&self.node) as *const (), - Arc::as_ptr(&other.node) as *const (), + Arc::as_ptr(&self.0) as *const (), + Arc::as_ptr(&other.0) as *const (), ) } } -impl Hash for PackedNode { - fn hash<H: Hasher>(&self, state: &mut H) { - // Hash the node. - #[cfg(feature = "layout-cache")] - state.write_u64(self.hash); - #[cfg(not(feature = "layout-cache"))] - state.write_u64(self.hash64()); - } -} - trait Bounds: Layout + Debug + Sync + Send + 'static { fn as_any(&self) -> &dyn Any; fn hash64(&self) -> u64; @@ -275,6 +261,12 @@ where } } +impl Hash for dyn Bounds { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_u64(self.hash64()); + } +} + /// A layout node that produces an empty frame. /// /// The packed version of this is returned by [`PackedNode::default`]. @@ -301,7 +293,7 @@ struct SizedNode { /// How to size the node horizontally and vertically. sizing: Spec<Option<Linear>>, /// The node to be sized. - child: PackedNode, + child: LayoutNode, } impl Layout for SizedNode { @@ -355,7 +347,7 @@ struct FillNode { /// How to fill the frames resulting from the `child`. fill: Paint, /// The node to fill. - child: PackedNode, + child: LayoutNode, } impl Layout for FillNode { @@ -380,7 +372,7 @@ struct StrokeNode { /// How to stroke the frames resulting from the `child`. stroke: Stroke, /// The node to stroke. - child: PackedNode, + child: LayoutNode, } impl Layout for StrokeNode { diff --git a/src/library/align.rs b/src/library/align.rs index 5d89dcb6..5dc12938 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -9,14 +9,14 @@ pub struct AlignNode { /// How to align the node horizontally and vertically. pub aligns: Spec<Option<Align>>, /// The node to be aligned. - pub child: PackedNode, + pub child: LayoutNode, } #[class] impl AlignNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { let aligns: Spec<_> = args.find().unwrap_or_default(); - let body: PackedNode = args.expect("body")?; + let body: LayoutNode = args.expect("body")?; Ok(Template::block(body.aligned(aligns))) } } diff --git a/src/library/columns.rs b/src/library/columns.rs index 9e696b49..98989c5a 100644 --- a/src/library/columns.rs +++ b/src/library/columns.rs @@ -10,7 +10,7 @@ pub struct ColumnsNode { pub columns: NonZeroUsize, /// The child to be layouted into the columns. Most likely, this should be a /// flow or stack node. - pub child: PackedNode, + pub child: LayoutNode, } #[class] @@ -24,11 +24,6 @@ impl ColumnsNode { child: args.expect("body")?, })) } - - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - styles.set_opt(Self::GUTTER, args.named("gutter")?); - Ok(()) - } } impl Layout for ColumnsNode { diff --git a/src/library/container.rs b/src/library/container.rs index d2a4f481..143bddfc 100644 --- a/src/library/container.rs +++ b/src/library/container.rs @@ -10,7 +10,7 @@ impl BoxNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { let width = args.named("width")?; let height = args.named("height")?; - let body: PackedNode = args.find().unwrap_or_default(); + let body: LayoutNode = args.find().unwrap_or_default(); Ok(Template::inline(body.sized(Spec::new(width, height)))) } } diff --git a/src/library/deco.rs b/src/library/deco.rs index ec2235bc..40ef4e73 100644 --- a/src/library/deco.rs +++ b/src/library/deco.rs @@ -4,57 +4,67 @@ use super::prelude::*; use super::TextNode; /// Typeset underline, striken-through or overlined text. -pub struct DecoNode<L: LineKind>(pub L); +#[derive(Debug, Hash)] +pub struct DecoNode<L: LineKind> { + /// The kind of line. + pub kind: L, + /// The decorated contents. + pub body: Template, +} #[class] impl<L: LineKind> DecoNode<L> { + /// Stroke color of the line, defaults to the text color if `None`. + #[shorthand] + pub const STROKE: Option<Paint> = None; + /// Thickness of the line's strokes (dependent on scaled font size), read + /// from the font tables if `None`. + #[shorthand] + pub const THICKNESS: Option<Linear> = None; + /// Position of the line relative to the baseline (dependent on scaled font + /// size), read from the font tables if `None`. + pub const OFFSET: Option<Linear> = None; + /// Amount that the line will be longer or shorter than its associated text + /// (dependent on scaled font size). + pub const EXTENT: Linear = Linear::zero(); + /// Whether the line skips sections in which it would collide + /// with the glyphs. Does not apply to strikethrough. + pub const EVADE: bool = true; + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { - let deco = Decoration { + Ok(Template::show(Self { + kind: L::default(), + body: args.expect::<Template>("body")?, + })) + } +} + +impl<L: LineKind> Show for DecoNode<L> { + fn show(&self, styles: StyleChain) -> Template { + self.body.clone().styled(TextNode::LINES, vec![Decoration { line: L::LINE, - stroke: args.named("stroke")?.or_else(|| args.find()), - thickness: args.named::<Linear>("thickness")?.or_else(|| args.find()), - offset: args.named("offset")?, - extent: args.named("extent")?.unwrap_or_default(), - evade: args.named("evade")?.unwrap_or(true), - }; - Ok(args.expect::<Template>("body")?.styled(TextNode::LINES, vec![deco])) + stroke: styles.get(Self::STROKE), + thickness: styles.get(Self::THICKNESS), + offset: styles.get(Self::OFFSET), + extent: styles.get(Self::EXTENT), + evade: styles.get(Self::EVADE), + }]) } } /// Defines a line that is positioned over, under or on top of text. +/// +/// For more details, see [`DecoNode`]. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Decoration { - /// Which line to draw. pub line: DecoLine, - /// Stroke color of the line, defaults to the text color if `None`. pub stroke: Option<Paint>, - /// Thickness of the line's strokes (dependent on scaled font size), read - /// from the font tables if `None`. pub thickness: Option<Linear>, - /// Position of the line relative to the baseline (dependent on scaled font - /// size), read from the font tables if `None`. pub offset: Option<Linear>, - /// Amount that the line will be longer or shorter than its associated text - /// (dependent on scaled font size). pub extent: Linear, - /// Whether the line skips sections in which it would collide - /// with the glyphs. Does not apply to strikethrough. pub evade: bool, } -impl From<DecoLine> for Decoration { - fn from(line: DecoLine) -> Self { - Self { - line, - stroke: None, - thickness: None, - offset: None, - extent: Linear::zero(), - evade: true, - } - } -} - /// The kind of decorative line. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum DecoLine { @@ -67,7 +77,7 @@ pub enum DecoLine { } /// Different kinds of decorative lines for text. -pub trait LineKind { +pub trait LineKind: Debug + Default + Hash + Sync + Send + 'static { const LINE: DecoLine; } diff --git a/src/library/flow.rs b/src/library/flow.rs index bba296a0..ca554033 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -13,6 +13,8 @@ pub struct FlowNode(pub StyleVec<FlowChild>); /// A child of a flow node. #[derive(Hash)] pub enum FlowChild { + /// Leading between other children. + Leading, /// A paragraph / block break. Parbreak, /// A column / region break. @@ -20,7 +22,7 @@ pub enum FlowChild { /// Vertical spacing between other children. Spacing(SpacingKind), /// An arbitrary block-level node. - Node(PackedNode), + Node(LayoutNode), } impl Layout for FlowNode { @@ -35,10 +37,17 @@ impl Layout for FlowNode { for (child, map) in self.0.iter() { let styles = map.chain(&styles); match child { + FlowChild::Leading => { + let em = styles.get(TextNode::SIZE).abs; + let amount = styles.get(ParNode::LEADING).resolve(em); + layouter.layout_spacing(amount.into()); + } FlowChild::Parbreak => { let em = styles.get(TextNode::SIZE).abs; - let amount = styles.get(ParNode::SPACING).resolve(em); - layouter.layout_spacing(SpacingKind::Linear(amount.into())); + let leading = styles.get(ParNode::LEADING); + let spacing = styles.get(ParNode::SPACING); + let amount = (leading + spacing).resolve(em); + layouter.layout_spacing(amount.into()); } FlowChild::Colbreak => { layouter.finish_region(); @@ -72,6 +81,7 @@ impl Debug for FlowNode { impl Debug for FlowChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { + Self::Leading => f.pad("Leading"), Self::Parbreak => f.pad("Parbreak"), Self::Colbreak => f.pad("Colbreak"), Self::Spacing(kind) => write!(f, "{:?}", kind), @@ -93,8 +103,6 @@ pub struct FlowLayouter { used: Size, /// The sum of fractional ratios in the current region. fr: Fractional, - /// Whether to add leading before the next node. - leading: bool, /// Spacing and layouted nodes. items: Vec<FlowItem>, /// Finished frames for previous regions. @@ -129,7 +137,6 @@ impl FlowLayouter { full, used: Size::zero(), fr: Fractional::zero(), - leading: false, items: vec![], finished: vec![], } @@ -149,7 +156,6 @@ impl FlowLayouter { SpacingKind::Fractional(v) => { self.items.push(FlowItem::Fractional(v)); self.fr += v; - self.leading = false; } } } @@ -158,7 +164,7 @@ impl FlowLayouter { pub fn layout_node( &mut self, ctx: &mut LayoutContext, - node: &PackedNode, + node: &LayoutNode, styles: StyleChain, ) { // Don't even try layouting into a full region. @@ -168,9 +174,7 @@ impl FlowLayouter { // Placed nodes that are out of flow produce placed items which aren't // aligned later. - let mut is_placed = false; if let Some(placed) = node.downcast::<PlaceNode>() { - is_placed = true; if placed.out_of_flow() { let frame = node.layout(ctx, &self.regions, styles).remove(0); self.items.push(FlowItem::Placed(frame.item)); @@ -178,13 +182,6 @@ impl FlowLayouter { } } - // Add leading. - if self.leading { - let em = styles.get(TextNode::SIZE).abs; - let amount = styles.get(ParNode::LEADING).resolve(em); - self.layout_spacing(SpacingKind::Linear(amount.into())); - } - // How to align the node. let aligns = Spec::new( // For non-expanding paragraphs it is crucial that we align the @@ -210,8 +207,6 @@ impl FlowLayouter { self.finish_region(); } } - - self.leading = !is_placed; } /// Finish the frame for one region. @@ -264,7 +259,6 @@ impl FlowLayouter { self.full = self.regions.current; self.used = Size::zero(); self.fr = Fractional::zero(); - self.leading = false; self.finished.push(output.constrain(cts)); } diff --git a/src/library/grid.rs b/src/library/grid.rs index 59fe8486..f85e5d56 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -10,7 +10,7 @@ pub struct GridNode { /// Defines sizing of gutter rows and columns between content. pub gutter: Spec<Vec<TrackSizing>>, /// The nodes to be arranged in a grid. - pub children: Vec<PackedNode>, + pub children: Vec<LayoutNode>, } #[class] @@ -92,7 +92,7 @@ castable! { /// Performs grid layout. pub struct GridLayouter<'a> { /// The grid cells. - cells: &'a [PackedNode], + cells: &'a [LayoutNode], /// The column tracks including gutter tracks. cols: Vec<TrackSizing>, /// The row tracks including gutter tracks. @@ -136,7 +136,7 @@ impl<'a> GridLayouter<'a> { pub fn new( tracks: Spec<&[TrackSizing]>, gutter: Spec<&[TrackSizing]>, - cells: &'a [PackedNode], + cells: &'a [LayoutNode], regions: &Regions, styles: StyleChain<'a>, ) -> Self { @@ -606,7 +606,7 @@ impl<'a> GridLayouter<'a> { /// /// Returns `None` if it's a gutter cell. #[track_caller] - fn cell(&self, x: usize, y: usize) -> Option<&'a PackedNode> { + fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> { assert!(x < self.cols.len()); assert!(y < self.rows.len()); diff --git a/src/library/heading.rs b/src/library/heading.rs index ab0864e7..00a48021 100644 --- a/src/library/heading.rs +++ b/src/library/heading.rs @@ -9,8 +9,8 @@ pub struct HeadingNode { /// The logical nesting depth of the section, starting from one. In the /// default style, this controls the text size of the heading. pub level: usize, - /// The node that produces the heading's contents. - pub child: PackedNode, + /// The heading's contents. + pub body: Template, } #[class] @@ -23,70 +23,76 @@ impl HeadingNode { /// The fill color of text in the heading. Just the surrounding text color /// if `auto`. pub const FILL: Smart<Paint> = Smart::Auto; + /// Whether text in the heading is strengthend. + pub const STRONG: bool = true; + /// Whether text in the heading is emphasized. + pub const EMPH: bool = false; + /// Whether the heading is underlined. + pub const UNDERLINE: bool = false; /// The extra padding above the heading. pub const ABOVE: Length = Length::zero(); /// The extra padding below the heading. pub const BELOW: Length = Length::zero(); fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { - Ok(Template::block(Self { - child: args.expect("body")?, + Ok(Template::show(Self { + body: args.expect("body")?, level: args.named("level")?.unwrap_or(1), })) } - - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - styles.set_opt(Self::FAMILY, args.named("family")?); - styles.set_opt(Self::SIZE, args.named("size")?); - styles.set_opt(Self::FILL, args.named("fill")?); - styles.set_opt(Self::ABOVE, args.named("above")?); - styles.set_opt(Self::BELOW, args.named("below")?); - Ok(()) - } } -impl Layout for HeadingNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - styles: StyleChain, - ) -> Vec<Constrained<Arc<Frame>>> { - let upscale = (1.6 - 0.1 * self.level as f64).max(0.75); +impl Show for HeadingNode { + fn show(&self, styles: StyleChain) -> Template { + let mut map = StyleMap::new(); - let mut passed = StyleMap::new(); - passed.set(TextNode::STRONG, true); - passed.set( + let upscale = (1.6 - 0.1 * self.level as f64).max(0.75); + map.set( TextNode::SIZE, styles.get(Self::SIZE).unwrap_or(Relative::new(upscale).into()), ); if let Smart::Custom(family) = styles.get_ref(Self::FAMILY) { - passed.set( - TextNode::FAMILY_LIST, + map.set( + TextNode::FAMILY, std::iter::once(family) - .chain(styles.get_ref(TextNode::FAMILY_LIST)) + .chain(styles.get_ref(TextNode::FAMILY)) .cloned() .collect(), ); } if let Smart::Custom(fill) = styles.get(Self::FILL) { - passed.set(TextNode::FILL, fill); + map.set(TextNode::FILL, fill); + } + + if styles.get(Self::STRONG) { + map.set(TextNode::STRONG, true); + } + + if styles.get(Self::EMPH) { + map.set(TextNode::EMPH, true); + } + + let mut body = self.body.clone(); + if styles.get(Self::UNDERLINE) { + body = body.underlined(); } - let mut frames = self.child.layout(ctx, regions, passed.chain(&styles)); + let mut seq = vec![]; let above = styles.get(Self::ABOVE); - let below = styles.get(Self::BELOW); + if !above.is_zero() { + seq.push(Template::Vertical(above.into())); + } + + seq.push(body); - // FIXME: Constraints and region size. - for Constrained { item: frame, .. } in &mut frames { - let frame = Arc::make_mut(frame); - frame.size.y += above + below; - frame.translate(Point::with_y(above)); + let below = styles.get(Self::BELOW); + if !below.is_zero() { + seq.push(Template::Vertical(below.into())); } - frames + Template::block(Template::sequence(seq).styled_with_map(map)) } } diff --git a/src/library/hide.rs b/src/library/hide.rs index cfef8f73..72d8e52b 100644 --- a/src/library/hide.rs +++ b/src/library/hide.rs @@ -4,7 +4,7 @@ use super::prelude::*; /// Hide a node without affecting layout. #[derive(Debug, Hash)] -pub struct HideNode(pub PackedNode); +pub struct HideNode(pub LayoutNode); #[class] impl HideNode { diff --git a/src/library/image.rs b/src/library/image.rs index 309ffad3..59e30364 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -31,11 +31,6 @@ impl ImageNode { ImageNode(id).pack().sized(Spec::new(width, height)), )) } - - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - styles.set_opt(Self::FIT, args.named("fit")?); - Ok(()) - } } impl Layout for ImageNode { diff --git a/src/library/link.rs b/src/library/link.rs index a27908c7..a43c5290 100644 --- a/src/library/link.rs +++ b/src/library/link.rs @@ -5,10 +5,22 @@ use super::TextNode; use crate::util::EcoString; /// Link text and other elements to an URL. -pub struct LinkNode; +#[derive(Debug, Hash)] +pub struct LinkNode { + /// The url the link points to. + pub url: EcoString, + /// How the link is represented. + pub body: Template, +} #[class] impl LinkNode { + /// The fill color of text in the link. Just the surrounding text color + /// if `auto`. + pub const FILL: Smart<Paint> = Smart::Auto; + /// Whether to underline link. + pub const UNDERLINE: bool = true; + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { let url = args.expect::<EcoString>("url")?; let body = args.find().unwrap_or_else(|| { @@ -20,6 +32,24 @@ impl LinkNode { Template::Text(if shorter { text.into() } else { url.clone() }) }); - Ok(body.styled(TextNode::LINK, Some(url))) + Ok(Template::show(Self { url, body })) + } +} + +impl Show for LinkNode { + fn show(&self, styles: StyleChain) -> Template { + let mut map = StyleMap::new(); + map.set(TextNode::LINK, Some(self.url.clone())); + + if let Smart::Custom(fill) = styles.get(Self::FILL) { + map.set(TextNode::FILL, fill); + } + + let mut body = self.body.clone(); + if styles.get(Self::UNDERLINE) { + body = body.underlined(); + } + + body.styled_with_map(map) } } diff --git a/src/library/list.rs b/src/library/list.rs index 639b5715..0c0e61ad 100644 --- a/src/library/list.rs +++ b/src/library/list.rs @@ -5,15 +5,15 @@ use super::{GridNode, TextNode, TrackSizing}; /// An unordered or ordered list. #[derive(Debug, Hash)] -pub struct ListNode<L: ListKind> { - /// The list labelling style -- unordered or ordered. - pub kind: L, +pub struct ListNode<L: ListLabel> { + /// The list label -- unordered or ordered with index. + pub label: L, /// The node that produces the item's body. - pub child: PackedNode, + pub child: LayoutNode, } #[class] -impl<L: ListKind> ListNode<L> { +impl<L: ListLabel> ListNode<L> { /// The indentation of each item's label. pub const LABEL_INDENT: Linear = Relative::new(0.0).into(); /// The space between the label and the body of each item. @@ -22,29 +22,19 @@ impl<L: ListKind> ListNode<L> { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { Ok(args .all() - .map(|child: PackedNode| Template::block(Self { kind: L::default(), child })) + .enumerate() + .map(|(i, child)| Template::show(Self { label: L::new(1 + i), child })) .sum()) } - - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - styles.set_opt(Self::LABEL_INDENT, args.named("label-indent")?); - styles.set_opt(Self::BODY_INDENT, args.named("body-indent")?); - Ok(()) - } } -impl<L: ListKind> Layout for ListNode<L> { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - styles: StyleChain, - ) -> Vec<Constrained<Arc<Frame>>> { +impl<L: ListLabel> Show for ListNode<L> { + fn show(&self, styles: StyleChain) -> Template { let em = styles.get(TextNode::SIZE).abs; let label_indent = styles.get(Self::LABEL_INDENT).resolve(em); let body_indent = styles.get(Self::BODY_INDENT).resolve(em); - let grid = GridNode { + Template::block(GridNode { tracks: Spec::with_x(vec![ TrackSizing::Linear(label_indent.into()), TrackSizing::Auto, @@ -53,19 +43,20 @@ impl<L: ListKind> Layout for ListNode<L> { ]), gutter: Spec::default(), children: vec![ - PackedNode::default(), - Template::Text(self.kind.label()).pack(), - PackedNode::default(), + LayoutNode::default(), + Template::Text(self.label.label()).pack(), + LayoutNode::default(), self.child.clone(), ], - }; - - grid.layout(ctx, regions, styles) + }) } } /// How to label a list. -pub trait ListKind: Debug + Default + Hash + Sync + Send + 'static { +pub trait ListLabel: Debug + Default + Hash + Sync + Send + 'static { + /// Create a new list label. + fn new(number: usize) -> Self; + /// Return the item's label. fn label(&self) -> EcoString; } @@ -74,7 +65,11 @@ pub trait ListKind: Debug + Default + Hash + Sync + Send + 'static { #[derive(Debug, Default, Hash)] pub struct Unordered; -impl ListKind for Unordered { +impl ListLabel for Unordered { + fn new(_: usize) -> Self { + Self + } + fn label(&self) -> EcoString { '•'.into() } @@ -84,7 +79,11 @@ impl ListKind for Unordered { #[derive(Debug, Default, Hash)] pub struct Ordered(pub Option<usize>); -impl ListKind for Ordered { +impl ListLabel for Ordered { + fn new(number: usize) -> Self { + Self(Some(number)) + } + fn label(&self) -> EcoString { format_eco!("{}.", self.0.unwrap_or(1)) } diff --git a/src/library/math.rs b/src/library/math.rs new file mode 100644 index 00000000..c75cdea8 --- /dev/null +++ b/src/library/math.rs @@ -0,0 +1,32 @@ +//! Mathematical formulas. + +use super::prelude::*; + +/// A mathematical formula. +#[derive(Debug, Hash)] +pub struct MathNode { + /// The formula. + pub formula: EcoString, + /// Whether the formula is display-level. + pub display: bool, +} + +#[class] +impl MathNode { + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { + Ok(Template::show(Self { + formula: args.expect("formula")?, + display: args.named("display")?.unwrap_or(false), + })) + } +} + +impl Show for MathNode { + fn show(&self, _: StyleChain) -> Template { + let mut template = Template::Text(self.formula.trim().into()); + if self.display { + template = Template::Block(template.pack()); + } + template.monospaced() + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index ef0fa016..b2cb8698 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -14,18 +14,20 @@ pub mod hide; pub mod image; pub mod link; pub mod list; +pub mod math; pub mod pad; pub mod page; pub mod par; pub mod place; +pub mod raw; pub mod shape; pub mod spacing; pub mod stack; pub mod table; pub mod text; pub mod transform; -pub mod utility; +pub mod utility; pub use self::image::*; pub use align::*; pub use columns::*; @@ -37,10 +39,12 @@ pub use heading::*; pub use hide::*; pub use link::*; pub use list::*; +pub use math::*; pub use pad::*; pub use page::*; pub use par::*; pub use place::*; +pub use raw::*; pub use shape::*; pub use spacing::*; pub use stack::*; @@ -68,13 +72,13 @@ prelude! { pub use crate::diag::{At, TypResult}; pub use crate::eval::{ - Args, Construct, EvalContext, Merge, Property, Scope, Set, Smart, StyleChain, - StyleMap, StyleVec, Template, Value, + Args, Construct, EvalContext, Merge, Property, Scope, Set, Show, ShowNode, Smart, + StyleChain, StyleMap, StyleVec, Template, Value, }; pub use crate::frame::*; pub use crate::geom::*; pub use crate::layout::{ - Constrain, Constrained, Constraints, Layout, LayoutContext, PackedNode, Regions, + Constrain, Constrained, Constraints, Layout, LayoutContext, LayoutNode, Regions, }; pub use crate::syntax::{Span, Spanned}; pub use crate::util::{EcoString, OptionExt}; @@ -95,6 +99,8 @@ pub fn new() -> Scope { std.def_class::<TextNode>("text"); std.def_class::<StrongNode>("strong"); std.def_class::<EmphNode>("emph"); + std.def_class::<RawNode>("raw"); + std.def_class::<MathNode>("math"); std.def_class::<DecoNode<Underline>>("underline"); std.def_class::<DecoNode<Strikethrough>>("strike"); std.def_class::<DecoNode<Overline>>("overline"); @@ -207,9 +213,9 @@ castable! { castable! { NonZeroUsize, Expected: "positive integer", - Value::Int(int) => int + Value::Int(int) => Value::Int(int) + .cast::<usize>()? .try_into() - .and_then(usize::try_into) .map_err(|_| "must be positive")?, } @@ -226,7 +232,7 @@ castable! { } castable! { - PackedNode, + LayoutNode, Expected: "template", Value::Template(template) => template.pack(), } diff --git a/src/library/pad.rs b/src/library/pad.rs index af570659..b65057dc 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -8,7 +8,7 @@ pub struct PadNode { /// The amount of padding. pub padding: Sides<Linear>, /// The child node whose sides to pad. - pub child: PackedNode, + pub child: LayoutNode, } #[class] @@ -19,7 +19,7 @@ impl PadNode { let top = args.named("top")?; let right = args.named("right")?; let bottom = args.named("bottom")?; - let body: PackedNode = args.expect("body")?; + let body: LayoutNode = args.expect("body")?; let padding = Sides::new( left.or(all).unwrap_or_default(), top.or(all).unwrap_or_default(), diff --git a/src/library/page.rs b/src/library/page.rs index cdb78cc1..d54fefb0 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -8,16 +8,14 @@ use super::ColumnsNode; /// Layouts its child onto one or multiple pages. #[derive(Clone, PartialEq, Hash)] -pub struct PageNode(pub PackedNode); +pub struct PageNode(pub LayoutNode); #[class] impl PageNode { /// The unflipped width of the page. - pub const WIDTH: Smart<Length> = Smart::Custom(Paper::default().width()); + pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width()); /// The unflipped height of the page. - pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::default().height()); - /// The class of paper. Defines the default margins. - pub const CLASS: PaperClass = Paper::default().class(); + pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height()); /// Whether the page is flipped into landscape orientation. pub const FLIPPED: bool = false; /// The left margin. @@ -39,20 +37,12 @@ impl PageNode { fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) { - styles.set(Self::CLASS, paper.class()); styles.set(Self::WIDTH, Smart::Custom(paper.width())); styles.set(Self::HEIGHT, Smart::Custom(paper.height())); } - if let Some(width) = args.named("width")? { - styles.set(Self::CLASS, PaperClass::Custom); - styles.set(Self::WIDTH, width); - } - - if let Some(height) = args.named("height")? { - styles.set(Self::CLASS, PaperClass::Custom); - styles.set(Self::HEIGHT, height); - } + styles.set_opt(Self::WIDTH, args.named("width")?); + styles.set_opt(Self::HEIGHT, args.named("height")?); let margins = args.named("margins")?; styles.set_opt(Self::LEFT, args.named("left")?.or(margins)); @@ -80,14 +70,18 @@ impl PageNode { std::mem::swap(&mut size.x, &mut size.y); } + let mut min = width.min(height); + if min.is_infinite() { + min = Paper::A4.width(); + } + // Determine the margins. - let class = styles.get(Self::CLASS); - let default = class.default_margins(); + let default = Linear::from(0.1190 * min); let padding = Sides { - left: styles.get(Self::LEFT).unwrap_or(default.left), - right: styles.get(Self::RIGHT).unwrap_or(default.right), - top: styles.get(Self::TOP).unwrap_or(default.top), - bottom: styles.get(Self::BOTTOM).unwrap_or(default.bottom), + left: styles.get(Self::LEFT).unwrap_or(default), + right: styles.get(Self::RIGHT).unwrap_or(default), + top: styles.get(Self::TOP).unwrap_or(default), + bottom: styles.get(Self::BOTTOM).unwrap_or(default), }; let mut child = self.0.clone(); @@ -138,8 +132,6 @@ impl PagebreakNode { /// Specification of a paper. #[derive(Debug, Copy, Clone)] pub struct Paper { - /// The broad class this paper belongs to. - class: PaperClass, /// The width of the paper in millimeters. width: f64, /// The height of the paper in millimeters. @@ -147,11 +139,6 @@ pub struct Paper { } impl Paper { - /// The class of the paper. - pub fn class(self) -> PaperClass { - self.class - } - /// The width of the paper. pub fn width(self) -> Length { Length::mm(self.width) @@ -163,24 +150,14 @@ impl Paper { } } -impl Default for Paper { - fn default() -> Self { - Paper::A4 - } -} - /// Defines paper constants and a paper parsing implementation. macro_rules! papers { - ($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => { + ($(($var:ident: $width:expr, $height: expr, $($pats:tt)*))*) => { /// Predefined papers. /// /// Each paper is parsable from its name in kebab-case. impl Paper { - $(pub const $var: Self = Self { - class: PaperClass::$class, - width: $width, - height: $height, - };)* + $(pub const $var: Self = Self { width: $width, height: $height };)* } impl FromStr for Paper { @@ -206,149 +183,149 @@ macro_rules! papers { papers! { // ---------------------------------------------------------------------- // // ISO 216 A Series - (A0: Base, 841.0, 1189.0, "a0") - (A1: Base, 594.0, 841.0, "a1") - (A2: Base, 420.0, 594.0, "a2") - (A3: Base, 297.0, 420.0, "a3") - (A4: Base, 210.0, 297.0, "a4") - (A5: Base, 148.0, 210.0, "a5") - (A6: Book, 105.0, 148.0, "a6") - (A7: Base, 74.0, 105.0, "a7") - (A8: Base, 52.0, 74.0, "a8") - (A9: Base, 37.0, 52.0, "a9") - (A10: Base, 26.0, 37.0, "a10") - (A11: Base, 18.0, 26.0, "a11") + (A0: 841.0, 1189.0, "a0") + (A1: 594.0, 841.0, "a1") + (A2: 420.0, 594.0, "a2") + (A3: 297.0, 420.0, "a3") + (A4: 210.0, 297.0, "a4") + (A5: 148.0, 210.0, "a5") + (A6: 105.0, 148.0, "a6") + (A7: 74.0, 105.0, "a7") + (A8: 52.0, 74.0, "a8") + (A9: 37.0, 52.0, "a9") + (A10: 26.0, 37.0, "a10") + (A11: 18.0, 26.0, "a11") // ISO 216 B Series - (ISO_B1: Base, 707.0, 1000.0, "iso-b1") - (ISO_B2: Base, 500.0, 707.0, "iso-b2") - (ISO_B3: Base, 353.0, 500.0, "iso-b3") - (ISO_B4: Base, 250.0, 353.0, "iso-b4") - (ISO_B5: Book, 176.0, 250.0, "iso-b5") - (ISO_B6: Book, 125.0, 176.0, "iso-b6") - (ISO_B7: Base, 88.0, 125.0, "iso-b7") - (ISO_B8: Base, 62.0, 88.0, "iso-b8") + (ISO_B1: 707.0, 1000.0, "iso-b1") + (ISO_B2: 500.0, 707.0, "iso-b2") + (ISO_B3: 353.0, 500.0, "iso-b3") + (ISO_B4: 250.0, 353.0, "iso-b4") + (ISO_B5: 176.0, 250.0, "iso-b5") + (ISO_B6: 125.0, 176.0, "iso-b6") + (ISO_B7: 88.0, 125.0, "iso-b7") + (ISO_B8: 62.0, 88.0, "iso-b8") // ISO 216 C Series - (ISO_C3: Base, 324.0, 458.0, "iso-c3") - (ISO_C4: Base, 229.0, 324.0, "iso-c4") - (ISO_C5: Base, 162.0, 229.0, "iso-c5") - (ISO_C6: Base, 114.0, 162.0, "iso-c6") - (ISO_C7: Base, 81.0, 114.0, "iso-c7") - (ISO_C8: Base, 57.0, 81.0, "iso-c8") + (ISO_C3: 324.0, 458.0, "iso-c3") + (ISO_C4: 229.0, 324.0, "iso-c4") + (ISO_C5: 162.0, 229.0, "iso-c5") + (ISO_C6: 114.0, 162.0, "iso-c6") + (ISO_C7: 81.0, 114.0, "iso-c7") + (ISO_C8: 57.0, 81.0, "iso-c8") // DIN D Series (extension to ISO) - (DIN_D3: Base, 272.0, 385.0, "din-d3") - (DIN_D4: Base, 192.0, 272.0, "din-d4") - (DIN_D5: Base, 136.0, 192.0, "din-d5") - (DIN_D6: Base, 96.0, 136.0, "din-d6") - (DIN_D7: Base, 68.0, 96.0, "din-d7") - (DIN_D8: Base, 48.0, 68.0, "din-d8") + (DIN_D3: 272.0, 385.0, "din-d3") + (DIN_D4: 192.0, 272.0, "din-d4") + (DIN_D5: 136.0, 192.0, "din-d5") + (DIN_D6: 96.0, 136.0, "din-d6") + (DIN_D7: 68.0, 96.0, "din-d7") + (DIN_D8: 48.0, 68.0, "din-d8") // SIS (used in academia) - (SIS_G5: Base, 169.0, 239.0, "sis-g5") - (SIS_E5: Base, 115.0, 220.0, "sis-e5") + (SIS_G5: 169.0, 239.0, "sis-g5") + (SIS_E5: 115.0, 220.0, "sis-e5") // ANSI Extensions - (ANSI_A: Base, 216.0, 279.0, "ansi-a") - (ANSI_B: Base, 279.0, 432.0, "ansi-b") - (ANSI_C: Base, 432.0, 559.0, "ansi-c") - (ANSI_D: Base, 559.0, 864.0, "ansi-d") - (ANSI_E: Base, 864.0, 1118.0, "ansi-e") + (ANSI_A: 216.0, 279.0, "ansi-a") + (ANSI_B: 279.0, 432.0, "ansi-b") + (ANSI_C: 432.0, 559.0, "ansi-c") + (ANSI_D: 559.0, 864.0, "ansi-d") + (ANSI_E: 864.0, 1118.0, "ansi-e") // ANSI Architectural Paper - (ARCH_A: Base, 229.0, 305.0, "arch-a") - (ARCH_B: Base, 305.0, 457.0, "arch-b") - (ARCH_C: Base, 457.0, 610.0, "arch-c") - (ARCH_D: Base, 610.0, 914.0, "arch-d") - (ARCH_E1: Base, 762.0, 1067.0, "arch-e1") - (ARCH_E: Base, 914.0, 1219.0, "arch-e") + (ARCH_A: 229.0, 305.0, "arch-a") + (ARCH_B: 305.0, 457.0, "arch-b") + (ARCH_C: 457.0, 610.0, "arch-c") + (ARCH_D: 610.0, 914.0, "arch-d") + (ARCH_E1: 762.0, 1067.0, "arch-e1") + (ARCH_E: 914.0, 1219.0, "arch-e") // JIS B Series - (JIS_B0: Base, 1030.0, 1456.0, "jis-b0") - (JIS_B1: Base, 728.0, 1030.0, "jis-b1") - (JIS_B2: Base, 515.0, 728.0, "jis-b2") - (JIS_B3: Base, 364.0, 515.0, "jis-b3") - (JIS_B4: Base, 257.0, 364.0, "jis-b4") - (JIS_B5: Base, 182.0, 257.0, "jis-b5") - (JIS_B6: Base, 128.0, 182.0, "jis-b6") - (JIS_B7: Base, 91.0, 128.0, "jis-b7") - (JIS_B8: Base, 64.0, 91.0, "jis-b8") - (JIS_B9: Base, 45.0, 64.0, "jis-b9") - (JIS_B10: Base, 32.0, 45.0, "jis-b10") - (JIS_B11: Base, 22.0, 32.0, "jis-b11") + (JIS_B0: 1030.0, 1456.0, "jis-b0") + (JIS_B1: 728.0, 1030.0, "jis-b1") + (JIS_B2: 515.0, 728.0, "jis-b2") + (JIS_B3: 364.0, 515.0, "jis-b3") + (JIS_B4: 257.0, 364.0, "jis-b4") + (JIS_B5: 182.0, 257.0, "jis-b5") + (JIS_B6: 128.0, 182.0, "jis-b6") + (JIS_B7: 91.0, 128.0, "jis-b7") + (JIS_B8: 64.0, 91.0, "jis-b8") + (JIS_B9: 45.0, 64.0, "jis-b9") + (JIS_B10: 32.0, 45.0, "jis-b10") + (JIS_B11: 22.0, 32.0, "jis-b11") // SAC D Series - (SAC_D0: Base, 764.0, 1064.0, "sac-d0") - (SAC_D1: Base, 532.0, 760.0, "sac-d1") - (SAC_D2: Base, 380.0, 528.0, "sac-d2") - (SAC_D3: Base, 264.0, 376.0, "sac-d3") - (SAC_D4: Base, 188.0, 260.0, "sac-d4") - (SAC_D5: Base, 130.0, 184.0, "sac-d5") - (SAC_D6: Base, 92.0, 126.0, "sac-d6") + (SAC_D0: 764.0, 1064.0, "sac-d0") + (SAC_D1: 532.0, 760.0, "sac-d1") + (SAC_D2: 380.0, 528.0, "sac-d2") + (SAC_D3: 264.0, 376.0, "sac-d3") + (SAC_D4: 188.0, 260.0, "sac-d4") + (SAC_D5: 130.0, 184.0, "sac-d5") + (SAC_D6: 92.0, 126.0, "sac-d6") // ISO 7810 ID - (ISO_ID_1: Base, 85.6, 53.98, "iso-id-1") - (ISO_ID_2: Base, 74.0, 105.0, "iso-id-2") - (ISO_ID_3: Base, 88.0, 125.0, "iso-id-3") + (ISO_ID_1: 85.6, 53.98, "iso-id-1") + (ISO_ID_2: 74.0, 105.0, "iso-id-2") + (ISO_ID_3: 88.0, 125.0, "iso-id-3") // ---------------------------------------------------------------------- // // Asia - (ASIA_F4: Base, 210.0, 330.0, "asia-f4") + (ASIA_F4: 210.0, 330.0, "asia-f4") // Japan - (JP_SHIROKU_BAN_4: Base, 264.0, 379.0, "jp-shiroku-ban-4") - (JP_SHIROKU_BAN_5: Base, 189.0, 262.0, "jp-shiroku-ban-5") - (JP_SHIROKU_BAN_6: Base, 127.0, 188.0, "jp-shiroku-ban-6") - (JP_KIKU_4: Base, 227.0, 306.0, "jp-kiku-4") - (JP_KIKU_5: Base, 151.0, 227.0, "jp-kiku-5") - (JP_BUSINESS_CARD: Base, 91.0, 55.0, "jp-business-card") + (JP_SHIROKU_BAN_4: 264.0, 379.0, "jp-shiroku-ban-4") + (JP_SHIROKU_BAN_5: 189.0, 262.0, "jp-shiroku-ban-5") + (JP_SHIROKU_BAN_6: 127.0, 188.0, "jp-shiroku-ban-6") + (JP_KIKU_4: 227.0, 306.0, "jp-kiku-4") + (JP_KIKU_5: 151.0, 227.0, "jp-kiku-5") + (JP_BUSINESS_CARD: 91.0, 55.0, "jp-business-card") // China - (CN_BUSINESS_CARD: Base, 90.0, 54.0, "cn-business-card") + (CN_BUSINESS_CARD: 90.0, 54.0, "cn-business-card") // Europe - (EU_BUSINESS_CARD: Base, 85.0, 55.0, "eu-business-card") + (EU_BUSINESS_CARD: 85.0, 55.0, "eu-business-card") // French Traditional (AFNOR) - (FR_TELLIERE: Base, 340.0, 440.0, "fr-tellière") - (FR_COURONNE_ECRITURE: Base, 360.0, 460.0, "fr-couronne-écriture") - (FR_COURONNE_EDITION: Base, 370.0, 470.0, "fr-couronne-édition") - (FR_RAISIN: Base, 500.0, 650.0, "fr-raisin") - (FR_CARRE: Base, 450.0, 560.0, "fr-carré") - (FR_JESUS: Base, 560.0, 760.0, "fr-jésus") + (FR_TELLIERE: 340.0, 440.0, "fr-tellière") + (FR_COURONNE_ECRITURE: 360.0, 460.0, "fr-couronne-écriture") + (FR_COURONNE_EDITION: 370.0, 470.0, "fr-couronne-édition") + (FR_RAISIN: 500.0, 650.0, "fr-raisin") + (FR_CARRE: 450.0, 560.0, "fr-carré") + (FR_JESUS: 560.0, 760.0, "fr-jésus") // United Kingdom Imperial - (UK_BRIEF: Base, 406.4, 342.9, "uk-brief") - (UK_DRAFT: Base, 254.0, 406.4, "uk-draft") - (UK_FOOLSCAP: Base, 203.2, 330.2, "uk-foolscap") - (UK_QUARTO: Base, 203.2, 254.0, "uk-quarto") - (UK_CROWN: Base, 508.0, 381.0, "uk-crown") - (UK_BOOK_A: Book, 111.0, 178.0, "uk-book-a") - (UK_BOOK_B: Book, 129.0, 198.0, "uk-book-b") + (UK_BRIEF: 406.4, 342.9, "uk-brief") + (UK_DRAFT: 254.0, 406.4, "uk-draft") + (UK_FOOLSCAP: 203.2, 330.2, "uk-foolscap") + (UK_QUARTO: 203.2, 254.0, "uk-quarto") + (UK_CROWN: 508.0, 381.0, "uk-crown") + (UK_BOOK_A: 111.0, 178.0, "uk-book-a") + (UK_BOOK_B: 129.0, 198.0, "uk-book-b") // Unites States - (US_LETTER: US, 215.9, 279.4, "us-letter") - (US_LEGAL: US, 215.9, 355.6, "us-legal") - (US_TABLOID: US, 279.4, 431.8, "us-tabloid") - (US_EXECUTIVE: US, 184.15, 266.7, "us-executive") - (US_FOOLSCAP_FOLIO: US, 215.9, 342.9, "us-foolscap-folio") - (US_STATEMENT: US, 139.7, 215.9, "us-statement") - (US_LEDGER: US, 431.8, 279.4, "us-ledger") - (US_OFICIO: US, 215.9, 340.36, "us-oficio") - (US_GOV_LETTER: US, 203.2, 266.7, "us-gov-letter") - (US_GOV_LEGAL: US, 215.9, 330.2, "us-gov-legal") - (US_BUSINESS_CARD: Base, 88.9, 50.8, "us-business-card") - (US_DIGEST: Book, 139.7, 215.9, "us-digest") - (US_TRADE: Book, 152.4, 228.6, "us-trade") + (US_LETTER: 215.9, 279.4, "us-letter") + (US_LEGAL: 215.9, 355.6, "us-legal") + (US_TABLOID: 279.4, 431.8, "us-tabloid") + (US_EXECUTIVE: 84.15, 266.7, "us-executive") + (US_FOOLSCAP_FOLIO: 215.9, 342.9, "us-foolscap-folio") + (US_STATEMENT: 139.7, 215.9, "us-statement") + (US_LEDGER: 431.8, 279.4, "us-ledger") + (US_OFICIO: 215.9, 340.36, "us-oficio") + (US_GOV_LETTER: 203.2, 266.7, "us-gov-letter") + (US_GOV_LEGAL: 215.9, 330.2, "us-gov-legal") + (US_BUSINESS_CARD: 88.9, 50.8, "us-business-card") + (US_DIGEST: 139.7, 215.9, "us-digest") + (US_TRADE: 152.4, 228.6, "us-trade") // ---------------------------------------------------------------------- // // Other - (NEWSPAPER_COMPACT: Newspaper, 280.0, 430.0, "newspaper-compact") - (NEWSPAPER_BERLINER: Newspaper, 315.0, 470.0, "newspaper-berliner") - (NEWSPAPER_BROADSHEET: Newspaper, 381.0, 578.0, "newspaper-broadsheet") - (PRESENTATION_16_9: Base, 297.0, 167.0625, "presentation-16-9") - (PRESENTATION_4_3: Base, 280.0, 210.0, "presentation-4-3") + (NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact") + (NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner") + (NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet") + (PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9") + (PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3") } castable! { @@ -357,31 +334,6 @@ castable! { Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?, } -/// Defines default margins for a class of related papers. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum PaperClass { - Custom, - Base, - US, - Newspaper, - Book, -} - -impl PaperClass { - /// The default margins for this page class. - fn default_margins(self) -> Sides<Linear> { - let f = |r| Relative::new(r).into(); - let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b)); - match self { - Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842), - Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842), - Self::US => s(0.1760, 0.1092, 0.1760, 0.0910), - Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294), - Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965), - } - } -} - /// The error when parsing a [`Paper`] from a string fails. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct PaperError; diff --git a/src/library/par.rs b/src/library/par.rs index 962834f2..2a4b4d08 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -22,7 +22,7 @@ pub enum ParChild { /// Horizontal spacing between other children. Spacing(SpacingKind), /// An arbitrary inline-level node. - Node(PackedNode), + Node(LayoutNode), } #[class] diff --git a/src/library/place.rs b/src/library/place.rs index d7b98c16..ee5ee2c9 100644 --- a/src/library/place.rs +++ b/src/library/place.rs @@ -5,7 +5,7 @@ use super::AlignNode; /// Place a node at an absolute position. #[derive(Debug, Hash)] -pub struct PlaceNode(pub PackedNode); +pub struct PlaceNode(pub LayoutNode); #[class] impl PlaceNode { @@ -13,7 +13,7 @@ impl PlaceNode { let aligns = args.find().unwrap_or(Spec::with_x(Some(Align::Left))); let tx = args.named("dx")?.unwrap_or_default(); let ty = args.named("dy")?.unwrap_or_default(); - let body: PackedNode = args.expect("body")?; + let body: LayoutNode = args.expect("body")?; Ok(Template::block(Self( body.moved(Point::new(tx, ty)).aligned(aligns), ))) diff --git a/src/library/raw.rs b/src/library/raw.rs new file mode 100644 index 00000000..e2ae259b --- /dev/null +++ b/src/library/raw.rs @@ -0,0 +1,117 @@ +//! Monospaced text and code. + +use once_cell::sync::Lazy; +use syntect::easy::HighlightLines; +use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet}; +use syntect::parsing::SyntaxSet; + +use super::prelude::*; +use crate::library::TextNode; +use crate::source::SourceId; +use crate::syntax::{self, RedNode}; + +/// The lazily-loaded theme used for syntax highlighting. +static THEME: Lazy<Theme> = + Lazy::new(|| ThemeSet::load_defaults().themes.remove("InspiredGitHub").unwrap()); + +/// The lazily-loaded syntect syntax definitions. +static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| SyntaxSet::load_defaults_newlines()); + +/// Monospaced text with optional syntax highlighting. +#[derive(Debug, Hash)] +pub struct RawNode { + /// The raw text. + pub text: EcoString, + /// Whether the node is block-level. + pub block: bool, +} + +#[class] +impl RawNode { + /// The language to syntax-highlight in. + pub const LANG: Option<EcoString> = None; + + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { + Ok(Template::show(Self { + text: args.expect("text")?, + block: args.named("block")?.unwrap_or(false), + })) + } +} + +impl Show for RawNode { + fn show(&self, styles: StyleChain) -> Template { + let lang = styles.get_ref(Self::LANG).as_ref(); + let foreground = THEME + .settings + .foreground + .map(Color::from) + .unwrap_or(Color::BLACK) + .into(); + + let mut template = if matches!( + lang.map(|s| s.to_lowercase()).as_deref(), + Some("typ" | "typst") + ) { + let mut seq = vec![]; + let green = crate::parse::parse(&self.text); + let red = RedNode::from_root(green, SourceId::from_raw(0)); + let highlighter = Highlighter::new(&THEME); + + syntax::highlight_syntect(red.as_ref(), &highlighter, &mut |range, style| { + seq.push(styled(&self.text[range], foreground, style)); + }); + + Template::sequence(seq) + } else if let Some(syntax) = + lang.and_then(|token| SYNTAXES.find_syntax_by_token(&token)) + { + let mut seq = vec![]; + let mut highlighter = HighlightLines::new(syntax, &THEME); + for (i, line) in self.text.lines().enumerate() { + if i != 0 { + seq.push(Template::Linebreak); + } + + for (style, piece) in highlighter.highlight(line, &SYNTAXES) { + seq.push(styled(piece, foreground, style)); + } + } + + Template::sequence(seq) + } else { + Template::Text(self.text.clone()) + }; + + if self.block { + template = Template::Block(template.pack()); + } + + template.monospaced() + } +} + +/// Style a piece of text with a syntect style. +fn styled(piece: &str, foreground: Paint, style: Style) -> Template { + let mut styles = StyleMap::new(); + let mut body = Template::Text(piece.into()); + + let paint = style.foreground.into(); + if paint != foreground { + styles.set(TextNode::FILL, paint); + } + + if style.font_style.contains(FontStyle::BOLD) { + styles.set(TextNode::STRONG, true); + } + + if style.font_style.contains(FontStyle::ITALIC) { + styles.set(TextNode::EMPH, true); + } + + if style.font_style.contains(FontStyle::UNDERLINE) { + body = body.underlined(); + } + + body.styled_with_map(styles) +} diff --git a/src/library/shape.rs b/src/library/shape.rs index 8ac9956f..95e3e288 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -11,7 +11,7 @@ pub struct ShapeNode<S: ShapeKind> { /// Which shape to place the child into. pub kind: S, /// The child node to place into the shape, if any. - pub child: Option<PackedNode>, + pub child: Option<LayoutNode>, } #[class] @@ -50,14 +50,6 @@ impl<S: ShapeKind> ShapeNode<S> { .sized(Spec::new(width, height)), )) } - - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - styles.set_opt(Self::FILL, args.named("fill")?); - styles.set_opt(Self::STROKE, args.named("stroke")?); - styles.set_opt(Self::THICKNESS, args.named("thickness")?); - styles.set_opt(Self::PADDING, args.named("padding")?); - Ok(()) - } } impl<S: ShapeKind> Layout for ShapeNode<S> { diff --git a/src/library/spacing.rs b/src/library/spacing.rs index feadf7f3..3e26cb3c 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -38,6 +38,12 @@ impl SpacingKind { } } +impl From<Length> for SpacingKind { + fn from(length: Length) -> Self { + Self::Linear(length.into()) + } +} + castable! { SpacingKind, Expected: "linear or fractional", diff --git a/src/library/stack.rs b/src/library/stack.rs index 0ff8e973..bccd552f 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -64,7 +64,7 @@ pub enum StackChild { /// Spacing between other nodes. Spacing(SpacingKind), /// An arbitrary node. - Node(PackedNode), + Node(LayoutNode), } impl Debug for StackChild { @@ -166,7 +166,7 @@ impl StackLayouter { pub fn layout_node( &mut self, ctx: &mut LayoutContext, - node: &PackedNode, + node: &LayoutNode, styles: StyleChain, ) { if self.regions.is_full() { diff --git a/src/library/table.rs b/src/library/table.rs index 33fa68cd..c40a117b 100644 --- a/src/library/table.rs +++ b/src/library/table.rs @@ -11,7 +11,7 @@ pub struct TableNode { /// Defines sizing of gutter rows and columns between content. pub gutter: Spec<Vec<TrackSizing>>, /// The nodes to be arranged in the table. - pub children: Vec<PackedNode>, + pub children: Vec<LayoutNode>, } #[class] @@ -33,7 +33,7 @@ impl TableNode { let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default(); let column_gutter = args.named("column-gutter")?; let row_gutter = args.named("row-gutter")?; - Ok(Template::block(Self { + Ok(Template::show(Self { tracks: Spec::new(columns, rows), gutter: Spec::new( column_gutter.unwrap_or_else(|| base_gutter.clone()), @@ -54,13 +54,8 @@ impl TableNode { } } -impl Layout for TableNode { - fn layout( - &self, - ctx: &mut LayoutContext, - regions: &Regions, - styles: StyleChain, - ) -> Vec<Constrained<Arc<Frame>>> { +impl Show for TableNode { + fn show(&self, styles: StyleChain) -> Template { let primary = styles.get(Self::PRIMARY); let secondary = styles.get(Self::SECONDARY); let thickness = styles.get(Self::THICKNESS); @@ -90,12 +85,10 @@ impl Layout for TableNode { }) .collect(); - let grid = GridNode { + Template::block(GridNode { tracks: self.tracks.clone(), gutter: self.gutter.clone(), children, - }; - - grid.layout(ctx, regions, styles) + }) } } diff --git a/src/library/text.rs b/src/library/text.rs index fb0f7e08..1ccbb1c6 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -24,13 +24,14 @@ pub struct TextNode; #[class] impl TextNode { /// A prioritized sequence of font families. - pub const FAMILY_LIST: Vec<FontFamily> = vec![FontFamily::SansSerif]; + #[variadic] + pub const FAMILY: Vec<FontFamily> = vec![FontFamily::SansSerif]; /// The serif font family/families. - pub const SERIF_LIST: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Serif")]; + pub const SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Serif")]; /// The sans-serif font family/families. - pub const SANS_SERIF_LIST: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Sans")]; + pub const SANS_SERIF: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Sans")]; /// The monospace font family/families. - pub const MONOSPACE_LIST: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Mono")]; + pub const MONOSPACE: Vec<NamedFamily> = vec![NamedFamily::new("IBM Plex Mono")]; /// Whether to allow font fallback when the primary font list contains no /// match. pub const FALLBACK: bool = true; @@ -41,23 +42,12 @@ impl TextNode { pub const WEIGHT: FontWeight = FontWeight::REGULAR; /// The width of the glyphs. pub const STRETCH: FontStretch = FontStretch::NORMAL; - /// Whether the font weight should be increased by 300. - #[fold(bool::bitxor)] - pub const STRONG: bool = false; - /// Whether the the font style should be inverted. - #[fold(bool::bitxor)] - pub const EMPH: bool = false; - /// Whether a monospace font should be preferred. - pub const MONOSPACE: bool = false; /// The glyph fill color. + #[shorthand] pub const FILL: Paint = Color::BLACK.into(); - /// Decorative lines. - #[fold(|a, b| a.into_iter().chain(b).collect())] - pub const LINES: Vec<Decoration> = vec![]; - /// An URL the text should link to. - pub const LINK: Option<EcoString> = None; /// The size of the glyphs. + #[shorthand] #[fold(Linear::compose)] pub const SIZE: Linear = Length::pt(11.0).into(); /// The amount of space that should be added between characters. @@ -94,73 +84,64 @@ impl TextNode { /// Raw OpenType features to apply. pub const FEATURES: Vec<(Tag, u32)> = vec![]; + /// Whether the font weight should be increased by 300. + #[skip] + #[fold(bool::bitxor)] + pub const STRONG: bool = false; + /// Whether the the font style should be inverted. + #[skip] + #[fold(bool::bitxor)] + pub const EMPH: bool = false; + /// Whether a monospace font should be preferred. + #[skip] + pub const MONOSPACED: bool = false; + /// Decorative lines. + #[skip] + #[fold(|a, b| a.into_iter().chain(b).collect())] + pub const LINES: Vec<Decoration> = vec![]; + /// An URL the text should link to. + #[skip] + pub const LINK: Option<EcoString> = None; + fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { // The text constructor is special: It doesn't create a text node. // Instead, it leaves the passed argument structurally unchanged, but // styles all text in it. args.expect("body") } - - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - let list = args.named("family")?.or_else(|| { - let families: Vec<_> = args.all().collect(); - (!families.is_empty()).then(|| families) - }); - - styles.set_opt(Self::FAMILY_LIST, list); - styles.set_opt(Self::SERIF_LIST, args.named("serif")?); - styles.set_opt(Self::SANS_SERIF_LIST, args.named("sans-serif")?); - styles.set_opt(Self::MONOSPACE_LIST, args.named("monospace")?); - styles.set_opt(Self::FALLBACK, args.named("fallback")?); - styles.set_opt(Self::STYLE, args.named("style")?); - styles.set_opt(Self::WEIGHT, args.named("weight")?); - styles.set_opt(Self::STRETCH, args.named("stretch")?); - styles.set_opt(Self::FILL, args.named("fill")?.or_else(|| args.find())); - styles.set_opt(Self::SIZE, args.named("size")?.or_else(|| args.find())); - styles.set_opt(Self::TRACKING, args.named("tracking")?.map(Em::new)); - styles.set_opt(Self::TOP_EDGE, args.named("top-edge")?); - styles.set_opt(Self::BOTTOM_EDGE, args.named("bottom-edge")?); - styles.set_opt(Self::KERNING, args.named("kerning")?); - styles.set_opt(Self::SMALLCAPS, args.named("smallcaps")?); - styles.set_opt(Self::ALTERNATES, args.named("alternates")?); - styles.set_opt(Self::STYLISTIC_SET, args.named("stylistic-set")?); - styles.set_opt(Self::LIGATURES, args.named("ligatures")?); - styles.set_opt( - Self::DISCRETIONARY_LIGATURES, - args.named("discretionary-ligatures")?, - ); - styles.set_opt( - Self::HISTORICAL_LIGATURES, - args.named("historical-ligatures")?, - ); - styles.set_opt(Self::NUMBER_TYPE, args.named("number-type")?); - styles.set_opt(Self::NUMBER_WIDTH, args.named("number-width")?); - styles.set_opt(Self::NUMBER_POSITION, args.named("number-position")?); - styles.set_opt(Self::SLASHED_ZERO, args.named("slashed-zero")?); - styles.set_opt(Self::FRACTIONS, args.named("fractions")?); - styles.set_opt(Self::FEATURES, args.named("features")?); - - Ok(()) - } } /// Strong text, rendered in boldface. -pub struct StrongNode; +#[derive(Debug, Hash)] +pub struct StrongNode(pub Template); #[class] impl StrongNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { - Ok(args.expect::<Template>("body")?.styled(TextNode::STRONG, true)) + Ok(Template::show(Self(args.expect("body")?))) + } +} + +impl Show for StrongNode { + fn show(&self, _: StyleChain) -> Template { + self.0.clone().styled(TextNode::STRONG, true) } } /// Emphasized text, rendered with an italic face. -pub struct EmphNode; +#[derive(Debug, Hash)] +pub struct EmphNode(pub Template); #[class] impl EmphNode { fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> { - Ok(args.expect::<Template>("body")?.styled(TextNode::EMPH, true)) + Ok(Template::show(Self(args.expect("body")?))) + } +} + +impl Show for EmphNode { + fn show(&self, _: StyleChain) -> Template { + self.0.clone().styled(TextNode::EMPH, true) } } @@ -275,6 +256,12 @@ castable! { } castable! { + Em, + Expected: "float", + Value::Float(v) => Self::new(v), +} + +castable! { VerticalFontMetric, Expected: "linear or string", Value::Length(v) => Self::Linear(v.into()), @@ -644,18 +631,18 @@ fn variant(styles: StyleChain) -> FontVariant { /// Resolve a prioritized iterator over the font families. fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone { - let head = if styles.get(TextNode::MONOSPACE) { - styles.get_ref(TextNode::MONOSPACE_LIST).as_slice() + let head = if styles.get(TextNode::MONOSPACED) { + styles.get_ref(TextNode::MONOSPACE).as_slice() } else { &[] }; - let core = styles.get_ref(TextNode::FAMILY_LIST).iter().flat_map(move |family| { + let core = styles.get_ref(TextNode::FAMILY).iter().flat_map(move |family| { match family { FontFamily::Named(name) => std::slice::from_ref(name), - FontFamily::Serif => styles.get_ref(TextNode::SERIF_LIST), - FontFamily::SansSerif => styles.get_ref(TextNode::SANS_SERIF_LIST), - FontFamily::Monospace => styles.get_ref(TextNode::MONOSPACE_LIST), + FontFamily::Serif => styles.get_ref(TextNode::SERIF), + FontFamily::SansSerif => styles.get_ref(TextNode::SANS_SERIF), + FontFamily::Monospace => styles.get_ref(TextNode::MONOSPACE), } }); diff --git a/src/library/transform.rs b/src/library/transform.rs index 9d04071d..de053a9d 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -9,7 +9,7 @@ pub struct TransformNode<T: TransformKind> { /// Transformation to apply to the contents. pub kind: T, /// The node whose contents should be transformed. - pub child: PackedNode, + pub child: LayoutNode, } #[class] @@ -23,11 +23,6 @@ impl<T: TransformKind> TransformNode<T> { child: args.expect("body")?, })) } - - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - styles.set_opt(Self::ORIGIN, args.named("origin")?); - Ok(()) - } } impl<T: TransformKind> Layout for TransformNode<T> { diff --git a/src/util/mod.rs b/src/util/mod.rs index 32ec8dc4..84bd1aa1 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -3,9 +3,11 @@ #[macro_use] mod eco_string; mod mac_roman; +mod prehashed; pub use eco_string::EcoString; pub use mac_roman::decode_mac_roman; +pub use prehashed::Prehashed; use std::cell::RefMut; use std::cmp::Ordering; diff --git a/src/util/prehashed.rs b/src/util/prehashed.rs new file mode 100644 index 00000000..bab1c8f8 --- /dev/null +++ b/src/util/prehashed.rs @@ -0,0 +1,72 @@ +use std::any::Any; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; + +/// A wrapper around a type that precomputes its hash. +#[derive(Copy, Clone)] +pub struct Prehashed<T: ?Sized> { + /// The precomputed hash. + #[cfg(feature = "layout-cache")] + hash: u64, + /// The wrapped item. + item: T, +} + +impl<T: Hash + 'static> Prehashed<T> { + /// Compute an item's hash and wrap it. + pub fn new(item: T) -> Self { + Self { + #[cfg(feature = "layout-cache")] + hash: { + // Also hash the TypeId because the type might be converted + // through an unsized coercion. + let mut state = fxhash::FxHasher64::default(); + item.type_id().hash(&mut state); + item.hash(&mut state); + state.finish() + }, + item, + } + } + + /// Return the wrapped value. + pub fn into_iter(self) -> T { + self.item + } +} + +impl<T: ?Sized> Deref for Prehashed<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.item + } +} + +impl<T: Debug + ?Sized> Debug for Prehashed<T> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.item.fmt(f) + } +} + +impl<T: Hash + ?Sized> Hash for Prehashed<T> { + fn hash<H: Hasher>(&self, state: &mut H) { + // Hash the node. + #[cfg(feature = "layout-cache")] + state.write_u64(self.hash); + #[cfg(not(feature = "layout-cache"))] + self.item.hash(state); + } +} + +impl<T: Eq + ?Sized> Eq for Prehashed<T> {} + +impl<T: PartialEq + ?Sized> PartialEq for Prehashed<T> { + fn eq(&self, other: &Self) -> bool { + #[cfg(feature = "layout-cache")] + return self.hash == other.hash; + #[cfg(not(feature = "layout-cache"))] + self.item.eq(&other.item) + } +} |
