diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc.rs | 242 | ||||
| -rw-r--r-- | src/eval/array.rs | 14 | ||||
| -rw-r--r-- | src/eval/func.rs | 69 | ||||
| -rw-r--r-- | src/eval/library.rs | 26 | ||||
| -rw-r--r-- | src/eval/methods.rs | 31 | ||||
| -rw-r--r-- | src/eval/mod.rs | 42 | ||||
| -rw-r--r-- | src/eval/scope.rs | 4 | ||||
| -rw-r--r-- | src/eval/symbol.rs | 126 | ||||
| -rw-r--r-- | src/eval/value.rs | 9 | ||||
| -rw-r--r-- | src/export/pdf/page.rs | 71 | ||||
| -rw-r--r-- | src/export/render.rs | 40 | ||||
| -rw-r--r-- | src/geom/paint.rs | 14 | ||||
| -rw-r--r-- | src/geom/path.rs | 22 | ||||
| -rw-r--r-- | src/ide/analyze.rs | 15 | ||||
| -rw-r--r-- | src/ide/complete.rs | 4 | ||||
| -rw-r--r-- | src/ide/jump.rs | 54 | ||||
| -rw-r--r-- | src/lib.rs | 4 | ||||
| -rw-r--r-- | src/model/content.rs | 538 | ||||
| -rw-r--r-- | src/model/element.rs | 145 | ||||
| -rw-r--r-- | src/model/introspect.rs | 170 | ||||
| -rw-r--r-- | src/model/mod.rs | 83 | ||||
| -rw-r--r-- | src/model/realize.rs | 96 | ||||
| -rw-r--r-- | src/model/styles.rs | 271 | ||||
| -rw-r--r-- | src/model/typeset.rs | 241 | ||||
| -rw-r--r-- | src/syntax/kind.rs | 6 | ||||
| -rw-r--r-- | src/syntax/lexer.rs | 8 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 1 | ||||
| -rw-r--r-- | src/syntax/source.rs | 3 | ||||
| -rw-r--r-- | src/util/mod.rs | 25 |
29 files changed, 1261 insertions, 1113 deletions
@@ -14,7 +14,7 @@ use crate::geom::{ Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, }; use crate::image::Image; -use crate::model::{Content, Introspector, MetaNode, StableId, StyleChain}; +use crate::model::{Content, Location, MetaElem, StyleChain}; use crate::syntax::Span; /// A finished document with metadata and page frames. @@ -28,7 +28,7 @@ pub struct Document { pub author: Vec<EcoString>, } -/// A finished layout with elements at fixed positions. +/// A finished layout with items at fixed positions. #[derive(Default, Clone, Hash)] pub struct Frame { /// The size of the frame. @@ -36,8 +36,8 @@ pub struct Frame { /// The baseline of the frame measured from the top. If this is `None`, the /// frame's implicit baseline is at the bottom. baseline: Option<Abs>, - /// The elements composing this layout. - elements: Arc<Vec<(Point, Element)>>, + /// The items composing this layout. + items: Arc<Vec<(Point, FrameItem)>>, } /// Constructor, accessors and setters. @@ -48,12 +48,12 @@ impl Frame { #[track_caller] pub fn new(size: Size) -> Self { assert!(size.is_finite()); - Self { size, baseline: None, elements: Arc::new(vec![]) } + Self { size, baseline: None, items: Arc::new(vec![]) } } - /// Whether the frame contains no elements. + /// Whether the frame contains no items. pub fn is_empty(&self) -> bool { - self.elements.is_empty() + self.items.is_empty() } /// The size of the frame. @@ -109,23 +109,23 @@ impl Frame { self.size.y - self.baseline() } - /// An iterator over the elements inside this frame alongside their - /// positions relative to the top-left of the frame. - pub fn elements(&self) -> std::slice::Iter<'_, (Point, Element)> { - self.elements.iter() + /// An iterator over the items inside this frame alongside their positions + /// relative to the top-left of the frame. + pub fn items(&self) -> std::slice::Iter<'_, (Point, FrameItem)> { + self.items.iter() } - /// Recover the text inside of the frame and its children. + /// Approximately recover the text inside of the frame and its children. pub fn text(&self) -> EcoString { let mut text = EcoString::new(); - for (_, element) in self.elements() { - match element { - Element::Text(element) => { - for glyph in &element.glyphs { + for (_, item) in self.items() { + match item { + FrameItem::Text(item) => { + for glyph in &item.glyphs { text.push(glyph.c); } } - Element::Group(group) => text.push_str(&group.frame.text()), + FrameItem::Group(group) => text.push_str(&group.frame.text()), _ => {} } } @@ -133,53 +133,53 @@ impl Frame { } } -/// Insert elements and subframes. +/// Insert items and subframes. impl Frame { /// The layer the next item will be added on. This corresponds to the number - /// of elements in the frame. + /// of items in the frame. pub fn layer(&self) -> usize { - self.elements.len() + self.items.len() } - /// Add an element at a position in the foreground. - pub fn push(&mut self, pos: Point, element: Element) { - Arc::make_mut(&mut self.elements).push((pos, element)); + /// Add an item at a position in the foreground. + pub fn push(&mut self, pos: Point, item: FrameItem) { + Arc::make_mut(&mut self.items).push((pos, item)); } /// Add a frame at a position in the foreground. /// /// Automatically decides whether to inline the frame or to include it as a - /// group based on the number of elements in it. + /// group based on the number of items in it. pub fn push_frame(&mut self, pos: Point, frame: Frame) { if self.should_inline(&frame) { self.inline(self.layer(), pos, frame); } else { - self.push(pos, Element::Group(Group::new(frame))); + self.push(pos, FrameItem::Group(GroupItem::new(frame))); } } - /// Insert an element at the given layer in the frame. + /// Insert an item at the given layer in the frame. /// /// This panics if the layer is greater than the number of layers present. #[track_caller] - pub fn insert(&mut self, layer: usize, pos: Point, element: Element) { - Arc::make_mut(&mut self.elements).insert(layer, (pos, element)); + pub fn insert(&mut self, layer: usize, pos: Point, items: FrameItem) { + Arc::make_mut(&mut self.items).insert(layer, (pos, items)); } - /// Add an element at a position in the background. - pub fn prepend(&mut self, pos: Point, element: Element) { - Arc::make_mut(&mut self.elements).insert(0, (pos, element)); + /// Add an item at a position in the background. + pub fn prepend(&mut self, pos: Point, item: FrameItem) { + Arc::make_mut(&mut self.items).insert(0, (pos, item)); } - /// Add multiple elements at a position in the background. + /// Add multiple items at a position in the background. /// - /// The first element in the iterator will be the one that is most in the + /// The first item in the iterator will be the one that is most in the /// background. - pub fn prepend_multiple<I>(&mut self, elements: I) + pub fn prepend_multiple<I>(&mut self, items: I) where - I: IntoIterator<Item = (Point, Element)>, + I: IntoIterator<Item = (Point, FrameItem)>, { - Arc::make_mut(&mut self.elements).splice(0..0, elements); + Arc::make_mut(&mut self.items).splice(0..0, items); } /// Add a frame at a position in the background. @@ -187,31 +187,31 @@ impl Frame { if self.should_inline(&frame) { self.inline(0, pos, frame); } else { - self.prepend(pos, Element::Group(Group::new(frame))); + self.prepend(pos, FrameItem::Group(GroupItem::new(frame))); } } /// Whether the given frame should be inlined. fn should_inline(&self, frame: &Frame) -> bool { - self.elements.is_empty() || frame.elements.len() <= 5 + self.items.is_empty() || frame.items.len() <= 5 } /// Inline a frame at the given layer. fn inline(&mut self, layer: usize, pos: Point, frame: Frame) { - // Try to just reuse the elements. - if pos.is_zero() && self.elements.is_empty() { - self.elements = frame.elements; + // Try to just reuse the items. + if pos.is_zero() && self.items.is_empty() { + self.items = frame.items; return; } - // Try to transfer the elements without adjusting the position. - // Also try to reuse the elements if the Arc isn't shared. + // Try to transfer the items without adjusting the position. + // Also try to reuse the items if the Arc isn't shared. let range = layer..layer; if pos.is_zero() { - let sink = Arc::make_mut(&mut self.elements); - match Arc::try_unwrap(frame.elements) { - Ok(elements) => { - sink.splice(range, elements); + let sink = Arc::make_mut(&mut self.items); + match Arc::try_unwrap(frame.items) { + Ok(items) => { + sink.splice(range, items); } Err(arc) => { sink.splice(range, arc.iter().cloned()); @@ -220,12 +220,12 @@ impl Frame { return; } - // We must adjust the element positions. - // But still try to reuse the elements if the Arc isn't shared. - let sink = Arc::make_mut(&mut self.elements); - match Arc::try_unwrap(frame.elements) { - Ok(elements) => { - sink.splice(range, elements.into_iter().map(|(p, e)| (p + pos, e))); + // We have to adjust the item positions. + // But still try to reuse the items if the Arc isn't shared. + let sink = Arc::make_mut(&mut self.items); + match Arc::try_unwrap(frame.items) { + Ok(items) => { + sink.splice(range, items.into_iter().map(|(p, e)| (p + pos, e))); } Err(arc) => { sink.splice(range, arc.iter().cloned().map(|(p, e)| (p + pos, e))); @@ -236,12 +236,12 @@ impl Frame { /// Modify the frame. impl Frame { - /// Remove all elements from the frame. + /// Remove all items from the frame. pub fn clear(&mut self) { - if Arc::strong_count(&self.elements) == 1 { - Arc::make_mut(&mut self.elements).clear(); + if Arc::strong_count(&self.items) == 1 { + Arc::make_mut(&mut self.items).clear(); } else { - self.elements = Arc::new(vec![]); + self.items = Arc::new(vec![]); } } @@ -264,7 +264,7 @@ impl Frame { if let Some(baseline) = &mut self.baseline { *baseline += offset.y; } - for (point, _) in Arc::make_mut(&mut self.elements) { + for (point, _) in Arc::make_mut(&mut self.items) { *point += offset; } } @@ -273,12 +273,12 @@ impl Frame { /// Attach the metadata from this style chain to the frame. pub fn meta(&mut self, styles: StyleChain, force: bool) { if force || !self.is_empty() { - for meta in MetaNode::data_in(styles) { + for meta in MetaElem::data_in(styles) { if matches!(meta, Meta::Hide) { self.clear(); break; } - self.prepend(Point::zero(), Element::Meta(meta, self.size)); + self.prepend(Point::zero(), FrameItem::Meta(meta, self.size)); } } } @@ -287,7 +287,7 @@ impl Frame { pub fn fill(&mut self, fill: Paint) { self.prepend( Point::zero(), - Element::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()), + FrameItem::Shape(Geometry::Rect(self.size()).filled(fill), Span::detached()), ); } @@ -307,7 +307,7 @@ impl Frame { self.prepend_multiple( rounded_rect(size, radius, fill, stroke) .into_iter() - .map(|x| (pos, Element::Shape(x, span))), + .map(|x| (pos, FrameItem::Shape(x, span))), ) } @@ -328,13 +328,13 @@ impl Frame { /// Wrap the frame's contents in a group and modify that group with `f`. fn group<F>(&mut self, f: F) where - F: FnOnce(&mut Group), + F: FnOnce(&mut GroupItem), { let mut wrapper = Frame::new(self.size); wrapper.baseline = self.baseline; - let mut group = Group::new(std::mem::take(self)); + let mut group = GroupItem::new(std::mem::take(self)); f(&mut group); - wrapper.push(Point::zero(), Element::Group(group)); + wrapper.push(Point::zero(), FrameItem::Group(group)); *self = wrapper; } } @@ -346,7 +346,7 @@ impl Frame { self.insert( 0, Point::zero(), - Element::Shape( + FrameItem::Shape( Geometry::Rect(self.size) .filled(RgbaColor { a: 100, ..Color::TEAL.to_rgba() }.into()), Span::detached(), @@ -355,7 +355,7 @@ impl Frame { self.insert( 1, Point::with_y(self.baseline()), - Element::Shape( + FrameItem::Shape( Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { paint: Color::RED.into(), thickness: Abs::pt(1.0), @@ -371,7 +371,7 @@ impl Frame { let radius = Abs::pt(2.0); self.push( pos - Point::splat(radius), - Element::Shape( + FrameItem::Shape( geom::ellipse(Size::splat(2.0 * radius), Some(Color::GREEN.into()), None), Span::detached(), ), @@ -382,7 +382,7 @@ impl Frame { pub fn mark_line(&mut self, y: Abs) { self.push( Point::with_y(y), - Element::Shape( + FrameItem::Shape( Geometry::Line(Point::with_x(self.size.x)).stroked(Stroke { paint: Color::GREEN.into(), thickness: Abs::pt(1.0), @@ -397,18 +397,18 @@ impl Debug for Frame { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Frame ")?; f.debug_list() - .entries(self.elements.iter().map(|(_, element)| element)) + .entries(self.items.iter().map(|(_, item)| item)) .finish() } } /// The building block frames are composed of. #[derive(Clone, Hash)] -pub enum Element { - /// A group of elements. - Group(Group), +pub enum FrameItem { + /// A subframe with optional transformation and clipping. + Group(GroupItem), /// A run of shaped text. - Text(Text), + Text(TextItem), /// A geometric shape with optional fill and stroke. Shape(Shape, Span), /// An image and its size. @@ -417,7 +417,7 @@ pub enum Element { Meta(Meta, Size), } -impl Debug for Element { +impl Debug for FrameItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Group(group) => group.fmt(f), @@ -429,9 +429,9 @@ impl Debug for Element { } } -/// A group of elements with optional clipping. +/// A subframe with optional transformation and clipping. #[derive(Clone, Hash)] -pub struct Group { +pub struct GroupItem { /// The group's frame. pub frame: Frame, /// A transformation to apply to the group. @@ -440,7 +440,7 @@ pub struct Group { pub clips: bool, } -impl Group { +impl GroupItem { /// Create a new group with default settings. pub fn new(frame: Frame) -> Self { Self { @@ -451,7 +451,7 @@ impl Group { } } -impl Debug for Group { +impl Debug for GroupItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Group ")?; self.frame.fmt(f) @@ -460,7 +460,7 @@ impl Debug for Group { /// A run of shaped text. #[derive(Clone, Eq, PartialEq, Hash)] -pub struct Text { +pub struct TextItem { /// The font the glyphs are contained in. pub font: Font, /// The font size. @@ -473,14 +473,14 @@ pub struct Text { pub glyphs: Vec<Glyph>, } -impl Text { +impl TextItem { /// The width of the text run. pub fn width(&self) -> Abs { self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size) } } -impl Debug for Text { +impl Debug for TextItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { // This is only a rough approxmiation of the source text. f.write_str("Text(\"")?; @@ -595,97 +595,73 @@ cast_to_value! { } /// Meta information that isn't visible or renderable. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum Meta { - /// Indicates that the content should be hidden. + /// An internal or external link to a destination. + Link(Destination), + /// An identifiable element that produces something within the area this + /// metadata is attached to. + Elem(Content), + /// Indicates that content should be hidden. This variant doesn't appear + /// in the final frames as it is removed alongside the content that should + /// be hidden. Hide, - /// An internal or external link. - Link(Link), - /// An identifiable piece of content that produces something within the - /// area this metadata is attached to. - Node(Content), } cast_from_value! { Meta: "meta", } -impl PartialEq for Meta { - fn eq(&self, other: &Self) -> bool { - crate::util::hash128(self) == crate::util::hash128(other) - } -} - -/// A possibly unresolved link. -#[derive(Debug, Clone, Hash)] -pub enum Link { - /// A fully resolved. - Dest(Destination), - /// An unresolved link to a node. - Node(StableId), -} - -impl Link { - /// Resolve a destination. - /// - /// Needs to lazily provide an introspector. - pub fn resolve<'a>( - &self, - introspector: impl FnOnce() -> &'a Introspector, - ) -> Destination { - match self { - Self::Dest(dest) => dest.clone(), - Self::Node(id) => Destination::Internal(introspector().location(*id)), - } - } -} - /// A link destination. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Destination { - /// A link to a point on a page. - Internal(Location), /// A link to a URL. Url(EcoString), + /// A link to a point on a page. + Position(Position), + /// An unresolved link to a location in the document. + Location(Location), } cast_from_value! { Destination, - loc: Location => Self::Internal(loc), - string: EcoString => Self::Url(string), + v: EcoString => Self::Url(v), + v: Position => Self::Position(v), + v: Location => Self::Location(v), } cast_to_value! { v: Destination => match v { - Destination::Internal(loc) => loc.into(), - Destination::Url(url) => url.into(), + Destination::Url(v) => v.into(), + Destination::Position(v) => v.into(), + Destination::Location(v) => v.into(), } } -/// A physical location in a document. +/// A physical position in a document. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct Location { +pub struct Position { /// The page, starting at 1. pub page: NonZeroUsize, /// The exact coordinates on the page (from the top left, as usual). - pub pos: Point, + pub point: Point, } cast_from_value! { - Location, + Position, mut dict: Dict => { let page = dict.take("page")?.cast()?; let x: Length = dict.take("x")?.cast()?; let y: Length = dict.take("y")?.cast()?; dict.finish(&["page", "x", "y"])?; - Self { page, pos: Point::new(x.abs, y.abs) } + Self { page, point: Point::new(x.abs, y.abs) } }, } cast_to_value! { - v: Location => Value::Dict(dict! { + v: Position => Value::Dict(dict! { "page" => Value::Int(v.page.get() as i64), - "x" => Value::Length(v.pos.x.into()), - "y" => Value::Length(v.pos.y.into()), + "x" => Value::Length(v.point.x.into()), + "y" => Value::Length(v.point.y.into()), }) } diff --git a/src/eval/array.rs b/src/eval/array.rs index fa71ff1a..bebbe809 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -137,7 +137,7 @@ impl Array { self.0.contains(value) } - /// Return the first matching element. + /// Return the first matching item. pub fn find(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<Value>> { for item in self.iter() { let args = Args::new(func.span(), [item.clone()]); @@ -148,7 +148,7 @@ impl Array { Ok(None) } - /// Return the index of the first matching element. + /// Return the index of the first matching item. pub fn position(&self, vm: &mut Vm, func: Func) -> SourceResult<Option<i64>> { for (i, item) in self.iter().enumerate() { let args = Args::new(func.span(), [item.clone()]); @@ -160,8 +160,8 @@ impl Array { Ok(None) } - /// Return a new array with only those elements for which the function - /// returns true. + /// Return a new array with only those items for which the function returns + /// true. pub fn filter(&self, vm: &mut Vm, func: Func) -> SourceResult<Self> { let mut kept = EcoVec::new(); for item in self.iter() { @@ -189,7 +189,7 @@ impl Array { .collect() } - /// Fold all of the array's elements into one with a function. + /// Fold all of the array's items into one with a function. pub fn fold(&self, vm: &mut Vm, init: Value, func: Func) -> SourceResult<Value> { let mut acc = init; for item in self.iter() { @@ -199,7 +199,7 @@ impl Array { Ok(acc) } - /// Whether any element matches. + /// Whether any item matches. pub fn any(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { for item in self.iter() { let args = Args::new(func.span(), [item.clone()]); @@ -211,7 +211,7 @@ impl Array { Ok(false) } - /// Whether all elements match. + /// Whether all items match. pub fn all(&self, vm: &mut Vm, func: Func) -> SourceResult<bool> { for item in self.iter() { let args = Args::new(func.span(), [item.clone()]); diff --git a/src/eval/func.rs b/src/eval/func.rs index 7bf1814f..ef042d6d 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -8,14 +8,12 @@ use comemo::{Prehashed, Track, Tracked, TrackedMut}; use once_cell::sync::Lazy; use super::{ - cast_to_value, Args, CastInfo, Dict, Eval, Flow, Route, Scope, Scopes, Tracer, Value, - Vm, + cast_to_value, Args, CastInfo, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm, }; -use crate::diag::{bail, SourceResult, StrResult}; -use crate::model::{Introspector, NodeId, Selector, StabilityProvider, StyleMap, Vt}; +use crate::diag::{bail, SourceResult}; +use crate::model::{ElemFunc, Introspector, StabilityProvider, Vt}; use crate::syntax::ast::{self, AstNode, Expr, Ident}; use crate::syntax::{SourceId, Span, SyntaxNode}; -use crate::util::hash128; use crate::World; /// An evaluatable function. @@ -32,8 +30,8 @@ pub struct Func { enum Repr { /// A native Rust function. Native(NativeFunc), - /// A function for a node. - Node(NodeId), + /// A function for an element. + Elem(ElemFunc), /// A user-defined closure. Closure(Closure), /// A nested function with pre-applied arguments. @@ -45,7 +43,7 @@ impl Func { pub fn name(&self) -> Option<&str> { match &**self.repr { Repr::Native(native) => Some(native.info.name), - Repr::Node(node) => Some(node.info.name), + Repr::Elem(func) => Some(func.info().name), Repr::Closure(closure) => closure.name.as_deref(), Repr::With(func, _) => func.name(), } @@ -55,7 +53,7 @@ impl Func { pub fn info(&self) -> Option<&FuncInfo> { match &**self.repr { Repr::Native(native) => Some(&native.info), - Repr::Node(node) => Some(&node.info), + Repr::Elem(func) => Some(func.info()), Repr::With(func, _) => func.info(), _ => None, } @@ -93,8 +91,8 @@ impl Func { args.finish()?; Ok(value) } - Repr::Node(node) => { - let value = (node.construct)(vm, &mut args)?; + Repr::Elem(func) => { + let value = func.construct(vm, &mut args)?; args.finish()?; Ok(Value::Content(value)) } @@ -145,46 +143,13 @@ impl Func { } } - /// Create a selector for this function's node type, filtering by node's - /// whose [fields](super::Content::field) match the given arguments. - pub fn where_(self, args: &mut Args) -> StrResult<Selector> { - let fields = args.to_named(); - args.items.retain(|arg| arg.name.is_none()); - self.select(Some(fields)) - } - - /// The node id of this function if it is an element function. - pub fn id(&self) -> Option<NodeId> { + /// Extract the element function, if it is one. + pub fn element(&self) -> Option<ElemFunc> { match **self.repr { - Repr::Node(id) => Some(id), + Repr::Elem(func) => Some(func), _ => None, } } - - /// Execute the function's set rule and return the resulting style map. - pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> { - Ok(match &**self.repr { - Repr::Node(node) => { - let styles = (node.set)(&mut args)?; - args.finish()?; - styles - } - _ => StyleMap::new(), - }) - } - - /// Create a selector for this function's node type. - pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> { - let Some(id) = self.id() else { - return Err("this function is not selectable".into()); - }; - - if id == item!(text_id) { - Err("to select text, please use a string or regex instead")?; - } - - Ok(Selector::Node(id, fields)) - } } impl Debug for Func { @@ -198,7 +163,7 @@ impl Debug for Func { impl PartialEq for Func { fn eq(&self, other: &Self) -> bool { - hash128(&self.repr) == hash128(&other.repr) + self.repr == other.repr } } @@ -211,13 +176,13 @@ impl From<Repr> for Func { } } -impl From<NodeId> for Func { - fn from(id: NodeId) -> Self { - Repr::Node(id).into() +impl From<ElemFunc> for Func { + fn from(func: ElemFunc) -> Self { + Repr::Elem(func).into() } } -/// A native Rust function. +/// A Typst function defined by a native Rust function. pub struct NativeFunc { /// The function's implementation. pub func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, diff --git a/src/eval/library.rs b/src/eval/library.rs index eae342c2..85d5647b 100644 --- a/src/eval/library.rs +++ b/src/eval/library.rs @@ -10,7 +10,7 @@ use super::{Args, Dynamic, Module, Value, Vm}; use crate::diag::SourceResult; use crate::doc::Document; use crate::geom::{Abs, Dir}; -use crate::model::{Content, Introspector, Label, NodeId, StyleChain, StyleMap, Vt}; +use crate::model::{Content, ElemFunc, Introspector, Label, StyleChain, Styles, Vt}; use crate::syntax::Span; use crate::util::hash128; use crate::World; @@ -23,7 +23,7 @@ pub struct Library { /// The scope containing definitions available in math mode. pub math: Module, /// The default properties for page size, font selection and so on. - pub styles: StyleMap, + pub styles: Styles, /// Defines which standard library items fulfill which syntactical roles. pub items: LangItems, } @@ -44,9 +44,9 @@ pub struct LangItems { pub linebreak: fn() -> Content, /// Plain text without markup. pub text: fn(text: EcoString) -> Content, - /// The id of the text node. - pub text_id: NodeId, - /// Get the string if this is a text node. + /// The text function. + pub text_func: ElemFunc, + /// Get the string if this is a text element. pub text_str: fn(&Content) -> Option<EcoString>, /// A smart quote: `'` or `"`. pub smart_quote: fn(double: bool) -> Content, @@ -114,7 +114,7 @@ impl Hash for LangItems { self.space.hash(state); self.linebreak.hash(state); self.text.hash(state); - self.text_id.hash(state); + self.text_func.hash(state); (self.text_str as usize).hash(state); self.smart_quote.hash(state); self.parbreak.hash(state); @@ -140,13 +140,15 @@ impl Hash for LangItems { #[doc(hidden)] pub static LANG_ITEMS: OnceCell<LangItems> = OnceCell::new(); -/// Set the lang items. This is a hack :( +/// Set the lang items. /// -/// Passing the lang items everywhere they are needed (especially the text node -/// related things) is very painful. By storing them globally, in theory, we -/// break incremental, but only when different sets of lang items are used in -/// the same program. For this reason, if this function is called multiple -/// times, the items must be the same. +/// This is a hack :( +/// +/// Passing the lang items everywhere they are needed (especially text related +/// things) is very painful. By storing them globally, in theory, we break +/// incremental, but only when different sets of lang items are used in the same +/// program. For this reason, if this function is called multiple times, the +/// items must be the same (and this is enforced). pub fn set_lang_items(items: LangItems) { if let Err(items) = LANG_ITEMS.set(items) { let first = hash128(LANG_ITEMS.get().unwrap()); diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 324191ab..72245fb0 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -4,7 +4,7 @@ use ecow::EcoString; use super::{Args, Str, Value, Vm}; use crate::diag::{At, SourceResult}; -use crate::model::StableId; +use crate::model::Location; use crate::syntax::Span; /// Call a method on a value. @@ -71,12 +71,12 @@ pub fn call( }, Value::Content(content) => match method { - "func" => Value::Func(content.id().into()), + "func" => content.func().into(), "has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)), "at" => content.at(&args.expect::<EcoString>("field")?).at(span)?.clone(), - "id" => content - .stable_id() - .ok_or("this method can only be called on content returned by query()") + "location" => content + .location() + .ok_or("this method can only be called on content returned by query(..)") .at(span)? .into(), _ => return missing(), @@ -130,7 +130,16 @@ pub fn call( Value::Func(func) => match method { "with" => Value::Func(func.with(args.take())), - "where" => Value::dynamic(func.where_(&mut args).at(span)?), + "where" => { + let fields = args.to_named(); + args.items.retain(|arg| arg.name.is_none()); + Value::dynamic( + func.element() + .ok_or("`where()` can only be called on element functions") + .at(span)? + .where_(fields), + ) + } _ => return missing(), }, @@ -141,10 +150,10 @@ pub fn call( }, Value::Dyn(dynamic) => { - if let Some(&id) = dynamic.downcast::<StableId>() { + if let Some(&location) = dynamic.downcast::<Location>() { match method { - "page" => vm.vt.introspector.page(id).into(), - "location" => vm.vt.introspector.location(id).into(), + "page" => vm.vt.introspector.page(location).into(), + "position" => vm.vt.introspector.position(location).into(), _ => return missing(), } } else { @@ -263,7 +272,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ("starts-with", true), ("trim", true), ], - "content" => &[("func", false), ("has", true), ("at", true), ("id", false)], + "content" => &[("func", false), ("has", true), ("at", true), ("location", false)], "array" => &[ ("all", true), ("any", true), @@ -299,7 +308,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ], "function" => &[("where", true), ("with", true)], "arguments" => &[("named", false), ("pos", false)], - "stable id" => &[("page", false), ("location", false)], + "location" => &[("page", false), ("position", false)], "counter" => &[ ("display", true), ("at", true), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 74c5f0b3..f19e4305 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -29,13 +29,14 @@ pub use self::cast::*; pub use self::dict::*; pub use self::func::*; pub use self::library::*; -pub use self::methods::*; pub use self::module::*; pub use self::scope::*; pub use self::str::*; pub use self::symbol::*; pub use self::value::*; +pub(crate) use self::methods::methods_on; + use std::collections::BTreeMap; use std::mem; use std::path::{Path, PathBuf}; @@ -47,11 +48,10 @@ use unicode_segmentation::UnicodeSegmentation; use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, }; -use crate::model::Introspector; -use crate::model::StabilityProvider; -use crate::model::Unlabellable; -use crate::model::Vt; -use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform}; +use crate::model::{ + Content, Introspector, Label, Recipe, Selector, StabilityProvider, Styles, Transform, + Unlabellable, Vt, +}; use crate::syntax::ast::AstNode; use crate::syntax::{ ast, parse_code, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, @@ -114,12 +114,12 @@ pub fn eval( /// /// Everything in the output is associated with the given `span`. #[comemo::memoize] -pub fn eval_code_str( +pub fn eval_string( world: Tracked<dyn World>, - text: &str, + code: &str, span: Span, ) -> SourceResult<Value> { - let mut root = parse_code(text); + let mut root = parse_code(code); root.synthesize(span); let errors = root.errors(); @@ -290,7 +290,7 @@ impl Route { } } -/// Traces which values existed for the expression with the given span. +/// Traces which values existed for the expression at a span. #[derive(Default, Clone)] pub struct Tracer { span: Option<Span>, @@ -377,10 +377,10 @@ fn eval_markup( } expr => match expr.eval(vm)? { Value::Label(label) => { - if let Some(node) = + if let Some(elem) = seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>()) { - *node = mem::take(node).labelled(label); + *elem = mem::take(elem).labelled(label); } } value => seq.push(value.display().spanned(expr.span())), @@ -643,7 +643,7 @@ impl Eval for ast::Math { Ok(Content::sequence( self.exprs() .map(|expr| expr.eval_display(vm)) - .collect::<SourceResult<_>>()?, + .collect::<SourceResult<Vec<_>>>()?, )) } } @@ -1049,7 +1049,7 @@ impl Eval for ast::FuncCall { if in_math && !matches!(callee, Value::Func(_)) { if let Value::Symbol(sym) = &callee { let c = sym.get(); - if let Some(accent) = combining_accent(c) { + if let Some(accent) = Symbol::combining_accent(c) { let base = args.expect("base")?; args.finish()?; return Ok(Value::Content((vm.items.math_accent)(base, accent))); @@ -1198,17 +1198,25 @@ impl Eval for ast::LetBinding { } impl Eval for ast::SetRule { - type Output = StyleMap; + type Output = Styles; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { if let Some(condition) = self.condition() { if !condition.eval(vm)?.cast::<bool>().at(condition.span())? { - return Ok(StyleMap::new()); + return Ok(Styles::new()); } } let target = self.target(); - let target = target.eval(vm)?.cast::<Func>().at(target.span())?; + let target = target + .eval(vm)? + .cast::<Func>() + .and_then(|func| { + func.element().ok_or_else(|| { + "only element functions can be used in set rules".into() + }) + }) + .at(target.span())?; let args = self.args().eval(vm)?; Ok(target.set(args)?.spanned(self.span())) } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index d4338b5c..e241cac5 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -163,7 +163,9 @@ impl Slot { fn write(&mut self) -> StrResult<&mut Value> { match self.kind { Kind::Normal => Ok(&mut self.value), - Kind::Captured => Err("cannot mutate a captured variable")?, + Kind::Captured => { + Err("variables from outside the function are read-only and cannot be modified")? + } } } } diff --git a/src/eval/symbol.rs b/src/eval/symbol.rs index 73c41067..6a199a1d 100644 --- a/src/eval/symbol.rs +++ b/src/eval/symbol.rs @@ -1,94 +1,127 @@ use std::cmp::Reverse; use std::collections::BTreeSet; use std::fmt::{self, Debug, Display, Formatter, Write}; +use std::sync::Arc; -use ecow::{EcoString, EcoVec}; +use ecow::EcoString; use crate::diag::StrResult; #[doc(inline)] pub use typst_macros::symbols; -/// A symbol. +/// A symbol, possibly with variants. #[derive(Clone, Eq, PartialEq, Hash)] -pub struct Symbol { - repr: Repr, - modifiers: EcoString, -} +pub struct Symbol(Repr); -/// A collection of symbols. +/// The internal representation. #[derive(Clone, Eq, PartialEq, Hash)] enum Repr { Single(char), + Const(&'static [(&'static str, char)]), + Multi(Arc<(List, EcoString)>), +} + +/// A collection of symbols. +#[derive(Clone, Eq, PartialEq, Hash)] +enum List { Static(&'static [(&'static str, char)]), - Runtime(EcoVec<(EcoString, char)>), + Runtime(Box<[(EcoString, char)]>), } impl Symbol { /// Create a new symbol from a single character. pub const fn new(c: char) -> Self { - Self { repr: Repr::Single(c), modifiers: EcoString::new() } + Self(Repr::Single(c)) } /// Create a symbol with a static variant list. #[track_caller] pub const fn list(list: &'static [(&'static str, char)]) -> Self { debug_assert!(!list.is_empty()); - Self { - repr: Repr::Static(list), - modifiers: EcoString::new(), - } + Self(Repr::Const(list)) } /// Create a symbol with a runtime variant list. #[track_caller] - pub fn runtime(list: EcoVec<(EcoString, char)>) -> Self { + pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { debug_assert!(!list.is_empty()); - Self { - repr: Repr::Runtime(list), - modifiers: EcoString::new(), - } + Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new())))) } /// Get the symbol's text. pub fn get(&self) -> char { - match self.repr { - Repr::Single(c) => c, - _ => find(self.variants(), &self.modifiers).unwrap(), + match &self.0 { + Repr::Single(c) => *c, + Repr::Const(_) => find(self.variants(), "").unwrap(), + Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(), } } /// Apply a modifier to the symbol. pub fn modified(mut self, modifier: &str) -> StrResult<Self> { - if !self.modifiers.is_empty() { - self.modifiers.push('.'); + if let Repr::Const(list) = self.0 { + self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new()))); } - self.modifiers.push_str(modifier); - if find(self.variants(), &self.modifiers).is_none() { - Err("unknown modifier")? + + if let Repr::Multi(arc) = &mut self.0 { + let (list, modifiers) = Arc::make_mut(arc); + if !modifiers.is_empty() { + modifiers.push('.'); + } + modifiers.push_str(modifier); + if find(list.variants(), &modifiers).is_some() { + return Ok(self); + } } - Ok(self) + + Err("unknown symbol modifier".into()) } /// The characters that are covered by this symbol. pub fn variants(&self) -> impl Iterator<Item = (&str, char)> { - match &self.repr { + match &self.0 { Repr::Single(c) => Variants::Single(Some(*c).into_iter()), - Repr::Static(list) => Variants::Static(list.iter()), - Repr::Runtime(list) => Variants::Runtime(list.iter()), + Repr::Const(list) => Variants::Static(list.iter()), + Repr::Multi(arc) => arc.0.variants(), } } /// Possible modifiers. pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ { let mut set = BTreeSet::new(); + let modifiers = match &self.0 { + Repr::Multi(arc) => arc.1.as_str(), + _ => "", + }; for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { - if !modifier.is_empty() && !contained(&self.modifiers, modifier) { + if !modifier.is_empty() && !contained(modifiers, modifier) { set.insert(modifier); } } set.into_iter() } + + /// Normalize an accent to a combining one. + pub fn combining_accent(c: char) -> Option<char> { + Some(match c { + '\u{0300}' | '`' => '\u{0300}', + '\u{0301}' | '´' => '\u{0301}', + '\u{0302}' | '^' | 'ˆ' => '\u{0302}', + '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', + '\u{0304}' | '¯' => '\u{0304}', + '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', + '\u{0306}' | '˘' => '\u{0306}', + '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', + '\u{0308}' | '¨' => '\u{0308}', + '\u{030a}' | '∘' | '○' => '\u{030a}', + '\u{030b}' | '˝' => '\u{030b}', + '\u{030c}' | 'ˇ' => '\u{030c}', + '\u{20d6}' | '←' => '\u{20d6}', + '\u{20d7}' | '→' | '⟶' => '\u{20d7}', + _ => return None, + }) + } } impl Debug for Symbol { @@ -103,6 +136,16 @@ impl Display for Symbol { } } +impl List { + /// The characters that are covered by this list. + fn variants(&self) -> Variants<'_> { + match self { + List::Static(list) => Variants::Static(list.iter()), + List::Runtime(list) => Variants::Runtime(list.iter()), + } + } +} + /// Iterator over variants. enum Variants<'a> { Single(std::option::IntoIter<char>), @@ -166,24 +209,3 @@ fn parts(modifiers: &str) -> impl Iterator<Item = &str> { fn contained(modifiers: &str, m: &str) -> bool { parts(modifiers).any(|part| part == m) } - -/// Normalize an accent to a combining one. -pub fn combining_accent(c: char) -> Option<char> { - Some(match c { - '\u{0300}' | '`' => '\u{0300}', - '\u{0301}' | '´' => '\u{0301}', - '\u{0302}' | '^' | 'ˆ' => '\u{0302}', - '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', - '\u{0304}' | '¯' => '\u{0304}', - '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', - '\u{0306}' | '˘' => '\u{0306}', - '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', - '\u{0308}' | '¨' => '\u{0308}', - '\u{030a}' | '∘' | '○' => '\u{030a}', - '\u{030b}' | '˝' => '\u{030b}', - '\u{030c}' | 'ˇ' => '\u{030c}', - '\u{20d6}' | '←' => '\u{20d6}', - '\u{20d7}' | '→' | '⟶' => '\u{20d7}', - _ => return None, - }) -} diff --git a/src/eval/value.rs b/src/eval/value.rs index 61af36f5..ce9c4e0e 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -13,6 +13,7 @@ use super::{ }; use crate::diag::StrResult; use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel}; +use crate::model::Styles; use crate::syntax::{ast, Span}; /// A computational value. @@ -48,6 +49,8 @@ pub enum Value { Label(Label), /// A content value: `[*Hi* there]`. Content(Content), + // Content styles. + Styles(Styles), /// An array of values: `(1, "hi", 12cm)`. Array(Array), /// A dictionary value: `(color: #f79143, pattern: dashed)`. @@ -101,6 +104,7 @@ impl Value { Self::Str(_) => Str::TYPE_NAME, Self::Label(_) => Label::TYPE_NAME, Self::Content(_) => Content::TYPE_NAME, + Self::Styles(_) => Styles::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME, @@ -120,7 +124,7 @@ impl Value { match self { Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol), Self::Dict(dict) => dict.at(&field).cloned(), - Self::Content(content) => content.at(&field).cloned(), + Self::Content(content) => content.at(&field), Self::Module(module) => module.get(&field).cloned(), v => Err(eco_format!("cannot access fields on type {}", v.type_name())), } @@ -188,6 +192,7 @@ impl Debug for Value { Self::Str(v) => Debug::fmt(v, f), Self::Label(v) => Debug::fmt(v, f), Self::Content(v) => Debug::fmt(v, f), + Self::Styles(v) => Debug::fmt(v, f), Self::Array(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), @@ -229,6 +234,7 @@ impl Hash for Value { Self::Str(v) => v.hash(state), Self::Label(v) => v.hash(state), Self::Content(v) => v.hash(state), + Self::Styles(v) => v.hash(state), Self::Array(v) => v.hash(state), Self::Dict(v) => v.hash(state), Self::Func(v) => v.hash(state), @@ -400,6 +406,7 @@ primitive! { Content: "content", Symbol(v) => item!(text)(v.get().into()), Str(v) => item!(text)(v.into()) } +primitive! { Styles: "styles", Styles } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Func: "function", Func } diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index 5347d831..f3c81cb8 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -4,7 +4,7 @@ use pdf_writer::writers::ColorSpace; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB}; -use crate::doc::{Destination, Element, Frame, Group, Link, Meta, Text}; +use crate::doc::{Destination, Frame, FrameItem, GroupItem, Meta, TextItem}; use crate::font::Font; use crate::geom::{ self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, @@ -110,29 +110,32 @@ fn write_page(ctx: &mut PdfContext, page: Page) { page_writer.contents(content_id); let mut annotations = page_writer.annotations(); - for (link, rect) in page.links { + for (dest, rect) in page.links { let mut annotation = annotations.push(); annotation.subtype(AnnotationType::Link).rect(rect); annotation.border(0.0, 0.0, 0.0, None); - match link.resolve(|| &ctx.introspector) { + + let pos = match dest { Destination::Url(uri) => { annotation .action() .action_type(ActionType::Uri) .uri(Str(uri.as_bytes())); + continue; } - Destination::Internal(loc) => { - let index = loc.page.get() - 1; - let y = (loc.pos.y - Abs::pt(10.0)).max(Abs::zero()); - if let Some(&height) = ctx.page_heights.get(index) { - annotation - .action() - .action_type(ActionType::GoTo) - .destination_direct() - .page(ctx.page_refs[index]) - .xyz(loc.pos.x.to_f32(), height - y.to_f32(), None); - } - } + Destination::Position(pos) => pos, + Destination::Location(loc) => ctx.introspector.position(loc), + }; + + let index = pos.page.get() - 1; + let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); + if let Some(&height) = ctx.page_heights.get(index) { + annotation + .action() + .action_type(ActionType::GoTo) + .destination_direct() + .page(ctx.page_refs[index]) + .xyz(pos.point.x.to_f32(), height - y.to_f32(), None); } } @@ -153,7 +156,7 @@ pub struct Page { /// The page's content stream. pub content: Content, /// Links in the PDF coordinate system. - pub links: Vec<(Link, Rect)>, + pub links: Vec<(Destination, Rect)>, } /// An exporter for the contents of a single PDF page. @@ -164,7 +167,7 @@ struct PageContext<'a, 'b> { state: State, saves: Vec<State>, bottom: f32, - links: Vec<(Link, Rect)>, + links: Vec<(Destination, Rect)>, } /// A simulated graphics state used to deduplicate graphics state changes and @@ -283,17 +286,17 @@ impl PageContext<'_, '_> { /// Encode a frame into the content stream. fn write_frame(ctx: &mut PageContext, frame: &Frame) { - for &(pos, ref element) in frame.elements() { + for &(pos, ref item) in frame.items() { let x = pos.x.to_f32(); let y = pos.y.to_f32(); - match element { - Element::Group(group) => write_group(ctx, pos, group), - Element::Text(text) => write_text(ctx, x, y, text), - Element::Shape(shape, _) => write_shape(ctx, x, y, shape), - Element::Image(image, size, _) => write_image(ctx, x, y, image, *size), - Element::Meta(meta, size) => match meta { - Meta::Link(link) => write_link(ctx, pos, link, *size), - Meta::Node(_) => {} + match item { + FrameItem::Group(group) => write_group(ctx, pos, group), + FrameItem::Text(text) => write_text(ctx, x, y, text), + FrameItem::Shape(shape, _) => write_shape(ctx, x, y, shape), + FrameItem::Image(image, size, _) => write_image(ctx, x, y, image, *size), + FrameItem::Meta(meta, size) => match meta { + Meta::Link(dest) => write_link(ctx, pos, dest, *size), + Meta::Elem(_) => {} Meta::Hide => {} }, } @@ -301,7 +304,7 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) { } /// Encode a group into the content stream. -fn write_group(ctx: &mut PageContext, pos: Point, group: &Group) { +fn write_group(ctx: &mut PageContext, pos: Point, group: &GroupItem) { let translation = Transform::translate(pos.x, pos.y); ctx.save_state(); @@ -324,7 +327,7 @@ fn write_group(ctx: &mut PageContext, pos: Point, group: &Group) { } /// Encode a text run into the content stream. -fn write_text(ctx: &mut PageContext, x: f32, y: f32, text: &Text) { +fn write_text(ctx: &mut PageContext, x: f32, y: f32, text: &TextItem) { *ctx.parent.languages.entry(text.lang).or_insert(0) += text.glyphs.len(); ctx.parent .glyph_sets @@ -422,13 +425,13 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) { fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) { for elem in &path.0 { match elem { - geom::PathElement::MoveTo(p) => { + geom::PathItem::MoveTo(p) => { ctx.content.move_to(x + p.x.to_f32(), y + p.y.to_f32()) } - geom::PathElement::LineTo(p) => { + geom::PathItem::LineTo(p) => { ctx.content.line_to(x + p.x.to_f32(), y + p.y.to_f32()) } - geom::PathElement::CubicTo(p1, p2, p3) => ctx.content.cubic_to( + geom::PathItem::CubicTo(p1, p2, p3) => ctx.content.cubic_to( x + p1.x.to_f32(), y + p1.y.to_f32(), x + p2.x.to_f32(), @@ -436,7 +439,7 @@ fn write_path(ctx: &mut PageContext, x: f32, y: f32, path: &geom::Path) { x + p3.x.to_f32(), y + p3.y.to_f32(), ), - geom::PathElement::ClosePath => ctx.content.close_path(), + geom::PathItem::ClosePath => ctx.content.close_path(), }; } } @@ -454,7 +457,7 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) } /// Save a link for later writing in the annotations dictionary. -fn write_link(ctx: &mut PageContext, pos: Point, link: &Link, size: Size) { +fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size) { let mut min_x = Abs::inf(); let mut min_y = Abs::inf(); let mut max_x = -Abs::inf(); @@ -480,5 +483,5 @@ fn write_link(ctx: &mut PageContext, pos: Point, link: &Link, size: Size) { let y2 = min_y.to_f32(); let rect = Rect::new(x1, y1, x2, y2); - ctx.links.push((link.clone(), rect)); + ctx.links.push((dest.clone(), rect)); } diff --git a/src/export/render.rs b/src/export/render.rs index 58659b98..11ab5447 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -9,9 +9,9 @@ use tiny_skia as sk; use ttf_parser::{GlyphId, OutlineBuilder}; use usvg::FitTo; -use crate::doc::{Element, Frame, Group, Meta, Text}; +use crate::doc::{Frame, FrameItem, GroupItem, Meta, TextItem}; use crate::geom::{ - self, Abs, Color, Geometry, Paint, PathElement, Shape, Size, Stroke, Transform, + self, Abs, Color, Geometry, Paint, PathItem, Shape, Size, Stroke, Transform, }; use crate::image::{DecodedImage, Image}; @@ -33,34 +33,34 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap { canvas } -/// Render all elements in a frame into the canvas. +/// Render a frame into the canvas. fn render_frame( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, frame: &Frame, ) { - for (pos, element) in frame.elements() { + for (pos, item) in frame.items() { let x = pos.x.to_f32(); let y = pos.y.to_f32(); let ts = ts.pre_translate(x, y); - match element { - Element::Group(group) => { + match item { + FrameItem::Group(group) => { render_group(canvas, ts, mask, group); } - Element::Text(text) => { + FrameItem::Text(text) => { render_text(canvas, ts, mask, text); } - Element::Shape(shape, _) => { + FrameItem::Shape(shape, _) => { render_shape(canvas, ts, mask, shape); } - Element::Image(image, size, _) => { + FrameItem::Image(image, size, _) => { render_image(canvas, ts, mask, image, *size); } - Element::Meta(meta, _) => match meta { + FrameItem::Meta(meta, _) => match meta { Meta::Link(_) => {} - Meta::Node(_) => {} + Meta::Elem(_) => {} Meta::Hide => {} }, } @@ -72,7 +72,7 @@ fn render_group( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - group: &Group, + group: &GroupItem, ) { let ts = ts.pre_concat(group.transform.into()); @@ -114,7 +114,7 @@ fn render_text( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, ) { let mut x = 0.0; for glyph in &text.glyphs { @@ -135,7 +135,7 @@ fn render_svg_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, _: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, id: GlyphId, ) -> Option<()> { let mut data = text.font.ttf().glyph_svg_image(id)?; @@ -184,7 +184,7 @@ fn render_bitmap_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, id: GlyphId, ) -> Option<()> { let size = text.size.to_f32(); @@ -208,7 +208,7 @@ fn render_outline_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, mask: Option<&sk::ClipMask>, - text: &Text, + text: &TextItem, id: GlyphId, ) -> Option<()> { let ppem = text.size.to_f32() * ts.sy; @@ -326,13 +326,13 @@ fn convert_path(path: &geom::Path) -> Option<sk::Path> { let mut builder = sk::PathBuilder::new(); for elem in &path.0 { match elem { - PathElement::MoveTo(p) => { + PathItem::MoveTo(p) => { builder.move_to(p.x.to_f32(), p.y.to_f32()); } - PathElement::LineTo(p) => { + PathItem::LineTo(p) => { builder.line_to(p.x.to_f32(), p.y.to_f32()); } - PathElement::CubicTo(p1, p2, p3) => { + PathItem::CubicTo(p1, p2, p3) => { builder.cubic_to( p1.x.to_f32(), p1.y.to_f32(), @@ -342,7 +342,7 @@ fn convert_path(path: &geom::Path) -> Option<sk::Path> { p3.y.to_f32(), ); } - PathElement::ClosePath => { + PathItem::ClosePath => { builder.close(); } }; diff --git a/src/geom/paint.rs b/src/geom/paint.rs index c01b21da..eacd6f95 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -236,7 +236,7 @@ impl FromStr for RgbaColor { fn from_str(hex_str: &str) -> Result<Self, Self::Err> { let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str); if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err("string contains non-hexadecimal letters"); + return Err("color string contains non-hexadecimal letters"); } let len = hex_str.len(); @@ -244,7 +244,7 @@ impl FromStr for RgbaColor { let short = len == 3 || len == 4; let alpha = len == 4 || len == 8; if !long && !short { - return Err("string has wrong length"); + return Err("color string has wrong length"); } let mut values: [u8; 4] = [u8::MAX; 4]; @@ -406,10 +406,10 @@ mod tests { assert_eq!(RgbaColor::from_str(hex), Err(message)); } - test("a5", "string has wrong length"); - test("12345", "string has wrong length"); - test("f075ff011", "string has wrong length"); - test("hmmm", "string contains non-hexadecimal letters"); - test("14B2AH", "string contains non-hexadecimal letters"); + test("a5", "color string has wrong length"); + test("12345", "color string has wrong length"); + test("f075ff011", "color string has wrong length"); + test("hmmm", "color string contains non-hexadecimal letters"); + test("14B2AH", "color string contains non-hexadecimal letters"); } } diff --git a/src/geom/path.rs b/src/geom/path.rs index 3a7c3033..1c5325a3 100644 --- a/src/geom/path.rs +++ b/src/geom/path.rs @@ -2,11 +2,11 @@ use super::*; /// A bezier path. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct Path(pub Vec<PathElement>); +pub struct Path(pub Vec<PathItem>); -/// An element in a bezier path. +/// An item in a bezier path. #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum PathElement { +pub enum PathItem { MoveTo(Point), LineTo(Point), CubicTo(Point, Point, Point), @@ -32,23 +32,23 @@ impl Path { path } - /// Push a [`MoveTo`](PathElement::MoveTo) element. + /// Push a [`MoveTo`](PathItem::MoveTo) item. pub fn move_to(&mut self, p: Point) { - self.0.push(PathElement::MoveTo(p)); + self.0.push(PathItem::MoveTo(p)); } - /// Push a [`LineTo`](PathElement::LineTo) element. + /// Push a [`LineTo`](PathItem::LineTo) item. pub fn line_to(&mut self, p: Point) { - self.0.push(PathElement::LineTo(p)); + self.0.push(PathItem::LineTo(p)); } - /// Push a [`CubicTo`](PathElement::CubicTo) element. + /// Push a [`CubicTo`](PathItem::CubicTo) item. pub fn cubic_to(&mut self, p1: Point, p2: Point, p3: Point) { - self.0.push(PathElement::CubicTo(p1, p2, p3)); + self.0.push(PathItem::CubicTo(p1, p2, p3)); } - /// Push a [`ClosePath`](PathElement::ClosePath) element. + /// Push a [`ClosePath`](PathItem::ClosePath) item. pub fn close_path(&mut self) { - self.0.push(PathElement::ClosePath); + self.0.push(PathItem::ClosePath); } } diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index 68b82b05..27c6c2a4 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -81,6 +81,11 @@ pub fn analyze_import( } /// Find all labels and details for them. +/// +/// Returns: +/// - All labels and descriptions for them, if available +/// - A split offset: All labels before this offset belong to nodes, all after +/// belong to a bibliography. pub fn analyze_labels( world: &(dyn World + 'static), frames: &[Frame], @@ -90,16 +95,16 @@ pub fn analyze_labels( let items = &world.library().items; // Labels in the document. - for node in introspector.all() { - let Some(label) = node.label() else { continue }; - let details = node + for elem in introspector.all() { + let Some(label) = elem.label() else { continue }; + let details = elem .field("caption") - .or_else(|| node.field("body")) + .or_else(|| elem.field("body")) .and_then(|field| match field { Value::Content(content) => Some(content), _ => None, }) - .and_then(|content| (items.text_str)(content)); + .and_then(|content| (items.text_str)(&content)); output.push((label.clone(), details)); } diff --git a/src/ide/complete.rs b/src/ide/complete.rs index 4a1f0216..886c1245 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -373,7 +373,7 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { } Value::Content(content) => { for (name, value) in content.fields() { - ctx.value_completion(Some(name.clone()), value, false, None); + ctx.value_completion(Some(name.clone()), &value, false, None); } } Value::Dict(dict) => { @@ -509,7 +509,7 @@ fn set_rule_completions(ctx: &mut CompletionContext) { fn show_rule_selector_completions(ctx: &mut CompletionContext) { ctx.scope_completions( false, - |value| matches!(value, Value::Func(func) if func.select(None).is_ok()), + |value| matches!(value, Value::Func(func) if func.element().is_some()), ); ctx.enrich("", ": "); diff --git a/src/ide/jump.rs b/src/ide/jump.rs index 17e318a7..d123ac06 100644 --- a/src/ide/jump.rs +++ b/src/ide/jump.rs @@ -1,6 +1,8 @@ use std::num::NonZeroUsize; -use crate::doc::{Destination, Element, Frame, Location, Meta}; +use ecow::EcoString; + +use crate::doc::{Destination, Frame, FrameItem, Meta, Position}; use crate::geom::{Geometry, Point, Size}; use crate::model::Introspector; use crate::syntax::{LinkedNode, Source, SourceId, Span, SyntaxKind}; @@ -11,8 +13,10 @@ use crate::World; pub enum Jump { /// Jump to a position in a source file. Source(SourceId, usize), - /// Jump to position in the output or to an external URL. - Dest(Destination), + /// Jump to an external URL. + Url(EcoString), + /// Jump to a point on a page. + Position(Position), } impl Jump { @@ -32,20 +36,27 @@ pub fn jump_from_click( ) -> Option<Jump> { let mut introspector = None; - // Prefer metadata. - for (pos, element) in frame.elements() { - if let Element::Meta(Meta::Link(link), size) = element { + // Try to find a link first. + for (pos, item) in frame.items() { + if let FrameItem::Meta(Meta::Link(dest), size) = item { if is_in_rect(*pos, *size, click) { - return Some(Jump::Dest(link.resolve(|| { - introspector.get_or_insert_with(|| Introspector::new(frames)) - }))); + return Some(match dest { + Destination::Url(url) => Jump::Url(url.clone()), + Destination::Position(pos) => Jump::Position(*pos), + Destination::Location(loc) => Jump::Position( + introspector + .get_or_insert_with(|| Introspector::new(frames)) + .position(*loc), + ), + }); } } } - for (mut pos, element) in frame.elements().rev() { - match element { - Element::Group(group) => { + // If there's no link, search for a jump target. + for (mut pos, item) in frame.items().rev() { + match item { + FrameItem::Group(group) => { // TODO: Handle transformation. if let Some(span) = jump_from_click(world, frames, &group.frame, click - pos) @@ -54,7 +65,7 @@ pub fn jump_from_click( } } - Element::Text(text) => { + FrameItem::Text(text) => { for glyph in &text.glyphs { if glyph.span.is_detached() { continue; @@ -85,14 +96,14 @@ pub fn jump_from_click( } } - Element::Shape(shape, span) => { + FrameItem::Shape(shape, span) => { let Geometry::Rect(size) = shape.geometry else { continue }; if is_in_rect(pos, size, click) { return Some(Jump::from_span(world, *span)); } } - Element::Image(_, size, span) if is_in_rect(pos, *size, click) => { + FrameItem::Image(_, size, span) if is_in_rect(pos, *size, click) => { return Some(Jump::from_span(world, *span)); } @@ -108,7 +119,7 @@ pub fn jump_from_cursor( frames: &[Frame], source: &Source, cursor: usize, -) -> Option<Location> { +) -> Option<Position> { let node = LinkedNode::new(source.root()).leaf_at(cursor)?; if node.kind() != SyntaxKind::Text { return None; @@ -117,7 +128,10 @@ pub fn jump_from_cursor( let span = node.span(); for (i, frame) in frames.iter().enumerate() { if let Some(pos) = find_in_frame(frame, span) { - return Some(Location { page: NonZeroUsize::new(i + 1).unwrap(), pos }); + return Some(Position { + page: NonZeroUsize::new(i + 1).unwrap(), + point: pos, + }); } } @@ -126,15 +140,15 @@ pub fn jump_from_cursor( /// Find the position of a span in a frame. fn find_in_frame(frame: &Frame, span: Span) -> Option<Point> { - for (mut pos, element) in frame.elements() { - if let Element::Group(group) = element { + for (mut pos, item) in frame.items() { + if let FrameItem::Group(group) = item { // TODO: Handle transformation. if let Some(point) = find_in_frame(&group.frame, span) { return Some(point + pos); } } - if let Element::Text(text) = element { + if let FrameItem::Text(text) = item { for glyph in &text.glyphs { if glyph.span == span { return Some(pos); @@ -9,12 +9,12 @@ //! The next step is to [evaluate] the markup. This produces a [module], //! consisting of a scope of values that were exported by the code and //! [content], a hierarchical, styled representation of what was written in -//! the source file. The nodes of the content tree are well structured and +//! the source file. The elements of the content tree are well structured and //! order-independent and thus much better suited for further processing than //! the raw markup. //! - **Typesetting:** //! Next, the content is [typeset] into a [document] containing one [frame] -//! per page with elements and fixed positions. +//! per page with items at fixed positions. //! - **Exporting:** //! These frames can finally be exported into an output format (currently //! supported are [PDF] and [raster images]). diff --git a/src/model/content.rs b/src/model/content.rs index 5317236e..b47da62c 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -1,160 +1,144 @@ use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; -use std::hash::{Hash, Hasher}; -use std::iter::{self, Sum}; -use std::ops::{Add, AddAssign, Deref}; +use std::iter::Sum; +use std::ops::{Add, AddAssign}; use ecow::{eco_format, EcoString, EcoVec}; -use once_cell::sync::Lazy; use super::{ - node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap, - Synthesize, + element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable, + Location, Recipe, Style, Styles, Synthesize, }; use crate::diag::{SourceResult, StrResult}; use crate::doc::Meta; -use crate::eval::{ - cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm, -}; +use crate::eval::{Cast, Str, Value, Vm}; use crate::syntax::Span; use crate::util::pretty_array_like; /// Composable representation of styled content. #[derive(Clone, Hash)] pub struct Content { - id: NodeId, - span: Span, - fields: EcoVec<(EcoString, Value)>, - modifiers: EcoVec<Modifier>, + func: ElemFunc, + attrs: EcoVec<Attr>, } -/// Modifiers that can be attached to content. +/// Attributes that can be attached to content. #[derive(Debug, Clone, PartialEq, Hash)] -enum Modifier { +enum Attr { + Span(Span), + Field(EcoString), + Value(Value), + Child(Content), + Styles(Styles), Prepared, Guard(Guard), - Id(StableId), + Location(Location), } impl Content { - /// Create a content of the given node kind. - pub fn new(id: NodeId) -> Self { - Self { - id, - span: Span::detached(), - fields: EcoVec::new(), - modifiers: EcoVec::new(), - } + /// Create an empty element. + pub fn new(func: ElemFunc) -> Self { + Self { func, attrs: EcoVec::new() } } /// Create empty content. pub fn empty() -> Self { - SequenceNode::new(vec![]).pack() + Self::new(SequenceElem::func()) } - /// Create a new sequence node from multiples nodes. - pub fn sequence(seq: Vec<Self>) -> Self { - match seq.as_slice() { - [_] => seq.into_iter().next().unwrap(), - _ => SequenceNode::new(seq).pack(), - } + /// Create a new sequence element from multiples elements. + pub fn sequence(iter: impl IntoIterator<Item = Self>) -> Self { + let mut iter = iter.into_iter(); + let Some(first) = iter.next() else { return Self::empty() }; + let Some(second) = iter.next() else { return first }; + let mut content = Content::empty(); + content.attrs.push(Attr::Child(first)); + content.attrs.push(Attr::Child(second)); + content.attrs.extend(iter.map(Attr::Child)); + content } - /// The id of the contained node. - pub fn id(&self) -> NodeId { - self.id + /// The element function of the contained content. + pub fn func(&self) -> ElemFunc { + self.func } - /// Whether the content is empty. + /// Whether the content is an empty sequence. pub fn is_empty(&self) -> bool { - self.to::<SequenceNode>() - .map_or(false, |seq| seq.children().is_empty()) + self.is::<SequenceElem>() && self.attrs.is_empty() } - /// Whether the contained node is of type `T`. - pub fn is<T>(&self) -> bool - where - T: Node + 'static, - { - self.id == NodeId::of::<T>() + /// Whether the contained element is of type `T`. + pub fn is<T: Element>(&self) -> bool { + self.func == T::func() } - /// Cast to `T` if the contained node is of type `T`. - pub fn to<T>(&self) -> Option<&T> - where - T: Node + 'static, - { - self.is::<T>().then(|| unsafe { std::mem::transmute(self) }) + /// Cast to `T` if the contained element is of type `T`. + pub fn to<T: Element>(&self) -> Option<&T> { + T::unpack(self) } - /// Whether this content has the given capability. + /// Access the children if this is a sequence. + pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Self>> { + if !self.is::<SequenceElem>() { + return None; + } + Some(self.attrs.iter().filter_map(Attr::child)) + } + + /// Access the child and styles. + pub fn to_styled(&self) -> Option<(&Content, &Styles)> { + if !self.is::<StyledElem>() { + return None; + } + let child = self.attrs.iter().find_map(Attr::child)?; + let styles = self.attrs.iter().find_map(Attr::styles)?; + Some((child, styles)) + } + + /// Whether the contained element has the given capability. pub fn can<C>(&self) -> bool where C: ?Sized + 'static, { - (self.id.0.vtable)(TypeId::of::<C>()).is_some() + (self.func.0.vtable)(TypeId::of::<C>()).is_some() } - /// Cast to a trait object if this content has the given capability. + /// Cast to a trait object if the contained element has the given + /// capability. pub fn with<C>(&self) -> Option<&C> where C: ?Sized + 'static, { - let vtable = (self.id.0.vtable)(TypeId::of::<C>())?; + let vtable = (self.func.0.vtable)(TypeId::of::<C>())?; let data = self as *const Self as *const (); Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) }) } - /// Cast to a trait object if this content has the given capability. + /// Cast to a mutable trait object if the contained element has the given + /// capability. pub fn with_mut<C>(&mut self) -> Option<&mut C> where C: ?Sized + 'static, { - let vtable = (self.id.0.vtable)(TypeId::of::<C>())?; + let vtable = (self.func.0.vtable)(TypeId::of::<C>())?; let data = self as *mut Self as *mut (); Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) }) } - /// The node's span. + /// The content's span. pub fn span(&self) -> Span { - self.span + self.attrs.iter().find_map(Attr::span).unwrap_or(Span::detached()) } /// Attach a span to the content if it doesn't already have one. pub fn spanned(mut self, span: Span) -> Self { - if self.span.is_detached() { - self.span = span; + if self.span().is_detached() { + self.attrs.push(Attr::Span(span)); } self } - /// Access a field on the content. - pub fn field(&self, name: &str) -> Option<&Value> { - self.fields - .iter() - .find(|(field, _)| field == name) - .map(|(_, value)| value) - } - - /// Try to access a field on the content as a specified type. - pub fn cast_field<T: Cast>(&self, name: &str) -> Option<T> { - match self.field(name) { - Some(value) => value.clone().cast().ok(), - None => None, - } - } - - /// Expect a field on the content to exist as a specified type. - #[track_caller] - pub fn expect_field<T: Cast>(&self, name: &str) -> T { - self.field(name).unwrap().clone().cast().unwrap() - } - - /// List all fields on the content. - pub fn fields(&self) -> &[(EcoString, Value)] { - &self.fields - } - /// Attach a field to the content. pub fn with_field( mut self, @@ -168,26 +152,97 @@ impl Content { /// Attach a field to the content. pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { let name = name.into(); - if let Some(i) = self.fields.iter().position(|(field, _)| *field == name) { - self.fields.make_mut()[i] = (name, value.into()); + if let Some(i) = self.attrs.iter().position(|attr| match attr { + Attr::Field(field) => *field == name, + _ => false, + }) { + self.attrs.make_mut()[i + 1] = Attr::Value(value.into()); + } else { + self.attrs.push(Attr::Field(name)); + self.attrs.push(Attr::Value(value.into())); + } + } + + /// Access a field on the content. + pub fn field(&self, name: &str) -> Option<Value> { + if let Some(iter) = self.to_sequence() { + (name == "children") + .then(|| Value::Array(iter.cloned().map(Value::Content).collect())) + } else if let Some((child, _)) = self.to_styled() { + (name == "child").then(|| Value::Content(child.clone())) } else { - self.fields.push((name, value.into())); + self.field_ref(name).cloned() + } + } + + /// Access a field on the content by reference. + /// + /// Does not include synthesized fields for sequence and styled elements. + pub fn field_ref(&self, name: &str) -> Option<&Value> { + self.fields_ref() + .find(|&(field, _)| field == name) + .map(|(_, value)| value) + } + + /// Iter over all fields on the content. + /// + /// Does not include synthesized fields for sequence and styled elements. + pub fn fields(&self) -> impl Iterator<Item = (&EcoString, Value)> { + static CHILD: EcoString = EcoString::inline("child"); + static CHILDREN: EcoString = EcoString::inline("children"); + + let option = if let Some(iter) = self.to_sequence() { + Some((&CHILDREN, Value::Array(iter.cloned().map(Value::Content).collect()))) + } else if let Some((child, _)) = self.to_styled() { + Some((&CHILD, Value::Content(child.clone()))) + } else { + None + }; + + self.fields_ref() + .map(|(name, value)| (name, value.clone())) + .chain(option) + } + + /// Iter over all fields on the content. + /// + /// Does not include synthesized fields for sequence and styled elements. + pub fn fields_ref(&self) -> impl Iterator<Item = (&EcoString, &Value)> { + let mut iter = self.attrs.iter(); + std::iter::from_fn(move || { + let field = iter.find_map(Attr::field)?; + let value = iter.next()?.value()?; + Some((field, value)) + }) + } + + /// Try to access a field on the content as a specified type. + pub fn cast_field<T: Cast>(&self, name: &str) -> Option<T> { + match self.field(name) { + Some(value) => value.cast().ok(), + None => None, } } + /// Expect a field on the content to exist as a specified type. + #[track_caller] + pub fn expect_field<T: Cast>(&self, name: &str) -> T { + self.field(name).unwrap().cast().unwrap() + } + /// Whether the content has the specified field. pub fn has(&self, field: &str) -> bool { self.field(field).is_some() } /// Borrow the value of the given field. - pub fn at(&self, field: &str) -> StrResult<&Value> { + pub fn at(&self, field: &str) -> StrResult<Value> { self.field(field).ok_or_else(|| missing_field(field)) } /// The content's label. pub fn label(&self) -> Option<&Label> { - match self.field("label")? { + match self.field_ref("label")? { Value::Label(label) => Some(label), _ => None, } @@ -199,20 +254,33 @@ impl Content { } /// Style this content with a style entry. - pub fn styled(self, style: impl Into<Style>) -> Self { - self.styled_with_map(style.into().into()) + pub fn styled(mut self, style: impl Into<Style>) -> Self { + if self.is::<StyledElem>() { + let prev = + self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); + prev.apply_one(style.into()); + self + } else { + self.styled_with_map(style.into().into()) + } } /// Style this content with a full style map. - pub fn styled_with_map(self, styles: StyleMap) -> Self { + pub fn styled_with_map(mut self, styles: Styles) -> Self { if styles.is_empty() { + return self; + } + + if self.is::<StyledElem>() { + let prev = + self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap(); + prev.apply(styles); self - } else if let Some(styled) = self.to::<StyledNode>() { - let mut map = styled.styles(); - map.apply(styles); - StyledNode::new(map, styled.body()).pack() } else { - StyledNode::new(styles, self).pack() + let mut content = Content::new(StyledElem::func()); + content.attrs.push(Attr::Child(self)); + content.attrs.push(Attr::Styles(styles)); + content } } @@ -221,7 +289,7 @@ impl Content { if recipe.selector.is_none() { recipe.apply_vm(vm, self) } else { - Ok(self.styled(Style::Recipe(recipe))) + Ok(self.styled(recipe)) } } @@ -232,35 +300,34 @@ impl Content { Ok(Self::sequence(vec![self.clone(); count])) } -} -#[doc(hidden)] -impl Content { /// Disable a show rule recipe. - pub fn guarded(mut self, id: Guard) -> Self { - self.modifiers.push(Modifier::Guard(id)); + pub fn guarded(mut self, guard: Guard) -> Self { + self.attrs.push(Attr::Guard(guard)); self } - /// Whether no show rule was executed for this node so far. - pub(super) fn is_pristine(&self) -> bool { - !self - .modifiers - .iter() - .any(|modifier| matches!(modifier, Modifier::Guard(_))) + /// Check whether a show rule recipe is disabled. + pub fn is_guarded(&self, guard: Guard) -> bool { + self.attrs.contains(&Attr::Guard(guard)) } - /// Check whether a show rule recipe is disabled. - pub(super) fn is_guarded(&self, id: Guard) -> bool { - self.modifiers.contains(&Modifier::Guard(id)) + /// Whether no show rule was executed for this content so far. + pub fn is_pristine(&self) -> bool { + !self.attrs.iter().any(|modifier| matches!(modifier, Attr::Guard(_))) } - /// Whether this node was prepared. + /// Whether this content has already been prepared. pub fn is_prepared(&self) -> bool { - self.modifiers.contains(&Modifier::Prepared) + self.attrs.contains(&Attr::Prepared) } - /// Whether the node needs to be realized specially. + /// Mark this content as prepared. + pub fn mark_prepared(&mut self) { + self.attrs.push(Attr::Prepared); + } + + /// Whether the content needs to be realized specially. pub fn needs_preparation(&self) -> bool { (self.can::<dyn Locatable>() || self.can::<dyn Synthesize>() @@ -268,37 +335,23 @@ impl Content { && !self.is_prepared() } - /// Mark this content as prepared. - pub fn mark_prepared(&mut self) { - self.modifiers.push(Modifier::Prepared); - } - - /// Attach a stable id to this content. - pub fn set_stable_id(&mut self, id: StableId) { - self.modifiers.push(Modifier::Id(id)); - } - - /// This content's stable identifier. - pub fn stable_id(&self) -> Option<StableId> { - self.modifiers.iter().find_map(|modifier| match modifier { - Modifier::Id(id) => Some(*id), + /// This content's location in the document flow. + pub fn location(&self) -> Option<Location> { + self.attrs.iter().find_map(|modifier| match modifier { + Attr::Location(location) => Some(*location), _ => None, }) } - /// Copy the modifiers from another piece of content. - pub(super) fn copy_modifiers(&mut self, from: &Content) { - self.span = from.span; - self.modifiers = from.modifiers.clone(); - if let Some(label) = from.label() { - self.push_field("label", label.clone()) - } + /// Attach a location to this content. + pub fn set_location(&mut self, location: Location) { + self.attrs.push(Attr::Location(location)); } } impl Debug for Content { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let name = self.id.name; + let name = self.func.name(); if let Some(text) = item!(text_str)(self) { f.write_char('[')?; f.write_str(&text)?; @@ -308,12 +361,15 @@ impl Debug for Content { return f.write_str("[ ]"); } - let pieces: Vec<_> = self - .fields - .iter() + let mut pieces: Vec<_> = self + .fields() .map(|(name, value)| eco_format!("{name}: {value:?}")) .collect(); + if self.is::<StyledElem>() { + pieces.push(EcoString::from("..")); + } + f.write_str(name)?; f.write_str(&pretty_array_like(&pieces, false)) } @@ -327,31 +383,36 @@ impl Default for Content { impl PartialEq for Content { fn eq(&self, other: &Self) -> bool { - self.id == other.id - && self.fields.len() == other.fields.len() - && self - .fields - .iter() - .all(|(name, value)| other.field(name) == Some(value)) + if let (Some(left), Some(right)) = (self.to_sequence(), other.to_sequence()) { + left.eq(right) + } else if let (Some(left), Some(right)) = (self.to_styled(), other.to_styled()) { + left == right + } else { + self.func == other.func && self.fields_ref().eq(other.fields_ref()) + } } } impl Add for Content { type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - let lhs = self; - let seq = match (lhs.to::<SequenceNode>(), rhs.to::<SequenceNode>()) { - (Some(lhs), Some(rhs)) => { - lhs.children().into_iter().chain(rhs.children()).collect() + fn add(self, mut rhs: Self) -> Self::Output { + let mut lhs = self; + match (lhs.is::<SequenceElem>(), rhs.is::<SequenceElem>()) { + (true, true) => { + lhs.attrs.extend(rhs.attrs); + lhs } - (Some(lhs), None) => { - lhs.children().into_iter().chain(iter::once(rhs)).collect() + (true, false) => { + lhs.attrs.push(Attr::Child(rhs)); + lhs } - (None, Some(rhs)) => iter::once(lhs).chain(rhs.children()).collect(), - (None, None) => vec![lhs, rhs], - }; - SequenceNode::new(seq).pack() + (false, true) => { + rhs.attrs.insert(0, Attr::Child(lhs)); + rhs + } + (false, false) => Self::sequence([lhs, rhs]), + } } } @@ -363,154 +424,77 @@ impl AddAssign for Content { impl Sum for Content { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - Self::sequence(iter.collect()) + Self::sequence(iter) } } -/// A constructable, stylable content node. -pub trait Node: Construct + Set + Sized + 'static { - /// The node's ID. - fn id() -> NodeId; - - /// Pack a node into type-erased content. - fn pack(self) -> Content; -} - -/// A unique identifier for a node. -#[derive(Copy, Clone)] -pub struct NodeId(pub &'static NodeMeta); - -impl NodeId { - /// Get the id of a node. - pub fn of<T: Node>() -> Self { - T::id() +impl Attr { + fn child(&self) -> Option<&Content> { + match self { + Self::Child(child) => Some(child), + _ => None, + } } -} -impl Debug for NodeId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(self.name) + fn styles(&self) -> Option<&Styles> { + match self { + Self::Styles(styles) => Some(styles), + _ => None, + } } -} -impl Hash for NodeId { - fn hash<H: Hasher>(&self, state: &mut H) { - state.write_usize(self.0 as *const _ as usize); + fn styles_mut(&mut self) -> Option<&mut Styles> { + match self { + Self::Styles(styles) => Some(styles), + _ => None, + } } -} - -impl Eq for NodeId {} -impl PartialEq for NodeId { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self.0, other.0) + fn field(&self) -> Option<&EcoString> { + match self { + Self::Field(field) => Some(field), + _ => None, + } } -} - -impl Deref for NodeId { - type Target = NodeMeta; - fn deref(&self) -> &Self::Target { - self.0 + fn value(&self) -> Option<&Value> { + match self { + Self::Value(value) => Some(value), + _ => None, + } } -} - -cast_from_value! { - NodeId, - v: Func => v.id().ok_or("this function is not an element")? -} - -cast_to_value! { - v: NodeId => Value::Func(v.into()) -} - -/// Static node for a node. -pub struct NodeMeta { - /// The node's name. - pub name: &'static str, - /// The node's vtable for caspability dispatch. - pub vtable: fn(of: TypeId) -> Option<*const ()>, - /// The node's constructor. - pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>, - /// The node's set rule. - pub set: fn(&mut Args) -> SourceResult<StyleMap>, - /// Details about the function. - pub info: Lazy<FuncInfo>, -} - -/// A node's constructor function. -pub trait Construct { - /// Construct a node from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// node's set rule. - fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>; -} - -/// A node's set rule. -pub trait Set { - /// Parse relevant arguments into style properties for this node. - fn set(args: &mut Args) -> SourceResult<StyleMap>; -} - -/// Indicates that a node cannot be labelled. -pub trait Unlabellable {} -/// A label for a node. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Label(pub EcoString); - -impl Debug for Label { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "<{}>", self.0) + fn span(&self) -> Option<Span> { + match self { + Self::Span(span) => Some(*span), + _ => None, + } } } -/// A sequence of nodes. -/// -/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in -/// Typst, the two text nodes are combined into a single sequence node. -/// /// Display: Sequence /// Category: special -#[node] -pub struct SequenceNode { - #[variadic] - pub children: Vec<Content>, -} +#[element] +struct SequenceElem {} -/// A node with applied styles. -/// -/// Display: Styled +/// Display: Sequence /// Category: special -#[node] -pub struct StyledNode { - /// The styles. - #[required] - pub styles: StyleMap, - - /// The styled content. - #[required] - pub body: Content, -} - -cast_from_value! { - StyleMap: "style map", -} +#[element] +struct StyledElem {} -/// Host for metadata. +/// Hosts metadata and ensures metadata is produced even for empty elements. /// /// Display: Meta /// Category: special -#[node(Behave)] -pub struct MetaNode { +#[element(Behave)] +pub struct MetaElem { /// Metadata that should be attached to all elements affected by this style /// property. #[fold] pub data: Vec<Meta>, } -impl Behave for MetaNode { +impl Behave for MetaElem { fn behaviour(&self) -> Behaviour { Behaviour::Ignorant } diff --git a/src/model/element.rs b/src/model/element.rs new file mode 100644 index 00000000..e25b22b4 --- /dev/null +++ b/src/model/element.rs @@ -0,0 +1,145 @@ +use std::any::TypeId; +use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; + +use ecow::EcoString; +use once_cell::sync::Lazy; + +use super::{Content, Selector, Styles}; +use crate::diag::SourceResult; +use crate::eval::{ + cast_from_value, cast_to_value, Args, Dict, Func, FuncInfo, Value, Vm, +}; + +/// A document element. +pub trait Element: Construct + Set + Sized + 'static { + /// Pack the element into type-erased content. + fn pack(self) -> Content; + + /// Extract this element from type-erased content. + fn unpack(content: &Content) -> Option<&Self>; + + /// The element's function. + fn func() -> ElemFunc; +} + +/// An element's constructor function. +pub trait Construct { + /// Construct an element from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// element's set rule. + fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>; +} + +/// An element's set rule. +pub trait Set { + /// Parse relevant arguments into style properties for this element. + fn set(args: &mut Args) -> SourceResult<Styles>; +} + +/// An element's function. +#[derive(Copy, Clone)] +pub struct ElemFunc(pub(super) &'static NativeElemFunc); + +impl ElemFunc { + /// The function's name. + pub fn name(self) -> &'static str { + self.0.name + } + + /// Apply the given arguments to the function. + pub fn with(self, args: Args) -> Func { + Func::from(self).with(args) + } + + /// Extract details about the function. + pub fn info(&self) -> &'static FuncInfo { + &self.0.info + } + + /// Construct an element. + pub fn construct(self, vm: &mut Vm, args: &mut Args) -> SourceResult<Content> { + (self.0.construct)(vm, args) + } + + /// Create a selector for elements of this function. + pub fn select(self) -> Selector { + Selector::Elem(self, None) + } + + /// Create a selector for elements of this function, filtering for those + /// whose [fields](super::Content::field) match the given arguments. + pub fn where_(self, fields: Dict) -> Selector { + Selector::Elem(self, Some(fields)) + } + + /// Execute the set rule for the element and return the resulting style map. + pub fn set(self, mut args: Args) -> SourceResult<Styles> { + let styles = (self.0.set)(&mut args)?; + args.finish()?; + Ok(styles) + } +} + +impl Debug for ElemFunc { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self.name()) + } +} + +impl Hash for ElemFunc { + fn hash<H: Hasher>(&self, state: &mut H) { + state.write_usize(self.0 as *const _ as usize); + } +} + +impl Eq for ElemFunc {} + +impl PartialEq for ElemFunc { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.0, other.0) + } +} + +cast_from_value! { + ElemFunc, + v: Func => v.element().ok_or("expected element function")?, +} + +cast_to_value! { + v: ElemFunc => Value::Func(v.into()) +} + +impl From<&'static NativeElemFunc> for ElemFunc { + fn from(native: &'static NativeElemFunc) -> Self { + Self(native) + } +} + +/// An element function backed by a Rust type. +pub struct NativeElemFunc { + /// The element's name. + pub name: &'static str, + /// The element's vtable for capability dispatch. + pub vtable: fn(of: TypeId) -> Option<*const ()>, + /// The element's constructor. + pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>, + /// The element's set rule. + pub set: fn(&mut Args) -> SourceResult<Styles>, + /// Details about the function. + pub info: Lazy<FuncInfo>, +} + +/// A label for an element. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Label(pub EcoString); + +impl Debug for Label { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "<{}>", self.0) + } +} + +/// Indicates that an element cannot be labelled. +pub trait Unlabellable {} diff --git a/src/model/introspect.rs b/src/model/introspect.rs new file mode 100644 index 00000000..35f0e628 --- /dev/null +++ b/src/model/introspect.rs @@ -0,0 +1,170 @@ +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; +use std::num::NonZeroUsize; + +use super::{Content, Selector}; +use crate::doc::{Frame, FrameItem, Meta, Position}; +use crate::eval::cast_from_value; +use crate::geom::{Point, Transform}; +use crate::util::NonZeroExt; + +/// Stably identifies a location in the document across multiple layout passes. +/// +/// This struct is created by [`StabilityProvider::locate`]. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct Location(u128, usize, usize); + +impl Location { + /// Produce a variant of this location. + pub fn variant(self, n: usize) -> Self { + Self(self.0, self.1, n) + } +} + +impl Debug for Location { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("..") + } +} + +cast_from_value! { + Location: "location", +} + +/// Provides stable identities to elements. +#[derive(Clone)] +pub struct StabilityProvider { + hashes: Vec<u128>, + checkpoints: Vec<usize>, +} + +impl StabilityProvider { + /// Create a new stability provider. + pub fn new() -> Self { + Self { hashes: vec![], checkpoints: vec![] } + } +} + +#[comemo::track] +impl StabilityProvider { + /// Produce a stable identifier for this call site. + pub fn locate(&mut self, hash: u128) -> Location { + let count = self.hashes.iter().filter(|&&prev| prev == hash).count(); + self.hashes.push(hash); + Location(hash, count, 0) + } + + /// Create a checkpoint of the state that can be restored. + pub fn save(&mut self) { + self.checkpoints.push(self.hashes.len()); + } + + /// Restore the last checkpoint. + pub fn restore(&mut self) { + if let Some(checkpoint) = self.checkpoints.pop() { + self.hashes.truncate(checkpoint); + } + } +} + +/// Can be queried for elements and their positions. +pub struct Introspector { + pages: usize, + elems: Vec<(Content, Position)>, +} + +impl Introspector { + /// Create a new introspector. + pub fn new(frames: &[Frame]) -> Self { + let mut introspector = Self { pages: frames.len(), elems: vec![] }; + for (i, frame) in frames.iter().enumerate() { + let page = NonZeroUsize::new(1 + i).unwrap(); + introspector.extract(frame, page, Transform::identity()); + } + introspector + } + + /// Iterate over all elements. + pub fn all(&self) -> impl Iterator<Item = &Content> { + self.elems.iter().map(|(elem, _)| elem) + } + + /// Extract metadata from a frame. + fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { + for (pos, item) in frame.items() { + match item { + FrameItem::Group(group) => { + let ts = ts + .pre_concat(Transform::translate(pos.x, pos.y)) + .pre_concat(group.transform); + self.extract(&group.frame, page, ts); + } + FrameItem::Meta(Meta::Elem(content), _) + if !self + .elems + .iter() + .any(|(prev, _)| prev.location() == content.location()) => + { + let pos = pos.transform(ts); + self.elems.push((content.clone(), Position { page, point: pos })); + } + _ => {} + } + } + } +} + +#[comemo::track] +impl Introspector { + /// Whether this introspector is not yet initialized. + pub fn init(&self) -> bool { + self.pages > 0 + } + + /// Query for all matching elements. + pub fn query(&self, selector: Selector) -> Vec<Content> { + self.all().filter(|elem| selector.matches(elem)).cloned().collect() + } + + /// Query for all matching element up to the given location. + pub fn query_before(&self, selector: Selector, location: Location) -> Vec<Content> { + let mut matches = vec![]; + for elem in self.all() { + if selector.matches(elem) { + matches.push(elem.clone()); + } + if elem.location() == Some(location) { + break; + } + } + matches + } + + /// Query for all matching elements starting from the given location. + pub fn query_after(&self, selector: Selector, location: Location) -> Vec<Content> { + self.all() + .skip_while(|elem| elem.location() != Some(location)) + .filter(|elem| selector.matches(elem)) + .cloned() + .collect() + } + + /// The total number pages. + pub fn pages(&self) -> NonZeroUsize { + NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE) + } + + /// Find the page number for the given location. + pub fn page(&self, location: Location) -> NonZeroUsize { + self.position(location).page + } + + /// Find the position for the given location. + pub fn position(&self, location: Location) -> Position { + self.elems + .iter() + .find(|(elem, _)| elem.location() == Some(location)) + .map(|(_, loc)| *loc) + .unwrap_or(Position { page: NonZeroUsize::ONE, point: Point::zero() }) + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 6015c365..7458dc3c 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,14 +1,87 @@ //! The document model. -#[macro_use] -mod styles; mod content; +mod element; +mod introspect; mod realize; -mod typeset; +mod styles; pub use self::content::*; +pub use self::element::*; +pub use self::introspect::*; pub use self::realize::*; pub use self::styles::*; -pub use self::typeset::*; -pub use typst_macros::node; +pub use typst_macros::element; + +use comemo::{Constraint, Track, Tracked, TrackedMut}; + +use crate::diag::SourceResult; +use crate::doc::Document; +use crate::eval::Tracer; +use crate::World; + +/// Typeset content into a fully layouted document. +#[comemo::memoize] +pub fn typeset( + world: Tracked<dyn World>, + mut tracer: TrackedMut<Tracer>, + content: &Content, +) -> SourceResult<Document> { + let library = world.library(); + let styles = StyleChain::new(&library.styles); + + let mut document; + let mut iter = 0; + let mut introspector = Introspector::new(&[]); + + // Relayout until all introspections stabilize. + // If that doesn't happen within five attempts, we give up. + loop { + let constraint = Constraint::new(); + let mut provider = StabilityProvider::new(); + let mut vt = Vt { + world, + tracer: TrackedMut::reborrow_mut(&mut tracer), + provider: provider.track_mut(), + introspector: introspector.track_with(&constraint), + }; + + document = (library.items.layout)(&mut vt, content, styles)?; + iter += 1; + + introspector = Introspector::new(&document.pages); + + if iter >= 5 || introspector.valid(&constraint) { + break; + } + } + + Ok(document) +} + +/// A virtual typesetter. +/// +/// Holds the state needed to [typeset] content. +pub struct Vt<'a> { + /// The compilation environment. + pub world: Tracked<'a, dyn World>, + /// The tracer for inspection of the values an expression produces. + pub tracer: TrackedMut<'a, Tracer>, + /// Provides stable identities to elements. + pub provider: TrackedMut<'a, StabilityProvider>, + /// Provides access to information about the document. + pub introspector: Tracked<'a, Introspector>, +} + +impl Vt<'_> { + /// Mutably reborrow with a shorter lifetime. + pub fn reborrow_mut(&mut self) -> Vt<'_> { + Vt { + world: self.world, + tracer: TrackedMut::reborrow_mut(&mut self.tracer), + provider: TrackedMut::reborrow_mut(&mut self.provider), + introspector: self.introspector, + } + } +} diff --git a/src/model/realize.rs b/src/model/realize.rs index 634a31fd..51d69fdc 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,4 +1,4 @@ -use super::{Content, MetaNode, Node, NodeId, Recipe, Selector, StyleChain, Vt}; +use super::{Content, ElemFunc, Element, MetaElem, Recipe, Selector, StyleChain, Vt}; use crate::diag::SourceResult; use crate::doc::Meta; use crate::util::hash128; @@ -35,28 +35,28 @@ pub fn realize( ) -> SourceResult<Option<Content>> { // Pre-process. if target.needs_preparation() { - let mut node = target.clone(); + let mut elem = target.clone(); if target.can::<dyn Locatable>() || target.label().is_some() { - let id = vt.provider.identify(hash128(target)); - node.set_stable_id(id); + let location = vt.provider.locate(hash128(target)); + elem.set_location(location); } - if let Some(node) = node.with_mut::<dyn Synthesize>() { - node.synthesize(vt, styles); + if let Some(elem) = elem.with_mut::<dyn Synthesize>() { + elem.synthesize(vt, styles); } - node.mark_prepared(); + elem.mark_prepared(); - if node.stable_id().is_some() { - let span = node.span(); - let meta = Meta::Node(node.clone()); + if elem.location().is_some() { + let span = elem.span(); + let meta = Meta::Elem(elem.clone()); return Ok(Some( - (node + MetaNode::new().pack().spanned(span)) - .styled(MetaNode::set_data(vec![meta])), + (elem + MetaElem::new().pack().spanned(span)) + .styled(MetaElem::set_data(vec![meta])), )); } - return Ok(Some(node)); + return Ok(Some(elem)); } // Find out how many recipes there are. @@ -77,17 +77,17 @@ pub fn realize( // Realize if there was no matching recipe. if let Some(showable) = target.with::<dyn Show>() { - let guard = Guard::Base(target.id()); + let guard = Guard::Base(target.func()); if realized.is_none() && !target.is_guarded(guard) { realized = Some(showable.show(vt, styles)?); } } - // Finalize only if this is the first application for this node. - if let Some(node) = target.with::<dyn Finalize>() { + // Finalize only if this is the first application for this element. + if let Some(elem) = target.with::<dyn Finalize>() { if target.is_pristine() { if let Some(already) = realized { - realized = Some(node.finalize(already, styles)); + realized = Some(elem.finalize(already, styles)); } } } @@ -103,8 +103,8 @@ fn try_apply( guard: Guard, ) -> SourceResult<Option<Content>> { match &recipe.selector { - Some(Selector::Node(id, _)) => { - if target.id() != *id { + Some(Selector::Elem(element, _)) => { + if target.func() != *element { return Ok(None); } @@ -124,22 +124,17 @@ fn try_apply( return Ok(None); }; - let make = |s| { - let mut content = item!(text)(s); - content.copy_modifiers(target); - content - }; - + let make = |s: &str| target.clone().with_field("text", s); let mut result = vec![]; let mut cursor = 0; for m in regex.find_iter(&text) { let start = m.start(); if cursor < start { - result.push(make(text[cursor..start].into())); + result.push(make(&text[cursor..start])); } - let piece = make(m.as_str().into()).guarded(guard); + let piece = make(m.as_str()).guarded(guard); let transformed = recipe.apply_vt(vt, piece)?; result.push(transformed); cursor = m.end(); @@ -150,7 +145,7 @@ fn try_apply( } if cursor < text.len() { - result.push(make(text[cursor..].into())); + result.push(make(&text[cursor..])); } Ok(Some(Content::sequence(result))) @@ -163,55 +158,56 @@ fn try_apply( } } -/// Makes this node locatable through `vt.locate`. +/// Makes this element locatable through `vt.locate`. pub trait Locatable {} -/// Synthesize fields on a node. This happens before execution of any show rule. +/// Synthesize fields on an element. This happens before execution of any show +/// rule. pub trait Synthesize { - /// Prepare the node for show rule application. + /// Prepare the element for show rule application. fn synthesize(&mut self, vt: &Vt, styles: StyleChain); } -/// The base recipe for a node. +/// The base recipe for an element. pub trait Show { - /// Execute the base recipe for this node. + /// Execute the base recipe for this element. fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>; } -/// Post-process a node after it was realized. +/// Post-process an element after it was realized. pub trait Finalize { - /// Finalize the fully realized form of the node. Use this for effects that + /// Finalize the fully realized form of the element. Use this for effects that /// should work even in the face of a user-defined show rule, for example - /// the linking behaviour of a link node. + /// the linking behaviour of a link element. fn finalize(&self, realized: Content, styles: StyleChain) -> Content; } -/// How a node interacts with other nodes. +/// How the element interacts with other elements. pub trait Behave { - /// The node's interaction behaviour. + /// The element's interaction behaviour. fn behaviour(&self) -> Behaviour; - /// Whether this weak node is larger than a previous one and thus picked as - /// the maximum when the levels are the same. + /// Whether this weak element is larger than a previous one and thus picked + /// as the maximum when the levels are the same. #[allow(unused_variables)] fn larger(&self, prev: &Content) -> bool { false } } -/// How a node interacts with other nodes in a stream. +/// How an element interacts with other elements in a stream. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Behaviour { - /// A weak node which only survives when a supportive node is before and - /// after it. Furthermore, per consecutive run of weak nodes, only one - /// survives: The one with the lowest weakness level (or the larger one if - /// there is a tie). + /// A weak element which only survives when a supportive element is before + /// and after it. Furthermore, per consecutive run of weak elements, only + /// one survives: The one with the lowest weakness level (or the larger one + /// if there is a tie). Weak(usize), - /// A node that enables adjacent weak nodes to exist. The default. + /// An element that enables adjacent weak elements to exist. The default. Supportive, - /// A node that destroys adjacent weak nodes. + /// An element that destroys adjacent weak elements. Destructive, - /// A node that does not interact at all with other nodes, having the + /// An element that does not interact at all with other elements, having the /// same effect as if it didn't exist. Ignorant, } @@ -221,6 +217,6 @@ pub enum Behaviour { pub enum Guard { /// The nth recipe from the top of the chain. Nth(usize), - /// The [base recipe](Show) for a kind of node. - Base(NodeId), + /// The [base recipe](Show) for a kind of element. + Base(ElemFunc), } diff --git a/src/model/styles.rs b/src/model/styles.rs index b7d09774..db2b2053 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -1,25 +1,26 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::iter; +use std::mem; -use ecow::{eco_format, EcoString, EcoVec}; +use ecow::{eco_format, eco_vec, EcoString, EcoVec}; -use super::{Content, Label, Node, NodeId, Vt}; +use super::{Content, ElemFunc, Element, Label, Vt}; use crate::diag::{SourceResult, Trace, Tracepoint}; use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value, Vm}; use crate::syntax::Span; use crate::util::pretty_array_like; -/// A map of style properties. -#[derive(Default, Clone, Hash)] -pub struct StyleMap(Vec<Style>); +/// A list of style properties. +#[derive(Default, PartialEq, Clone, Hash)] +pub struct Styles(EcoVec<Style>); -impl StyleMap { - /// Create a new, empty style map. +impl Styles { + /// Create a new, empty style list. pub fn new() -> Self { Self::default() } - /// Whether this map contains no styles. + /// Whether this contains no styles. pub fn is_empty(&self) -> bool { self.0.is_empty() } @@ -39,13 +40,25 @@ impl StyleMap { } /// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place. - pub fn apply(&mut self, outer: Self) { - self.0.splice(0..0, outer.0.iter().cloned()); + pub fn apply(&mut self, mut outer: Self) { + outer.0.extend(mem::take(self).0.into_iter()); + *self = outer; + } + + /// Apply one outer styles. Like [`chain_one`](StyleChain::chain_one), but + /// in-place. + pub fn apply_one(&mut self, outer: Style) { + self.0.insert(0, outer); + } + + /// Apply a slice of outer styles. + pub fn apply_slice(&mut self, outer: &[Style]) { + self.0 = outer.iter().cloned().chain(mem::take(self).0.into_iter()).collect(); } /// Add an origin span to all contained properties. pub fn spanned(mut self, span: Span) -> Self { - for entry in &mut self.0 { + for entry in self.0.make_mut() { if let Style::Property(property) = entry { property.span = Some(span); } @@ -53,37 +66,31 @@ impl StyleMap { self } - /// Returns `Some(_)` with an optional span if this map contains styles for - /// the given `node`. - pub fn interruption<T: Node>(&self) -> Option<Option<Span>> { - let node = NodeId::of::<T>(); + /// Returns `Some(_)` with an optional span if this list contains + /// styles for the given element. + pub fn interruption<T: Element>(&self) -> Option<Option<Span>> { + let func = T::func(); self.0.iter().find_map(|entry| match entry { - Style::Property(property) => property.is_of(node).then(|| property.span), - Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.span)), + Style::Property(property) => property.is_of(func).then(|| property.span), + Style::Recipe(recipe) => recipe.is_of(func).then(|| Some(recipe.span)), }) } } -impl From<Style> for StyleMap { +impl From<Style> for Styles { fn from(entry: Style) -> Self { - Self(vec![entry]) - } -} - -impl PartialEq for StyleMap { - fn eq(&self, other: &Self) -> bool { - crate::util::hash128(self) == crate::util::hash128(other) + Self(eco_vec![entry]) } } -impl Debug for StyleMap { +impl Debug for Styles { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad("..") } } /// A single style property or recipe. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum Style { /// A style property originating from a set rule or constructor. Property(Property), @@ -124,11 +131,17 @@ impl From<Property> for Style { } } +impl From<Recipe> for Style { + fn from(recipe: Recipe) -> Self { + Self::Recipe(recipe) + } +} + /// A style property originating from a set rule or constructor. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub struct Property { - /// The id of the node the property belongs to. - node: NodeId, + /// The element the property belongs to. + element: ElemFunc, /// The property's name. name: EcoString, /// The property's value. @@ -139,44 +152,44 @@ pub struct Property { impl Property { /// Create a new property from a key-value pair. - pub fn new(node: NodeId, name: EcoString, value: Value) -> Self { - Self { node, name, value, span: None } + pub fn new(element: ElemFunc, name: EcoString, value: Value) -> Self { + Self { element, name, value, span: None } } /// Whether this property is the given one. - pub fn is(&self, node: NodeId, name: &str) -> bool { - self.node == node && self.name == name + pub fn is(&self, element: ElemFunc, name: &str) -> bool { + self.element == element && self.name == name } - /// Whether this property belongs to the node with the given id. - pub fn is_of(&self, node: NodeId) -> bool { - self.node == node + /// Whether this property belongs to the given element. + pub fn is_of(&self, element: ElemFunc) -> bool { + self.element == element } } impl Debug for Property { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "set {}({}: {:?})", self.node.name, self.name, self.value)?; + write!(f, "set {}({}: {:?})", self.element.name(), self.name, self.value)?; Ok(()) } } /// A show rule recipe. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub struct Recipe { /// The span errors are reported with. pub span: Span, - /// Determines whether the recipe applies to a node. + /// Determines whether the recipe applies to an element. pub selector: Option<Selector>, /// The transformation to perform on the match. pub transform: Transform, } impl Recipe { - /// Whether this recipe is for the given node. - pub fn is_of(&self, node: NodeId) -> bool { + /// Whether this recipe is for the given type of element. + pub fn is_of(&self, element: ElemFunc) -> bool { match self.selector { - Some(Selector::Node(id, _)) => id == node, + Some(Selector::Elem(own, _)) => own == element, _ => false, } } @@ -197,7 +210,7 @@ impl Recipe { let mut result = func.call_vm(vm, args); // For selector-less show rules, a tracepoint makes no sense. if self.selector.is_some() { - let point = || Tracepoint::Show(content.id().name.into()); + let point = || Tracepoint::Show(content.func().name().into()); result = result.trace(vm.world(), point, content.span()); } Ok(result?.display()) @@ -213,7 +226,7 @@ impl Recipe { Transform::Func(func) => { let mut result = func.call_vt(vt, [Value::Content(content.clone())]); if self.selector.is_some() { - let point = || Tracepoint::Show(content.id().name.into()); + let point = || Tracepoint::Show(content.func().name().into()); result = result.trace(vt.world, point, content.span()); } Ok(result?.display()) @@ -238,25 +251,20 @@ impl Debug for Recipe { /// A selector in a show rule. #[derive(Clone, PartialEq, Hash)] pub enum Selector { - /// Matches a specific type of node. + /// Matches a specific type of element. /// - /// If there is a dictionary, only nodes with the fields from the + /// If there is a dictionary, only elements with the fields from the /// dictionary match. - Node(NodeId, Option<Dict>), - /// Matches nodes with a specific label. + Elem(ElemFunc, Option<Dict>), + /// Matches elements with a specific label. Label(Label), - /// Matches text nodes through a regular expression. + /// Matches text elements through a regular expression. Regex(Regex), /// Matches if any of the subselectors match. Any(EcoVec<Self>), } impl Selector { - /// Define a simple node selector. - pub fn node<T: Node>() -> Self { - Self::Node(NodeId::of::<T>(), None) - } - /// Define a simple text selector. pub fn text(text: &str) -> Self { Self::Regex(Regex::new(®ex::escape(text)).unwrap()) @@ -265,16 +273,16 @@ impl Selector { /// Whether the selector matches for the target. pub fn matches(&self, target: &Content) -> bool { match self { - Self::Node(id, dict) => { - target.id() == *id + Self::Elem(element, dict) => { + target.func() == *element && dict .iter() .flat_map(|dict| dict.iter()) - .all(|(name, value)| target.field(name) == Some(value)) + .all(|(name, value)| target.field_ref(name) == Some(value)) } Self::Label(label) => target.label() == Some(label), Self::Regex(regex) => { - target.id() == item!(text_id) + target.func() == item!(text_func) && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) } Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)), @@ -285,8 +293,8 @@ impl Selector { impl Debug for Selector { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - Self::Node(node, dict) => { - f.write_str(node.name)?; + Self::Elem(elem, dict) => { + f.write_str(elem.name())?; if let Some(dict) = dict { f.write_str(".where")?; dict.fmt(f)?; @@ -307,21 +315,24 @@ impl Debug for Selector { cast_from_value! { Selector: "selector", - text: EcoString => Self::text(&text), + func: Func => func + .element() + .ok_or("only element functions can be used as selectors")? + .select(), label: Label => Self::Label(label), - func: Func => func.select(None)?, + text: EcoString => Self::text(&text), regex: Regex => Self::Regex(regex), } /// A show rule transformation that can be applied to a match. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum Transform { /// Replacement content. Content(Content), /// A function to apply to the match. Func(Func), /// Apply styles to the content. - Style(StyleMap), + Style(Styles), } impl Debug for Transform { @@ -340,11 +351,11 @@ cast_from_value! { func: Func => Self::Func(func), } -/// A chain of style maps, similar to a linked list. +/// A chain of styles, similar to a linked list. /// -/// A style chain allows to combine properties from multiple style maps in a -/// node hierarchy in a non-allocating way. Rather than eagerly merging the -/// maps, each access walks the hierarchy from the innermost to the outermost +/// A style chain allows to combine properties from multiple style lists in a +/// element hierarchy in a non-allocating way. Rather than eagerly merging the +/// lists, each access walks the hierarchy from the innermost to the outermost /// map, trying to find a match and then folding it with matches further up the /// chain. #[derive(Default, Clone, Copy, Hash)] @@ -356,21 +367,21 @@ pub struct StyleChain<'a> { } impl<'a> StyleChain<'a> { - /// Start a new style chain with a root map. - pub fn new(root: &'a StyleMap) -> Self { + /// Start a new style chain with root styles. + pub fn new(root: &'a Styles) -> Self { Self { head: &root.0, tail: None } } - /// Make the given map the first link of this chain. + /// Make the given style list the first link of this chain. /// - /// The resulting style chain contains styles from `map` as well as - /// `self`. The ones from `map` take precedence over the ones from - /// `self`. For folded properties `map` contributes the inner value. - pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> { - if map.is_empty() { + /// The resulting style chain contains styles from `local` as well as + /// `self`. The ones from `local` take precedence over the ones from + /// `self`. For folded properties `local` contributes the inner value. + pub fn chain<'b>(&'b self, local: &'b Styles) -> StyleChain<'b> { + if local.is_empty() { *self } else { - StyleChain { head: &map.0, tail: Some(self) } + StyleChain { head: &local.0, tail: Some(self) } } } @@ -385,12 +396,12 @@ impl<'a> StyleChain<'a> { /// Cast the first value for the given property in the chain. pub fn get<T: Cast>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> T, ) -> T { - self.properties::<T>(node, name, inherent) + self.properties::<T>(func, name, inherent) .next() .unwrap_or_else(default) } @@ -398,18 +409,18 @@ impl<'a> StyleChain<'a> { /// Cast the first value for the given property in the chain. pub fn get_resolve<T: Cast + Resolve>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> T, ) -> T::Output { - self.get(node, name, inherent, default).resolve(self) + self.get(func, name, inherent, default).resolve(self) } /// Cast the first value for the given property in the chain. pub fn get_fold<T: Cast + Fold>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> T::Output, @@ -424,13 +435,13 @@ impl<'a> StyleChain<'a> { .map(|value| value.fold(next(values, styles, default))) .unwrap_or_else(|| default()) } - next(self.properties::<T>(node, name, inherent), self, &default) + next(self.properties::<T>(func, name, inherent), self, &default) } /// Cast the first value for the given property in the chain. pub fn get_resolve_fold<T>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, default: impl Fn() -> <T::Output as Fold>::Output, @@ -453,7 +464,7 @@ impl<'a> StyleChain<'a> { .map(|value| value.resolve(styles).fold(next(values, styles, default))) .unwrap_or_else(|| default()) } - next(self.properties::<T>(node, name, inherent), self, &default) + next(self.properties::<T>(func, name, inherent), self, &default) } /// Iterate over all style recipes in the chain. @@ -464,7 +475,7 @@ impl<'a> StyleChain<'a> { /// Iterate over all values for the given property in the chain. pub fn properties<T: Cast + 'a>( self, - node: NodeId, + func: ElemFunc, name: &'a str, inherent: Option<Value>, ) -> impl Iterator<Item = T> + '_ { @@ -473,21 +484,21 @@ impl<'a> StyleChain<'a> { .chain( self.entries() .filter_map(Style::property) - .filter(move |property| property.is(node, name)) + .filter(move |property| property.is(func, name)) .map(|property| property.value.clone()), ) .map(move |value| { - value - .cast() - .unwrap_or_else(|err| panic!("{} (for {}.{})", err, node.name, name)) + value.cast().unwrap_or_else(|err| { + panic!("{} (for {}.{})", err, func.name(), name) + }) }) } /// Convert to a style map. - pub fn to_map(self) -> StyleMap { - let mut suffix = StyleMap::new(); + pub fn to_map(self) -> Styles { + let mut suffix = Styles::new(); for link in self.links() { - suffix.0.splice(0..0, link.iter().cloned()); + suffix.apply_slice(link); } suffix } @@ -502,13 +513,13 @@ impl<'a> StyleChain<'a> { Links(Some(self)) } - /// Build a style map from the suffix (all links beyond the `len`) of the + /// Build owned styles from the suffix (all links beyond the `len`) of the /// chain. - fn suffix(self, len: usize) -> StyleMap { - let mut suffix = StyleMap::new(); + fn suffix(self, len: usize) -> Styles { + let mut suffix = Styles::new(); let take = self.links().count().saturating_sub(len); for link in self.links().take(take) { - suffix.0.splice(0..0, link.iter().cloned()); + suffix.apply_slice(link); } suffix } @@ -517,6 +528,16 @@ impl<'a> StyleChain<'a> { fn pop(&mut self) { *self = self.tail.copied().unwrap_or_default(); } + + /// Whether two style chains contain the same pointers. + fn ptr_eq(self, other: Self) -> bool { + std::ptr::eq(self.head, other.head) + && match (self.tail, other.tail) { + (Some(a), Some(b)) => std::ptr::eq(a, b), + (None, None) => true, + _ => false, + } + } } impl Debug for StyleChain<'_> { @@ -530,7 +551,7 @@ impl Debug for StyleChain<'_> { impl PartialEq for StyleChain<'_> { fn eq(&self, other: &Self) -> bool { - crate::util::hash128(self) == crate::util::hash128(other) + self.ptr_eq(*other) || crate::util::hash128(self) == crate::util::hash128(other) } } @@ -574,7 +595,7 @@ impl<'a> Iterator for Links<'a> { #[derive(Clone, Hash)] pub struct StyleVec<T> { items: Vec<T>, - maps: Vec<(StyleMap, usize)>, + styles: Vec<(Styles, usize)>, } impl<T> StyleVec<T> { @@ -588,14 +609,14 @@ impl<T> StyleVec<T> { self.items.len() } - /// Insert an element in the front. The element will share the style of the - /// current first element. + /// Insert an item in the front. The item will share the style of the + /// current first item. /// /// This method has no effect if the vector is empty. pub fn push_front(&mut self, item: T) { - if !self.maps.is_empty() { + if !self.styles.is_empty() { self.items.insert(0, item); - self.maps[0].1 += 1; + self.styles[0].1 += 1; } } @@ -606,14 +627,14 @@ impl<T> StyleVec<T> { { StyleVec { items: self.items.iter().map(f).collect(), - maps: self.maps.clone(), + styles: self.styles.clone(), } } - /// Iterate over references to the contained items and associated style maps. - pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ { + /// Iterate over references to the contained items and associated styles. + pub fn iter(&self) -> impl Iterator<Item = (&T, &Styles)> + '_ { self.items().zip( - self.maps + self.styles .iter() .flat_map(|(map, count)| iter::repeat(map).take(*count)), ) @@ -624,13 +645,13 @@ impl<T> StyleVec<T> { self.items.iter() } - /// Iterate over the contained maps. Note that zipping this with `items()` - /// does not yield the same result as calling `iter()` because this method - /// only returns maps once that are shared by consecutive items. This method - /// is designed for use cases where you want to check, for example, whether - /// any of the maps fulfills a specific property. - pub fn styles(&self) -> impl Iterator<Item = &StyleMap> { - self.maps.iter().map(|(map, _)| map) + /// Iterate over the contained style lists. Note that zipping this with + /// `items()` does not yield the same result as calling `iter()` because + /// this method only returns lists once that are shared by consecutive + /// items. This method is designed for use cases where you want to check, + /// for example, whether any of the lists fulfills a specific property. + pub fn styles(&self) -> impl Iterator<Item = &Styles> { + self.styles.iter().map(|(map, _)| map) } } @@ -639,35 +660,35 @@ impl StyleVec<Content> { self.items .into_iter() .zip( - self.maps + self.styles .iter() .flat_map(|(map, count)| iter::repeat(map).take(*count)), ) - .map(|(content, map)| content.styled_with_map(map.clone())) + .map(|(content, styles)| content.styled_with_map(styles.clone())) .collect() } } impl<T> Default for StyleVec<T> { fn default() -> Self { - Self { items: vec![], maps: vec![] } + Self { items: vec![], styles: vec![] } } } impl<T> FromIterator<T> for StyleVec<T> { fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { let items: Vec<_> = iter.into_iter().collect(); - let maps = vec![(StyleMap::new(), items.len())]; - Self { items, maps } + let styles = vec![(Styles::new(), items.len())]; + Self { items, styles } } } impl<T: Debug> Debug for StyleVec<T> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_list() - .entries(self.iter().map(|(item, map)| { + .entries(self.iter().map(|(item, styles)| { crate::util::debug(|f| { - map.fmt(f)?; + styles.fmt(f)?; item.fmt(f) }) })) @@ -708,7 +729,7 @@ impl<'a, T> StyleVecBuilder<'a, T> { } /// Iterate over the contained items. - pub fn items(&self) -> std::slice::Iter<'_, T> { + pub fn elems(&self) -> std::slice::Iter<'_, T> { self.items.iter() } @@ -743,13 +764,13 @@ impl<'a, T> StyleVecBuilder<'a, T> { } } - let maps = self + let styles = self .chains .into_iter() .map(|(chain, count)| (chain.suffix(shared), count)) .collect(); - (StyleVec { items: self.items, maps }, trunk) + (StyleVec { items: self.items, styles }, trunk) } } diff --git a/src/model/typeset.rs b/src/model/typeset.rs deleted file mode 100644 index 8216d7a8..00000000 --- a/src/model/typeset.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::fmt::{self, Debug, Formatter}; -use std::hash::Hash; -use std::num::NonZeroUsize; - -use comemo::{Constraint, Track, Tracked, TrackedMut}; - -use super::{Content, Selector, StyleChain}; -use crate::diag::SourceResult; -use crate::doc::{Document, Element, Frame, Location, Meta}; -use crate::eval::{cast_from_value, Tracer}; -use crate::geom::{Point, Transform}; -use crate::util::NonZeroExt; -use crate::World; - -/// Typeset content into a fully layouted document. -#[comemo::memoize] -pub fn typeset( - world: Tracked<dyn World>, - mut tracer: TrackedMut<Tracer>, - content: &Content, -) -> SourceResult<Document> { - let library = world.library(); - let styles = StyleChain::new(&library.styles); - - let mut document; - let mut iter = 0; - let mut introspector = Introspector::new(&[]); - - // Relayout until all introspections stabilize. - // If that doesn't happen within five attempts, we give up. - loop { - let constraint = Constraint::new(); - let mut provider = StabilityProvider::new(); - let mut vt = Vt { - world, - tracer: TrackedMut::reborrow_mut(&mut tracer), - provider: provider.track_mut(), - introspector: introspector.track_with(&constraint), - }; - - document = (library.items.layout)(&mut vt, content, styles)?; - iter += 1; - - introspector = Introspector::new(&document.pages); - introspector.init = true; - - if iter >= 5 || introspector.valid(&constraint) { - break; - } - } - - Ok(document) -} - -/// A virtual typesetter. -/// -/// Holds the state needed to [typeset] content. -pub struct Vt<'a> { - /// The compilation environment. - pub world: Tracked<'a, dyn World>, - /// The tracer for inspection of the values an expression produces. - pub tracer: TrackedMut<'a, Tracer>, - /// Provides stable identities to nodes. - pub provider: TrackedMut<'a, StabilityProvider>, - /// Provides access to information about the document. - pub introspector: Tracked<'a, Introspector>, -} - -impl Vt<'_> { - /// Mutably reborrow with a shorter lifetime. - pub fn reborrow_mut(&mut self) -> Vt<'_> { - Vt { - world: self.world, - tracer: TrackedMut::reborrow_mut(&mut self.tracer), - provider: TrackedMut::reborrow_mut(&mut self.provider), - introspector: self.introspector, - } - } -} - -/// Provides stable identities to nodes. -#[derive(Clone)] -pub struct StabilityProvider { - hashes: Vec<u128>, - checkpoints: Vec<usize>, -} - -impl StabilityProvider { - /// Create a new stability provider. - pub fn new() -> Self { - Self { hashes: vec![], checkpoints: vec![] } - } -} - -#[comemo::track] -impl StabilityProvider { - /// Produce a stable identifier for this call site. - pub fn identify(&mut self, hash: u128) -> StableId { - let count = self.hashes.iter().filter(|&&prev| prev == hash).count(); - self.hashes.push(hash); - StableId(hash, count, 0) - } - - /// Create a checkpoint of the state that can be restored. - pub fn save(&mut self) { - self.checkpoints.push(self.hashes.len()); - } - - /// Restore the last checkpoint. - pub fn restore(&mut self) { - if let Some(checkpoint) = self.checkpoints.pop() { - self.hashes.truncate(checkpoint); - } - } -} - -/// Stably identifies a call site across multiple layout passes. -/// -/// This struct is created by [`StabilityProvider::identify`]. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct StableId(u128, usize, usize); - -impl StableId { - /// Produce a variant of this id. - pub fn variant(self, n: usize) -> Self { - Self(self.0, self.1, n) - } -} - -impl Debug for StableId { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("..") - } -} - -cast_from_value! { - StableId: "stable id", -} - -/// Provides access to information about the document. -pub struct Introspector { - init: bool, - pages: usize, - nodes: Vec<(Content, Location)>, -} - -impl Introspector { - /// Create a new introspector. - pub fn new(frames: &[Frame]) -> Self { - let mut introspector = Self { init: false, pages: frames.len(), nodes: vec![] }; - for (i, frame) in frames.iter().enumerate() { - let page = NonZeroUsize::new(1 + i).unwrap(); - introspector.extract(frame, page, Transform::identity()); - } - introspector - } - - /// Iterate over all nodes. - pub fn all(&self) -> impl Iterator<Item = &Content> { - self.nodes.iter().map(|(node, _)| node) - } - - /// Extract metadata from a frame. - fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { - for (pos, element) in frame.elements() { - match element { - Element::Group(group) => { - let ts = ts - .pre_concat(Transform::translate(pos.x, pos.y)) - .pre_concat(group.transform); - self.extract(&group.frame, page, ts); - } - Element::Meta(Meta::Node(content), _) - if !self - .nodes - .iter() - .any(|(prev, _)| prev.stable_id() == content.stable_id()) => - { - let pos = pos.transform(ts); - self.nodes.push((content.clone(), Location { page, pos })); - } - _ => {} - } - } - } -} - -#[comemo::track] -impl Introspector { - /// Whether this introspector is not yet initialized. - pub fn init(&self) -> bool { - self.init - } - - /// Query for all nodes for the given selector. - pub fn query(&self, selector: Selector) -> Vec<Content> { - self.all().filter(|node| selector.matches(node)).cloned().collect() - } - - /// Query for all nodes up to the given id. - pub fn query_before(&self, selector: Selector, id: StableId) -> Vec<Content> { - let mut matches = vec![]; - for node in self.all() { - if selector.matches(node) { - matches.push(node.clone()); - } - if node.stable_id() == Some(id) { - break; - } - } - matches - } - - /// Query for all nodes starting from the given id. - pub fn query_after(&self, selector: Selector, id: StableId) -> Vec<Content> { - self.all() - .skip_while(|node| node.stable_id() != Some(id)) - .filter(|node| selector.matches(node)) - .cloned() - .collect() - } - - /// The total number pages. - pub fn pages(&self) -> NonZeroUsize { - NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE) - } - - /// Find the page number for the given stable id. - pub fn page(&self, id: StableId) -> NonZeroUsize { - self.location(id).page - } - - /// Find the location for the given stable id. - pub fn location(&self, id: StableId) -> Location { - self.nodes - .iter() - .find(|(node, _)| node.stable_id() == Some(id)) - .map(|(_, loc)| *loc) - .unwrap_or(Location { page: NonZeroUsize::ONE, pos: Point::zero() }) - } -} diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 869d77af..c96539d1 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -4,11 +4,7 @@ #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[repr(u8)] pub enum SyntaxKind { - /// Markup of which all lines must have a minimal indentation. - /// - /// Notably, the number does not determine in which column the markup - /// started, but to the right of which column all markup elements must be, - /// so it is zero except inside indent-aware constructs like lists. + /// Markup. Markup, /// Plain text without markup. Text, diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index 8e27d98d..3fea3fe1 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -473,7 +473,7 @@ impl Lexer<'_> { c if is_id_start(c) => self.ident(start), - _ => self.error("not valid here"), + _ => self.error("this character is not valid in code"), } } @@ -634,7 +634,7 @@ fn count_newlines(text: &str) -> usize { newlines } -/// Whether a string is a valid unicode identifier. +/// Whether a string is a valid Typst identifier. /// /// In addition to what is specified in the [Unicode Standard][uax31], we allow: /// - `_` as a starting character, @@ -651,13 +651,13 @@ pub fn is_ident(string: &str) -> bool { /// Whether a character can start an identifier. #[inline] -pub fn is_id_start(c: char) -> bool { +pub(crate) fn is_id_start(c: char) -> bool { c.is_xid_start() || c == '_' } /// Whether a character can continue an identifier. #[inline] -pub fn is_id_continue(c: char) -> bool { +pub(crate) fn is_id_continue(c: char) -> bool { c.is_xid_continue() || c == '_' || c == '-' } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index ae12e818..c27547c4 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -14,6 +14,5 @@ pub use self::kind::*; pub use self::lexer::*; pub use self::node::*; pub use self::parser::*; -pub use self::reparser::*; pub use self::source::*; pub use self::span::*; diff --git a/src/syntax/source.rs b/src/syntax/source.rs index 607a2603..052e841a 100644 --- a/src/syntax/source.rs +++ b/src/syntax/source.rs @@ -9,7 +9,8 @@ use comemo::Prehashed; use unscanny::Scanner; use super::ast::Markup; -use super::{is_newline, parse, reparse, LinkedNode, Span, SyntaxNode}; +use super::reparser::reparse; +use super::{is_newline, parse, LinkedNode, Span, SyntaxNode}; use crate::diag::SourceResult; use crate::util::{PathExt, StrExt}; diff --git a/src/util/mod.rs b/src/util/mod.rs index 596282de..3e0e7aa2 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -40,7 +40,7 @@ pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 { state.finish128().as_u128() } -/// Extra methods for [`NonZeroUsize`]. +/// An extra constant for [`NonZeroUsize`]. pub trait NonZeroExt { /// The number `1`. const ONE: Self; @@ -210,7 +210,13 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str buf.push('('); if list.contains('\n') { buf.push('\n'); - buf.push_str(&indent(&list, 2)); + for (i, line) in list.lines().enumerate() { + if i > 0 { + buf.push('\n'); + } + buf.push_str(" "); + buf.push_str(line); + } buf.push('\n'); } else { buf.push_str(&list); @@ -218,18 +224,3 @@ pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> Str buf.push(')'); buf } - -/// Indent a string by two spaces. -pub fn indent(text: &str, amount: usize) -> String { - let mut buf = String::new(); - for (i, line) in text.lines().enumerate() { - if i > 0 { - buf.push('\n'); - } - for _ in 0..amount { - buf.push(' '); - } - buf.push_str(line); - } - buf -} |
