From 4ec3bcee487c1567bc6551f81d4f69eee4379076 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 19 Sep 2022 11:14:58 +0200 Subject: [PATCH] Remove pins and memoization --- src/eval/cast.rs | 6 +- src/eval/methods.rs | 17 -- src/eval/mod.rs | 18 +- src/export/render.rs | 12 +- src/font.rs | 4 - src/lib.rs | 27 +- src/library/layout/grid.rs | 4 - src/library/layout/locate.rs | 14 -- src/library/layout/mod.rs | 2 - src/library/mod.rs | 2 - src/memo.rs | 203 --------------- src/model/content.rs | 38 +-- src/model/layout.rs | 48 +--- src/model/locate.rs | 397 ------------------------------ src/model/mod.rs | 2 - src/model/styles.rs | 2 - tests/typ/layout/locate-break.typ | 5 - tests/typ/layout/locate-group.typ | 89 ------- tests/typ/layout/locate.typ | 22 -- 19 files changed, 18 insertions(+), 894 deletions(-) delete mode 100644 src/library/layout/locate.rs delete mode 100644 src/memo.rs delete mode 100644 src/model/locate.rs delete mode 100644 tests/typ/layout/locate-break.typ delete mode 100644 tests/typ/layout/locate-group.typ delete mode 100644 tests/typ/layout/locate.typ diff --git a/src/eval/cast.rs b/src/eval/cast.rs index b27e3edc1..99c348102 100644 --- a/src/eval/cast.rs +++ b/src/eval/cast.rs @@ -3,7 +3,7 @@ use std::num::NonZeroUsize; use super::{Regex, Value}; use crate::diag::{with_alternative, StrResult}; use crate::geom::{Corners, Dir, Paint, Sides}; -use crate::model::{Content, Group, Layout, LayoutNode, Pattern}; +use crate::model::{Content, Layout, LayoutNode, Pattern}; use crate::syntax::Spanned; use crate::util::EcoString; @@ -128,10 +128,6 @@ dynamic! { Regex: "regular expression", } -dynamic! { - Group: "group", -} - castable! { usize, Expected: "non-negative integer", diff --git a/src/eval/methods.rs b/src/eval/methods.rs index aeb84c5a4..5072e6882 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -2,7 +2,6 @@ use super::{Args, Machine, Value}; use crate::diag::{At, TypResult}; -use crate::model::{Content, Group}; use crate::syntax::Span; use crate::util::EcoString; @@ -109,22 +108,6 @@ pub fn call( _ => return missing(), }, - Value::Dyn(dynamic) => { - if let Some(group) = dynamic.downcast::() { - match method { - "entry" => Value::Content(Content::Locate( - group.entry(args.expect("recipe")?, args.named("value")?), - )), - "all" => { - Value::Content(Content::Locate(group.all(args.expect("recipe")?))) - } - _ => return missing(), - } - } else { - return missing(); - } - } - _ => return missing(), }; diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 7f182f48e..2ab94785b 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -62,15 +62,6 @@ pub fn evaluate( panic!("Tried to cyclicly evaluate {}", path); } - // Check whether the module was already evaluated. - if let Some(module) = ctx.modules.get(&id) { - if module.valid(&ctx.sources) { - return Ok(module.clone()); - } else { - ctx.modules.remove(&id); - } - } - route.push(id); // Parse the file. @@ -91,16 +82,11 @@ pub fn evaluate( } // Assemble the module. - let module = Module { + Ok(Module { scope: vm.scopes.top, content: result?, deps: vm.deps, - }; - - // Save the evaluated module. - ctx.modules.insert(id, module.clone()); - - Ok(module) + }) } /// An evaluated module, ready for importing or layouting. diff --git a/src/export/render.rs b/src/export/render.rs index 4933edf0b..afa6d3da5 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -246,16 +246,8 @@ fn render_outline_glyph( // Rasterize the glyph with `pixglyph`. // Try to retrieve a prepared glyph or prepare it from scratch if it // doesn't exist, yet. - let bitmap = crate::memo::memoized_ref( - (&ctx.fonts, text.face_id, id), - |(fonts, face_id, id)| { - ( - pixglyph::Glyph::load(fonts.get(face_id).ttf(), id), - ((), (), ()), - ) - }, - |glyph| glyph.as_ref().map(|g| g.rasterize(ts.tx, ts.ty, ppem)), - )?; + let glyph = pixglyph::Glyph::load(ctx.fonts.get(text.face_id).ttf(), id)?; + let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem); let cw = canvas.width() as i32; let ch = canvas.height() as i32; diff --git a/src/font.rs b/src/font.rs index cb7117703..fd9799438 100644 --- a/src/font.rs +++ b/src/font.rs @@ -234,10 +234,6 @@ fn shared_prefix_words(left: &str, right: &str) -> usize { .count() } -impl_track_empty!(FontStore); -impl_track_hash!(FaceId); -impl_track_hash!(GlyphId); - /// A font face. pub struct Face { /// The raw face data, possibly shared with other faces from the same diff --git a/src/lib.rs b/src/lib.rs index 72c7fb51e..5338816df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,8 +34,6 @@ #[macro_use] pub mod util; #[macro_use] -pub mod memo; -#[macro_use] pub mod geom; #[macro_use] pub mod diag; @@ -52,19 +50,16 @@ pub mod parse; pub mod source; pub mod syntax; -use std::collections::HashMap; -use std::hash::Hasher; use std::path::PathBuf; use std::sync::Arc; use crate::diag::TypResult; -use crate::eval::{Module, Scope}; +use crate::eval::Scope; use crate::font::FontStore; use crate::frame::Frame; use crate::image::ImageStore; use crate::loading::Loader; -use crate::memo::Track; -use crate::model::{PinBoard, PinConstraint, StyleMap}; +use crate::model::StyleMap; use crate::source::{SourceId, SourceStore}; /// Typeset a source file into a collection of layouted frames. @@ -89,10 +84,6 @@ pub struct Context { pub images: ImageStore, /// The context's configuration. config: Config, - /// Stores evaluated modules. - modules: HashMap, - /// Stores document pins. - pins: PinBoard, } impl Context { @@ -104,24 +95,10 @@ impl Context { fonts: FontStore::new(Arc::clone(&loader)), images: ImageStore::new(loader), config, - modules: HashMap::new(), - pins: PinBoard::new(), } } } -impl Track for &mut Context { - type Constraint = PinConstraint; - - fn key(&self, hasher: &mut H) { - self.pins.key(hasher); - } - - fn matches(&self, constraint: &Self::Constraint) -> bool { - self.pins.matches(constraint) - } -} - /// Compilation configuration. pub struct Config { /// The compilation root. diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 5e86f3d0b..93dbf97e6 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -204,9 +204,7 @@ impl<'a> GridLayouter<'a> { /// Determines the columns sizes and then layouts the grid row-by-row. pub fn layout(mut self) -> TypResult> { - self.ctx.pins.freeze(); self.measure_columns()?; - self.ctx.pins.unfreeze(); for y in 0 .. self.rows.len() { // Skip to next region if current one is full, but only for content @@ -372,12 +370,10 @@ impl<'a> GridLayouter<'a> { pod.base.x = self.regions.base.x; } - self.ctx.pins.freeze(); let mut sizes = node .layout(self.ctx, &pod, self.styles)? .into_iter() .map(|frame| frame.height()); - self.ctx.pins.unfreeze(); // For each region, we want to know the maximum height any // column requires. diff --git a/src/library/layout/locate.rs b/src/library/layout/locate.rs deleted file mode 100644 index 74480b915..000000000 --- a/src/library/layout/locate.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::library::prelude::*; -use crate::model::{Group, LocateNode}; - -/// Format content with access to its location on the page. -pub fn locate(_: &mut Machine, args: &mut Args) -> TypResult { - let node = LocateNode::single(args.expect("recipe")?); - Ok(Value::Content(Content::Locate(node))) -} - -/// Create a new group of locatable elements. -pub fn group(_: &mut Machine, args: &mut Args) -> TypResult { - let key = args.expect("key")?; - Ok(Value::dynamic(Group::new(key))) -} diff --git a/src/library/layout/mod.rs b/src/library/layout/mod.rs index 6cf5b550c..588b15aa1 100644 --- a/src/library/layout/mod.rs +++ b/src/library/layout/mod.rs @@ -5,7 +5,6 @@ mod columns; mod container; mod flow; mod grid; -mod locate; mod pad; mod page; mod place; @@ -17,7 +16,6 @@ pub use columns::*; pub use container::*; pub use flow::*; pub use grid::*; -pub use locate::*; pub use pad::*; pub use page::*; pub use place::*; diff --git a/src/library/mod.rs b/src/library/mod.rs index c1a645fb3..566a4f26d 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -57,8 +57,6 @@ pub fn new() -> Scope { std.def_node::("columns"); std.def_node::("colbreak"); std.def_node::("place"); - std.def_fn("locate", layout::locate); - std.def_fn("group", layout::group); // Graphics. std.def_node::("image"); diff --git a/src/memo.rs b/src/memo.rs deleted file mode 100644 index 2c1903d9c..000000000 --- a/src/memo.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Function memoization. - -use std::any::Any; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt::{self, Display, Formatter}; -use std::hash::Hasher; - -thread_local! { - /// The thread-local cache. - static CACHE: RefCell = RefCell::default(); -} - -/// A map from hashes to cache entries. -type Cache = HashMap; - -/// Access the cache mutably. -fn with(f: F) -> R -where - F: FnOnce(&mut Cache) -> R, -{ - CACHE.with(|cell| f(&mut cell.borrow_mut())) -} - -/// An entry in the cache. -struct CacheEntry { - /// The memoized function's result plus constraints on the input in the form - /// `(O, I::Contrast)`. - data: Box, - /// How many evictions have passed since the entry has been last used. - age: usize, -} - -/// Execute a memoized function call. -/// -/// This [tracks](Track) all inputs to the function and then either returns a -/// cached version from the thread-local cache or executes the function and -/// saves a copy of the results in the cache. -/// -/// Note that `f` must be a pure function. -pub fn memoized(input: I, f: fn(input: I) -> (O, I::Constraint)) -> O -where - I: Track, - O: Clone + 'static, -{ - memoized_ref(input, f, Clone::clone) -} - -/// Execute a function and then call another function with a reference to the -/// result. -/// -/// This [tracks](Track) all inputs to the function and then either -/// - calls `g` with a cached version from the thread-local cache, -/// - or executes `f`, calls `g` with the fresh version and saves the result in -/// the cache. -/// -/// Note that `f` must be a pure function, while `g` does not need to be pure. -pub fn memoized_ref( - input: I, - f: fn(input: I) -> (O, I::Constraint), - g: G, -) -> R -where - I: Track, - O: 'static, - G: Fn(&O) -> R, -{ - let mut state = fxhash::FxHasher64::default(); - input.key(&mut state); - - let key = state.finish(); - let result = with(|cache| { - let entry = cache.get_mut(&key)?; - entry.age = 0; - entry - .data - .downcast_ref::<(O, I::Constraint)>() - .filter(|(_, constraint)| input.matches(constraint)) - .map(|(output, _)| g(output)) - }); - - result.unwrap_or_else(|| { - let output = f(input); - let result = g(&output.0); - let entry = CacheEntry { - data: Box::new(output) as Box<(O, I::Constraint)> as Box, - age: 0, - }; - with(|cache| cache.insert(key, entry)); - result - }) -} - -/// Garbage-collect the thread-local cache. -/// -/// This deletes elements which haven't been used in a while and returns details -/// about the eviction. -pub fn evict() -> Eviction { - with(|cache| { - const MAX_AGE: usize = 5; - - let before = cache.len(); - cache.retain(|_, entry| { - entry.age += 1; - entry.age <= MAX_AGE - }); - - Eviction { before, after: cache.len() } - }) -} - -/// Details about a cache eviction. -pub struct Eviction { - /// The number of items in the cache before the eviction. - pub before: usize, - /// The number of items in the cache after the eviction. - pub after: usize, -} - -impl Display for Eviction { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - writeln!(f, "Before: {}", self.before)?; - writeln!(f, "Evicted: {}", self.before - self.after)?; - writeln!(f, "After: {}", self.after) - } -} - -/// Tracks input dependencies of a memoized function. -pub trait Track { - /// The type of constraint generated by this input. - type Constraint: 'static; - - /// Feed the key portion of the input into a hasher. - fn key(&self, hasher: &mut H); - - /// Whether this instance matches the given constraint. - fn matches(&self, constraint: &Self::Constraint) -> bool; -} - -impl Track for &T { - type Constraint = T::Constraint; - - fn key(&self, hasher: &mut H) { - Track::key(*self, hasher) - } - - fn matches(&self, constraint: &Self::Constraint) -> bool { - Track::matches(*self, constraint) - } -} - -macro_rules! impl_track_empty { - ($ty:ty) => { - impl $crate::memo::Track for $ty { - type Constraint = (); - - fn key(&self, _: &mut H) {} - - fn matches(&self, _: &Self::Constraint) -> bool { - true - } - } - }; -} - -macro_rules! impl_track_hash { - ($ty:ty) => { - impl $crate::memo::Track for $ty { - type Constraint = (); - - fn key(&self, hasher: &mut H) { - std::hash::Hash::hash(self, hasher) - } - - fn matches(&self, _: &Self::Constraint) -> bool { - true - } - } - }; -} - -macro_rules! impl_track_tuple { - ($($idx:tt: $field:ident),*) => { - #[allow(unused_variables)] - impl<$($field: Track),*> Track for ($($field,)*) { - type Constraint = ($($field::Constraint,)*); - - fn key(&self, hasher: &mut H) { - $(self.$idx.key(hasher);)* - } - - fn matches(&self, constraint: &Self::Constraint) -> bool { - true $(&& self.$idx.matches(&constraint.$idx))* - } - } - }; -} - -impl_track_tuple! {} -impl_track_tuple! { 0: A } -impl_track_tuple! { 0: A, 1: B } -impl_track_tuple! { 0: A, 1: B, 2: C } -impl_track_tuple! { 0: A, 1: B, 2: C, 3: D } diff --git a/src/model/content.rs b/src/model/content.rs index 264785ece..efbaed0e4 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -7,8 +7,8 @@ use std::ops::{Add, AddAssign}; use typed_arena::Arena; use super::{ - Barrier, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, LocateNode, - Property, Show, ShowNode, StyleEntry, StyleMap, StyleVecBuilder, Target, + Barrier, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Property, Show, + ShowNode, StyleEntry, StyleMap, StyleVecBuilder, Target, }; use crate::diag::StrResult; use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing}; @@ -23,30 +23,6 @@ use crate::util::EcoString; /// /// Relayouts until all pinned locations are converged. pub fn layout(ctx: &mut Context, content: &Content) -> TypResult> { - let mut pass = 0; - let mut frames; - - loop { - let prev = ctx.pins.clone(); - let result = layout_once(ctx, content); - ctx.pins.reset(); - frames = result?; - pass += 1; - - ctx.pins.locate(&frames); - - // Quit if we're done or if we've had five passes. - let unresolved = ctx.pins.unresolved(&prev); - if unresolved == 0 || pass >= 5 { - break; - } - } - - Ok(frames) -} - -/// Layout content into a collection of pages once. -fn layout_once(ctx: &mut Context, content: &Content) -> TypResult> { let copy = ctx.config.styles.clone(); let styles = StyleChain::with_root(©); let scratch = Scratch::default(); @@ -114,8 +90,6 @@ pub enum Content { /// A node that can be realized with styles, optionally with attached /// properties. Show(ShowNode, Option), - /// A node that can be realized with its location on the page. - Locate(LocateNode), /// A pin identified by index. Pin(usize), /// Content with attached styles. @@ -307,7 +281,6 @@ impl Debug for Content { Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"), Self::Page(page) => page.fmt(f), Self::Show(node, _) => node.fmt(f), - Self::Locate(node) => node.fmt(f), Self::Pin(idx) => write!(f, "Pin({idx})"), Self::Styled(styled) => { let (sub, map) = styled.as_ref(); @@ -425,7 +398,6 @@ impl<'a, 'ctx> Builder<'a, 'ctx> { } Content::Show(node, _) => return self.show(node, styles), - Content::Locate(node) => return self.locate(node, styles), Content::Styled(styled) => return self.styled(styled, styles), Content::Sequence(seq) => return self.sequence(seq, styles), @@ -474,12 +446,6 @@ impl<'a, 'ctx> Builder<'a, 'ctx> { Ok(()) } - fn locate(&mut self, node: &LocateNode, styles: StyleChain<'a>) -> TypResult<()> { - let realized = node.realize(self.ctx)?; - let stored = self.scratch.templates.alloc(realized); - self.accept(stored, styles) - } - fn styled( &mut self, (content, map): &'a (Content, StyleMap), diff --git a/src/model/layout.rs b/src/model/layout.rs index ffe417253..d712a1781 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::hash::Hash; use std::sync::Arc; -use super::{Barrier, NodeId, PinConstraint, Resolve, StyleChain, StyleEntry}; +use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry}; use crate::diag::TypResult; use crate::eval::{RawAlign, RawLength}; use crate::frame::{Element, Frame}; @@ -131,8 +131,6 @@ impl Regions { } } -impl_track_hash!(Regions); - /// A type-erased layouting node with a precomputed hash. #[derive(Clone, Hash)] pub struct LayoutNode(Arc>); @@ -222,43 +220,17 @@ impl Layout for LayoutNode { regions: &Regions, styles: StyleChain, ) -> TypResult> { - let prev = ctx.pins.dirty.replace(false); + let barrier = StyleEntry::Barrier(Barrier::new(self.id())); + let styles = barrier.chain(&styles); - let (result, at, fresh, dirty) = crate::memo::memoized( - (self, &mut *ctx, regions, styles), - |(node, ctx, regions, styles)| { - let hash = fxhash::hash64(&ctx.pins); - let at = ctx.pins.cursor(); - - let barrier = StyleEntry::Barrier(Barrier::new(node.id())); - let styles = barrier.chain(&styles); - - let mut result = node.0.layout(ctx, regions, styles); - if let Some(role) = styles.role() { - result = result.map(|mut frames| { - for frame in frames.iter_mut() { - frame.apply_role(role); - } - frames - }); - } - - let fresh = ctx.pins.from(at); - let dirty = ctx.pins.dirty.get(); - let constraint = PinConstraint(dirty.then(|| hash)); - ((result, at, fresh, dirty), ((), constraint, (), ())) - }, - ); - - ctx.pins.dirty.replace(prev || dirty); - - // Replay the side effect in case of caching. This should currently be - // more or less the only relevant side effect on the context. - if dirty { - ctx.pins.replay(at, fresh); + let mut frames = self.0.layout(ctx, regions, styles)?; + if let Some(role) = styles.role() { + for frame in &mut frames { + frame.apply_role(role); + } } - result + Ok(frames) } fn pack(self) -> LayoutNode { @@ -266,8 +238,6 @@ impl Layout for LayoutNode { } } -impl_track_hash!(LayoutNode); - impl Default for LayoutNode { fn default() -> Self { EmptyNode.pack() diff --git a/src/model/locate.rs b/src/model/locate.rs deleted file mode 100644 index d5c714238..000000000 --- a/src/model/locate.rs +++ /dev/null @@ -1,397 +0,0 @@ -use std::cell::Cell; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroUsize; -use std::sync::Arc; - -use super::Content; -use crate::diag::TypResult; -use crate::eval::{Args, Array, Dict, Func, Value}; -use crate::frame::{Element, Frame, Location}; -use crate::geom::{Point, Transform}; -use crate::memo::Track; -use crate::syntax::Spanned; -use crate::util::EcoString; -use crate::Context; - -/// A group of locatable elements. -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct Group(EcoString); - -impl Group { - /// Create a group of elements that is identified by a string key. - pub fn new(key: EcoString) -> Self { - Self(key) - } - - /// Add an entry to the group. - pub fn entry(&self, recipe: Spanned, value: Option) -> LocateNode { - LocateNode::entry(self.clone(), recipe, value) - } - - /// Do something with all entries of a group. - pub fn all(&self, recipe: Spanned) -> LocateNode { - LocateNode::all(self.clone(), recipe) - } -} - -impl Debug for Group { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "group({:?})", self.0) - } -} - -/// A node that can be realized with pinned document locations. -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct LocateNode(Arc); - -impl LocateNode { - /// Create a new locatable single node. - pub fn single(recipe: Spanned) -> Self { - Self(Arc::new(Repr::Single(SingleNode(recipe)))) - } - - /// Create a new locatable group entry node. - pub fn entry(group: Group, recipe: Spanned, value: Option) -> Self { - Self(Arc::new(Repr::Entry(EntryNode { group, recipe, value }))) - } - - /// Create a new node with access to all of a group's members. - pub fn all(group: Group, recipe: Spanned) -> Self { - Self(Arc::new(Repr::All(AllNode { group, recipe }))) - } - - /// Realize the node. - pub fn realize(&self, ctx: &mut Context) -> TypResult { - match self.0.as_ref() { - Repr::Single(single) => single.realize(ctx), - Repr::Entry(entry) => entry.realize(ctx), - Repr::All(all) => all.realize(ctx), - } - } -} - -/// The different kinds of locate nodes. -#[derive(Debug, Clone, PartialEq, Hash)] -enum Repr { - /// A single `locate(me => ...)`. - Single(SingleNode), - /// A locatable group entry. - Entry(EntryNode), - /// A recipe for all entries of a group. - All(AllNode), -} - -/// An ungrouped locatable node. -#[derive(Debug, Clone, PartialEq, Hash)] -struct SingleNode(Spanned); - -impl SingleNode { - fn realize(&self, ctx: &mut Context) -> TypResult { - let idx = ctx.pins.cursor; - let pin = ctx.pins.get_or_create(None, None); - let dict = pin.encode(None); - let args = Args::new(self.0.span, [Value::Dict(dict)]); - Ok(Content::Pin(idx) + self.0.v.call_detached(ctx, args)?.display()) - } -} - -/// A locatable grouped node which can interact with its peers' details. -#[derive(Debug, Clone, PartialEq, Hash)] -struct EntryNode { - /// Which group the node belongs to. - group: Group, - /// The recipe to execute. - recipe: Spanned, - /// An arbitrary attached value. - value: Option, -} - -impl EntryNode { - fn realize(&self, ctx: &mut Context) -> TypResult { - let idx = ctx.pins.cursor; - let pin = ctx.pins.get_or_create(Some(self.group.clone()), self.value.clone()); - - // Determine the index among the peers. - let index = ctx - .pins - .iter() - .enumerate() - .filter(|&(k, other)| { - other.is_in(&self.group) - && if k < idx { - other.flow <= pin.flow - } else { - other.flow < pin.flow - } - }) - .count(); - - // Prepare first argument. - let dict = pin.encode(Some(index)); - let mut args = Args::new(self.recipe.span, [Value::Dict(dict)]); - - // Collect all group members if second argument is requested. - if self.recipe.v.argc() == Some(2) { - let all = ctx.pins.encode_group(&self.group); - args.push(self.recipe.span, Value::Array(all)) - } - - Ok(Content::Pin(idx) + self.recipe.v.call_detached(ctx, args)?.display()) - } -} - -/// A node with access to a group's members. -#[derive(Debug, Clone, PartialEq, Hash)] -struct AllNode { - /// Which group the node has access to. - group: Group, - /// The recipe to execute. - recipe: Spanned, -} - -impl AllNode { - fn realize(&self, ctx: &mut Context) -> TypResult { - let all = ctx.pins.encode_group(&self.group); - let args = Args::new(self.recipe.span, [Value::Array(all)]); - Ok(self.recipe.v.call_detached(ctx, args)?.display()) - } -} - -/// Manages document pins. -#[derive(Debug, Clone)] -pub struct PinBoard { - /// All currently active pins. - list: Vec, - /// The index of the next pin, in order. - cursor: usize, - /// If larger than zero, the board is frozen and the cursor will not be - /// advanced. This is used to disable pinning during measure-only layouting. - frozen: usize, - /// Whether the board was accessed. - pub(super) dirty: Cell, -} - -impl PinBoard { - /// Create an empty pin board. - pub fn new() -> Self { - Self { - list: vec![], - cursor: 0, - frozen: 0, - dirty: Cell::new(false), - } - } -} - -/// Internal methods for implementation of locatable nodes. -impl PinBoard { - /// Access or create the next pin. - fn get_or_create(&mut self, group: Option, value: Option) -> Pin { - self.dirty.set(true); - if self.frozen() { - return Pin::default(); - } - - let cursor = self.cursor; - self.cursor += 1; - if self.cursor >= self.list.len() { - self.list.resize(self.cursor, Pin::default()); - } - - let pin = &mut self.list[cursor]; - pin.group = group; - pin.value = value; - pin.clone() - } - - /// Encode a group into a user-facing array. - fn encode_group(&self, group: &Group) -> Array { - self.dirty.set(true); - let mut all: Vec<_> = self.iter().filter(|pin| pin.is_in(group)).collect(); - all.sort_by_key(|pin| pin.flow); - all.iter() - .enumerate() - .map(|(index, member)| Value::Dict(member.encode(Some(index)))) - .collect() - } - - /// Iterate over all pins on the board. - fn iter(&self) -> std::slice::Iter { - self.dirty.set(true); - self.list.iter() - } -} - -/// Caching related methods. -impl PinBoard { - /// The current cursor. - pub fn cursor(&self) -> usize { - self.cursor - } - - /// All pins from `prev` to the current cursor. - pub fn from(&self, prev: usize) -> Vec { - self.list[prev .. self.cursor].to_vec() - } - - /// Add the given pins at the given location and set the cursor behind them. - pub fn replay(&mut self, at: usize, pins: Vec) { - if !self.frozen() { - self.cursor = at + pins.len(); - let end = self.cursor.min(self.list.len()); - self.list.splice(at .. end, pins); - } - } -} - -/// Control methods that are called during layout. -impl PinBoard { - /// Freeze the board to prevent modifications. - pub fn freeze(&mut self) { - self.frozen += 1; - } - - /// Freeze the board to prevent modifications. - pub fn unfreeze(&mut self) { - self.frozen -= 1; - } - - /// Whether the board is currently frozen. - pub fn frozen(&self) -> bool { - self.frozen > 0 - } -} - -/// Methods that are called in between layout passes. -impl PinBoard { - /// Reset the cursor and remove all unused pins. - pub fn reset(&mut self) { - self.list.truncate(self.cursor); - self.cursor = 0; - self.dirty.set(false); - } - - /// Locate all pins in the frames. - pub fn locate(&mut self, frames: &[Frame]) { - let mut flow = 0; - for (i, frame) in frames.iter().enumerate() { - locate_in_frame( - &mut self.list, - &mut flow, - NonZeroUsize::new(1 + i).unwrap(), - frame, - Transform::identity(), - ); - } - } - - /// How many pins are unresolved in comparison to an earlier snapshot. - pub fn unresolved(&self, prev: &Self) -> usize { - self.list.len() - self.list.iter().zip(&prev.list).filter(|(a, b)| a == b).count() - } -} - -/// Locate all pins in a frame. -fn locate_in_frame( - pins: &mut [Pin], - flow: &mut usize, - page: NonZeroUsize, - frame: &Frame, - ts: Transform, -) { - for &(pos, ref element) in frame.elements() { - match element { - Element::Group(group) => { - let ts = ts - .pre_concat(Transform::translate(pos.x, pos.y)) - .pre_concat(group.transform); - locate_in_frame(pins, flow, page, &group.frame, ts); - } - - Element::Pin(idx) => { - let pin = &mut pins[*idx]; - pin.loc.page = page; - pin.loc.pos = pos.transform(ts); - pin.flow = *flow; - *flow += 1; - } - - _ => {} - } - } -} - -impl Hash for PinBoard { - fn hash(&self, state: &mut H) { - self.list.hash(state); - self.cursor.hash(state); - self.frozen.hash(state); - } -} - -/// Describes pin usage. -#[derive(Debug, Copy, Clone)] -pub struct PinConstraint(pub Option); - -impl Track for PinBoard { - type Constraint = PinConstraint; - - fn key(&self, _: &mut H) {} - - fn matches(&self, constraint: &Self::Constraint) -> bool { - match constraint.0 { - Some(hash) => fxhash::hash64(self) == hash, - None => true, - } - } -} - -/// A document pin. -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct Pin { - /// The physical location of the pin in the document. - loc: Location, - /// The flow index. - flow: usize, - /// The group the pin belongs to, if any. - group: Option, - /// An arbitrary attached value. - value: Option, -} - -impl Pin { - /// Whether the pin is part of the given group. - fn is_in(&self, group: &Group) -> bool { - self.group.as_ref() == Some(group) - } - - /// Encode into a user-facing dictionary. - fn encode(&self, index: Option) -> Dict { - let mut dict = self.loc.encode(); - - if let Some(value) = &self.value { - dict.insert("value".into(), value.clone()); - } - - if let Some(index) = index { - dict.insert("index".into(), Value::Int(index as i64)); - } - - dict - } -} - -impl Default for Pin { - fn default() -> Self { - Self { - loc: Location { - page: NonZeroUsize::new(1).unwrap(), - pos: Point::zero(), - }, - flow: 0, - group: None, - value: None, - } - } -} diff --git a/src/model/mod.rs b/src/model/mod.rs index 379b633f6..5c8b82c0b 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,7 +5,6 @@ mod styles; mod collapse; mod content; mod layout; -mod locate; mod property; mod recipe; mod show; @@ -13,7 +12,6 @@ mod show; pub use collapse::*; pub use content::*; pub use layout::*; -pub use locate::*; pub use property::*; pub use recipe::*; pub use show::*; diff --git a/src/model/styles.rs b/src/model/styles.rs index 03cdcaace..eab334028 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -413,8 +413,6 @@ impl PartialEq for StyleChain<'_> { } } -impl_track_hash!(StyleChain<'_>); - /// An iterator over the values in a style chain. struct Values<'a, K> { entries: Entries<'a>, diff --git a/tests/typ/layout/locate-break.typ b/tests/typ/layout/locate-break.typ deleted file mode 100644 index 28631cfa7..000000000 --- a/tests/typ/layout/locate-break.typ +++ /dev/null @@ -1,5 +0,0 @@ -// Test locate with crazy pagebreaks. - ---- -#set page(height: 10pt) -{3 * locate(me => me.page * pagebreak())} diff --git a/tests/typ/layout/locate-group.typ b/tests/typ/layout/locate-group.typ deleted file mode 100644 index c3a5ddab2..000000000 --- a/tests/typ/layout/locate-group.typ +++ /dev/null @@ -1,89 +0,0 @@ -// Test locatable groups. - ---- -// Test counting. -#let letters = group("\u{1F494}") -#let counter = letters.entry( - (me, all) => [{1 + me.index} / {all.len()}] -) - -#counter \ -#box(counter) \ -#counter \ - ---- -// Test minimal citation engine with references before the document. -#let cited = group("citations") -#let num(cited, key) = { - let index = 0 - for item in cited { - if item.value == key { - index = item.index - break - } - } - [\[{index + 1}\]] -} - -#let cite(key) = cited.entry(value: key, (_, all) => num(all, key)) -{cited.all(all => grid( - columns: (auto, 1fr), - gutter: 5pt, - ..{ - let seen = () - for item in all { - if item.value in seen { continue } - (num(all, item.value), item.value) - seen.push(item.value) - } - } -))} - -As shown in #cite("abc") and #cite("def") and #cite("abc") ... - ---- -// Test lovely sidebar. -#let lovely = group("lovely") -#let words = ("Juliet", "soft", "fair", "maid") -#let regex = regex(words.map(p => "(" + p + ")").join("|")) -#show word: regex as underline(word) + lovely.entry(_ => {}) -#set page( - paper: "a8", - margins: (left: 25pt, rest: 15pt), - foreground: lovely.all(entries => { - let seen = () - for y in entries.map(it => it.y) { - if y in seen { continue } - let line = entries.filter(it => it.y == y) - for i, it in line { - let x = 10pt - 4pt * (line.len() - i - 1) - place(dx: x, dy: it.y - 8pt, [💗]) - } - seen.push(y) - } - }), -) - -But, soft! what light through yonder window breaks? It is the east, and Juliet -is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and -pale with grief, That thou her maid art far more fair than she: Be not her maid, -since she is envious. - ---- -// Test that `all` contains `me`. -// Ref: false -#show it: heading as group("headings").entry( - (me, all) => { - let last - for prev in all { - last = prev - if prev.index == me.index { - break - } - } - assert(last == me) - } -) - -= A -== B diff --git a/tests/typ/layout/locate.typ b/tests/typ/layout/locate.typ deleted file mode 100644 index ec2262c50..000000000 --- a/tests/typ/layout/locate.typ +++ /dev/null @@ -1,22 +0,0 @@ -// Test locate me. - ---- -#set page(height: 60pt) -#let pin = locate(me => box({ - let c(length) = str(int(length / 1pt ) ) - square(size: 1.5pt, fill: blue) - h(0.15em) - text(0.5em)[{me.page}, #c(me.x), #c(me.y)] -})) - -#place(rotate(origin: top + left, 25deg, move(dx: 40pt, pin))) - -#pin -#h(10pt) -#box(pin) \ -#pin - -#place(bottom + right, pin) - -#pagebreak() -#align(center + horizon, pin + [\ ] + pin)