mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Test groups
This commit is contained in:
parent
3e052e6e01
commit
22214a1e0a
@ -35,17 +35,13 @@ pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>
|
|||||||
|
|
||||||
ctx.pins.locate(&frames);
|
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.
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// println!("Took {pass} passes");
|
|
||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ impl LocateNode {
|
|||||||
Self(Arc::new(Repr::Entry(EntryNode { group, recipe, value })))
|
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<Func>) -> Self {
|
pub fn all(group: Group, recipe: Spanned<Func>) -> Self {
|
||||||
Self(Arc::new(Repr::All(AllNode { group, recipe })))
|
Self(Arc::new(Repr::All(AllNode { group, recipe })))
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ enum Repr {
|
|||||||
All(AllNode),
|
All(AllNode),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A solo locatable node.
|
/// An ungrouped locatable node.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
struct SingleNode(Spanned<Func>);
|
struct SingleNode(Spanned<Func>);
|
||||||
|
|
||||||
@ -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)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
struct EntryNode {
|
struct EntryNode {
|
||||||
/// Which group the node belongs to, if any.
|
/// Which group the node belongs to.
|
||||||
group: Group,
|
group: Group,
|
||||||
/// The recipe to execute.
|
/// The recipe to execute.
|
||||||
recipe: Spanned<Func>,
|
recipe: Spanned<Func>,
|
||||||
@ -112,13 +112,14 @@ impl EntryNode {
|
|||||||
let index = ctx
|
let index = ctx
|
||||||
.pins
|
.pins
|
||||||
.iter()
|
.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();
|
.count();
|
||||||
|
|
||||||
|
// Prepare first argument.
|
||||||
let dict = pin.encode(Some(index));
|
let dict = pin.encode(Some(index));
|
||||||
let mut args = Args::new(self.recipe.span, [Value::Dict(dict)]);
|
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) {
|
if self.recipe.v.argc() == Some(2) {
|
||||||
let all = ctx.pins.encode_group(&self.group);
|
let all = ctx.pins.encode_group(&self.group);
|
||||||
args.push(self.recipe.span, Value::Array(all))
|
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)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
struct AllNode {
|
struct AllNode {
|
||||||
/// Which group.
|
/// Which group the node has access to.
|
||||||
group: Group,
|
group: Group,
|
||||||
/// The recipe to execute.
|
/// The recipe to execute.
|
||||||
recipe: Spanned<Func>,
|
recipe: Spanned<Func>,
|
||||||
@ -145,56 +146,22 @@ impl AllNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manages pins.
|
/// Manages document pins.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct PinBoard {
|
pub struct PinBoard {
|
||||||
/// All currently active pins.
|
/// All currently active pins.
|
||||||
pins: Vec<Pin>,
|
list: Vec<Pin>,
|
||||||
/// The index of the next pin in order.
|
/// The index of the next pin, in order.
|
||||||
cursor: usize,
|
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,
|
frozen: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PinBoard {
|
impl PinBoard {
|
||||||
/// Create an empty pin board.
|
/// Create an empty pin board.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { pins: vec![], cursor: 0, frozen: 0 }
|
Self { list: 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<Pin> {
|
|
||||||
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<Group>, value: Option<Value>) -> 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current cursor.
|
/// The current cursor.
|
||||||
@ -209,14 +176,24 @@ impl PinBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.cursor = cursor;
|
self.cursor = cursor;
|
||||||
if cursor >= self.pins.len() {
|
if cursor >= self.list.len() {
|
||||||
self.pins.resize(cursor, Pin::default());
|
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.
|
/// Reset the cursor and remove all unused pins.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.pins.truncate(self.cursor);
|
self.list.truncate(self.cursor);
|
||||||
self.cursor = 0;
|
self.cursor = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,8 +201,8 @@ impl PinBoard {
|
|||||||
pub fn locate(&mut self, frames: &[Arc<Frame>]) {
|
pub fn locate(&mut self, frames: &[Arc<Frame>]) {
|
||||||
let mut flow = 0;
|
let mut flow = 0;
|
||||||
for (i, frame) in frames.iter().enumerate() {
|
for (i, frame) in frames.iter().enumerate() {
|
||||||
locate_impl(
|
locate_in_frame(
|
||||||
&mut self.pins,
|
&mut self.list,
|
||||||
&mut flow,
|
&mut flow,
|
||||||
1 + i,
|
1 + i,
|
||||||
frame,
|
frame,
|
||||||
@ -234,15 +211,35 @@ impl PinBoard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How many pins are resolved in comparison to an earlier snapshot.
|
/// How many pins are unresolved in comparison to an earlier snapshot.
|
||||||
pub fn resolved(&self, prev: &Self) -> usize {
|
pub fn unresolved(&self, prev: &Self) -> usize {
|
||||||
self.pins.iter().zip(&prev.pins).filter(|(a, b)| a == b).count()
|
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<Group>, value: Option<Value>) -> 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<Pin> {
|
||||||
|
self.list.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode a group into a user-facing array.
|
/// 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();
|
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()
|
all.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, member)| Value::Dict(member.encode(Some(index))))
|
.map(|(index, member)| Value::Dict(member.encode(Some(index))))
|
||||||
@ -251,7 +248,7 @@ impl PinBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Locate all pins in a frame.
|
/// Locate all pins in a frame.
|
||||||
fn locate_impl(
|
fn locate_in_frame(
|
||||||
pins: &mut [Pin],
|
pins: &mut [Pin],
|
||||||
flow: &mut usize,
|
flow: &mut usize,
|
||||||
page: usize,
|
page: usize,
|
||||||
@ -264,14 +261,14 @@ fn locate_impl(
|
|||||||
let ts = ts
|
let ts = ts
|
||||||
.pre_concat(Transform::translate(pos.x, pos.y))
|
.pre_concat(Transform::translate(pos.x, pos.y))
|
||||||
.pre_concat(group.transform);
|
.pre_concat(group.transform);
|
||||||
locate_impl(pins, flow, page, &group.frame, ts);
|
locate_in_frame(pins, flow, page, &group.frame, ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
Element::Pin(idx) => {
|
Element::Pin(idx) => {
|
||||||
let loc = &mut pins[*idx].loc;
|
let pin = &mut pins[*idx];
|
||||||
loc.page = page;
|
pin.loc.page = page;
|
||||||
loc.pos = pos.transform(ts);
|
pin.loc.pos = pos.transform(ts);
|
||||||
loc.flow = *flow;
|
pin.flow = *flow;
|
||||||
*flow += 1;
|
*flow += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,9 +279,11 @@ fn locate_impl(
|
|||||||
|
|
||||||
/// A document pin.
|
/// A document pin.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||||
pub struct Pin {
|
struct Pin {
|
||||||
/// The physical location of the pin in the document.
|
/// The physical location of the pin in the document.
|
||||||
loc: Location,
|
loc: Location,
|
||||||
|
/// The flow index.
|
||||||
|
flow: usize,
|
||||||
/// The group the pin belongs to, if any.
|
/// The group the pin belongs to, if any.
|
||||||
group: Option<Group>,
|
group: Option<Group>,
|
||||||
/// An arbitrary attached value.
|
/// An arbitrary attached value.
|
||||||
@ -299,11 +298,7 @@ impl Pin {
|
|||||||
|
|
||||||
/// Encode into a user-facing dictionary.
|
/// Encode into a user-facing dictionary.
|
||||||
fn encode(&self, index: Option<usize>) -> Dict {
|
fn encode(&self, index: Option<usize>) -> Dict {
|
||||||
let mut dict = dict! {
|
let mut dict = self.loc.encode();
|
||||||
"page" => Value::Int(self.loc.page as i64),
|
|
||||||
"x" => Value::Length(self.loc.pos.x.into()),
|
|
||||||
"y" => Value::Length(self.loc.pos.y.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(value) = &self.value {
|
if let Some(value) = &self.value {
|
||||||
dict.insert("value".into(), value.clone());
|
dict.insert("value".into(), value.clone());
|
||||||
@ -319,11 +314,20 @@ impl Pin {
|
|||||||
|
|
||||||
/// A physical location in a document.
|
/// A physical location in a document.
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
|
||||||
pub struct Location {
|
struct Location {
|
||||||
/// The page, starting at 1.
|
/// The page, starting at 1.
|
||||||
pub page: usize,
|
page: usize,
|
||||||
/// The exact coordinates on the page (from the top left, as usual).
|
/// The exact coordinates on the page (from the top left, as usual).
|
||||||
pub pos: Point,
|
pos: Point,
|
||||||
/// The flow index.
|
}
|
||||||
pub flow: usize,
|
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BIN
tests/ref/layout/locate-group.png
Normal file
BIN
tests/ref/layout/locate-group.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
43
tests/typ/layout/locate-group.typ
Normal file
43
tests/typ/layout/locate-group.typ
Normal file
@ -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") ...
|
Loading…
x
Reference in New Issue
Block a user