diff --git a/src/model/content.rs b/src/model/content.rs index dad212c8..21bf8369 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -35,17 +35,13 @@ pub fn layout(ctx: &mut Context, content: &Content) -> TypResult> ctx.pins.locate(&frames); - let count = ctx.pins.len(); - let resolved = ctx.pins.resolved(&prev); - // Quit if we're done or if we've had five passes. - if resolved == count || pass >= 5 { + let unresolved = ctx.pins.unresolved(&prev); + if unresolved == 0 || pass >= 5 { break; } } - // println!("Took {pass} passes"); - Ok(frames) } diff --git a/src/model/locate.rs b/src/model/locate.rs index 10de70e1..a4b25d1d 100644 --- a/src/model/locate.rs +++ b/src/model/locate.rs @@ -52,7 +52,7 @@ impl LocateNode { Self(Arc::new(Repr::Entry(EntryNode { group, recipe, value }))) } - /// Create a new all node with access to a group's members. + /// Create a new node with access to a group's members. pub fn all(group: Group, recipe: Spanned) -> Self { Self(Arc::new(Repr::All(AllNode { group, recipe }))) } @@ -78,7 +78,7 @@ enum Repr { All(AllNode), } -/// A solo locatable node. +/// An ungrouped locatable node. #[derive(Debug, Clone, PartialEq, Hash)] struct SingleNode(Spanned); @@ -92,10 +92,10 @@ impl SingleNode { } } -/// A group node which can interact with its peer's details. +/// A locatable grouped node which can interact with its peers' details. #[derive(Debug, Clone, PartialEq, Hash)] struct EntryNode { - /// Which group the node belongs to, if any. + /// Which group the node belongs to. group: Group, /// The recipe to execute. recipe: Spanned, @@ -112,13 +112,14 @@ impl EntryNode { let index = ctx .pins .iter() - .filter(|other| other.is_in(&self.group) && other.loc.flow < pin.loc.flow) + .filter(|other| other.is_in(&self.group) && 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 members if requested. + // 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)) @@ -128,10 +129,10 @@ impl EntryNode { } } -/// A node with access to the group's members without being one itself. +/// A node with access to a group's members. #[derive(Debug, Clone, PartialEq, Hash)] struct AllNode { - /// Which group. + /// Which group the node has access to. group: Group, /// The recipe to execute. recipe: Spanned, @@ -145,56 +146,22 @@ impl AllNode { } } -/// Manages pins. +/// Manages document pins. #[derive(Debug, Clone, Hash)] pub struct PinBoard { /// All currently active pins. - pins: Vec, - /// The index of the next pin in order. + list: Vec, + /// The index of the next pin, in order. cursor: usize, - /// If larger than zero, the board is frozen. + /// 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, } impl PinBoard { /// Create an empty pin board. pub fn new() -> Self { - Self { pins: vec![], cursor: 0, frozen: 0 } - } - - /// The number of pins on the board. - pub fn len(&self) -> usize { - self.pins.len() - } - - /// Iterate over all pins on the board. - pub fn iter(&self) -> std::slice::Iter { - self.pins.iter() - } - - /// 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; - } - - /// Access the next pin. - pub fn next(&mut self, group: Option, value: Option) -> Pin { - if self.frozen > 0 { - return Pin::default(); - } - - let cursor = self.cursor; - self.jump(self.cursor + 1); - - let pin = &mut self.pins[cursor]; - pin.group = group; - pin.value = value; - pin.clone() + Self { list: vec![], cursor: 0, frozen: 0 } } /// The current cursor. @@ -209,14 +176,24 @@ impl PinBoard { } self.cursor = cursor; - if cursor >= self.pins.len() { - self.pins.resize(cursor, Pin::default()); + if cursor >= self.list.len() { + self.list.resize(cursor, Pin::default()); } } + /// 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; + } + /// Reset the cursor and remove all unused pins. pub fn reset(&mut self) { - self.pins.truncate(self.cursor); + self.list.truncate(self.cursor); self.cursor = 0; } @@ -224,8 +201,8 @@ impl PinBoard { pub fn locate(&mut self, frames: &[Arc]) { let mut flow = 0; for (i, frame) in frames.iter().enumerate() { - locate_impl( - &mut self.pins, + locate_in_frame( + &mut self.list, &mut flow, 1 + i, frame, @@ -234,15 +211,35 @@ impl PinBoard { } } - /// How many pins are resolved in comparison to an earlier snapshot. - pub fn resolved(&self, prev: &Self) -> usize { - self.pins.iter().zip(&prev.pins).filter(|(a, b)| a == b).count() + /// 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() + } + + /// Access the next pin. + fn next(&mut self, group: Option, value: Option) -> Pin { + if self.frozen > 0 { + return Pin::default(); + } + + let cursor = self.cursor; + self.jump(self.cursor + 1); + + let pin = &mut self.list[cursor]; + pin.group = group; + pin.value = value; + pin.clone() + } + + /// Iterate over all pins on the board. + fn iter(&self) -> std::slice::Iter { + self.list.iter() } /// Encode a group into a user-facing array. - pub fn encode_group(&self, group: &Group) -> Array { + fn encode_group(&self, group: &Group) -> Array { let mut all: Vec<_> = self.iter().filter(|other| other.is_in(group)).collect(); - all.sort_by_key(|pin| pin.loc.flow); + all.sort_by_key(|pin| pin.flow); all.iter() .enumerate() .map(|(index, member)| Value::Dict(member.encode(Some(index)))) @@ -251,7 +248,7 @@ impl PinBoard { } /// Locate all pins in a frame. -fn locate_impl( +fn locate_in_frame( pins: &mut [Pin], flow: &mut usize, page: usize, @@ -264,14 +261,14 @@ fn locate_impl( let ts = ts .pre_concat(Transform::translate(pos.x, pos.y)) .pre_concat(group.transform); - locate_impl(pins, flow, page, &group.frame, ts); + locate_in_frame(pins, flow, page, &group.frame, ts); } Element::Pin(idx) => { - let loc = &mut pins[*idx].loc; - loc.page = page; - loc.pos = pos.transform(ts); - loc.flow = *flow; + let pin = &mut pins[*idx]; + pin.loc.page = page; + pin.loc.pos = pos.transform(ts); + pin.flow = *flow; *flow += 1; } @@ -282,9 +279,11 @@ fn locate_impl( /// A document pin. #[derive(Debug, Default, Clone, PartialEq, Hash)] -pub struct Pin { +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. @@ -299,11 +298,7 @@ impl Pin { /// Encode into a user-facing dictionary. fn encode(&self, index: Option) -> Dict { - let mut dict = dict! { - "page" => Value::Int(self.loc.page as i64), - "x" => Value::Length(self.loc.pos.x.into()), - "y" => Value::Length(self.loc.pos.y.into()), - }; + let mut dict = self.loc.encode(); if let Some(value) = &self.value { dict.insert("value".into(), value.clone()); @@ -319,11 +314,20 @@ impl Pin { /// A physical location in a document. #[derive(Debug, Default, Copy, Clone, PartialEq, Hash)] -pub struct Location { +struct Location { /// The page, starting at 1. - pub page: usize, + page: usize, /// The exact coordinates on the page (from the top left, as usual). - pub pos: Point, - /// The flow index. - pub flow: usize, + pos: Point, +} + +impl Location { + /// Encode into a user-facing dictionary. + fn encode(&self) -> Dict { + dict! { + "page" => Value::Int(self.page as i64), + "x" => Value::Length(self.pos.x.into()), + "y" => Value::Length(self.pos.y.into()), + } + } } diff --git a/tests/ref/layout/locate-group.png b/tests/ref/layout/locate-group.png new file mode 100644 index 00000000..e38a4a32 Binary files /dev/null and b/tests/ref/layout/locate-group.png differ diff --git a/tests/typ/layout/locate-group.typ b/tests/typ/layout/locate-group.typ new file mode 100644 index 00000000..49d4e335 --- /dev/null +++ b/tests/typ/layout/locate-group.typ @@ -0,0 +1,43 @@ +// 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 not in seen { + seen.push(item.value) + (num(all, item.value), item.value) + } + } + } +))} + +As shown in #cite("abc") and #cite("def") and #cite("abc") ...