From 22214a1e0a79666caefd486e41828f015878ecb0 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 26 May 2022 15:59:11 +0200 Subject: [PATCH] Test groups --- src/model/content.rs | 8 +- src/model/locate.rs | 154 +++++++++++++++--------------- tests/ref/layout/locate-group.png | Bin 0 -> 3881 bytes tests/typ/layout/locate-group.typ | 43 +++++++++ 4 files changed, 124 insertions(+), 81 deletions(-) create mode 100644 tests/ref/layout/locate-group.png create mode 100644 tests/typ/layout/locate-group.typ diff --git a/src/model/content.rs b/src/model/content.rs index dad212c8a..21bf83695 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 10de70e15..a4b25d1de 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 0000000000000000000000000000000000000000..e38a4a32a00304b5b9cb4f5fcd1faba64bf84339 GIT binary patch literal 3881 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 [{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") ...