summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-19 22:28:49 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-19 22:39:19 +0100
commitab43bd802eafe33977a91893907e67553e099569 (patch)
treeaf4dead92b143348f52e2e8f869df3f7dfd7322a /src
parentd6aaae0cea1e79eecd85dc94ab85b9ad8eff48e8 (diff)
Renaming and refactoring
Diffstat (limited to 'src')
-rw-r--r--src/doc.rs242
-rw-r--r--src/eval/array.rs14
-rw-r--r--src/eval/func.rs69
-rw-r--r--src/eval/library.rs26
-rw-r--r--src/eval/methods.rs31
-rw-r--r--src/eval/mod.rs42
-rw-r--r--src/eval/scope.rs4
-rw-r--r--src/eval/symbol.rs126
-rw-r--r--src/eval/value.rs9
-rw-r--r--src/export/pdf/page.rs71
-rw-r--r--src/export/render.rs40
-rw-r--r--src/geom/paint.rs14
-rw-r--r--src/geom/path.rs22
-rw-r--r--src/ide/analyze.rs15
-rw-r--r--src/ide/complete.rs4
-rw-r--r--src/ide/jump.rs54
-rw-r--r--src/lib.rs4
-rw-r--r--src/model/content.rs538
-rw-r--r--src/model/element.rs145
-rw-r--r--src/model/introspect.rs170
-rw-r--r--src/model/mod.rs83
-rw-r--r--src/model/realize.rs96
-rw-r--r--src/model/styles.rs271
-rw-r--r--src/model/typeset.rs241
-rw-r--r--src/syntax/kind.rs6
-rw-r--r--src/syntax/lexer.rs8
-rw-r--r--src/syntax/mod.rs1
-rw-r--r--src/syntax/source.rs3
-rw-r--r--src/util/mod.rs25
29 files changed, 1261 insertions, 1113 deletions
diff --git a/src/doc.rs b/src/doc.rs
index f575ff1f..ebdca43e 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -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);
diff --git a/src/lib.rs b/src/lib.rs
index 9dbaf721..392bd7ac 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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(&regex::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
-}