Locatable groups

This commit is contained in:
Laurenz 2022-05-26 13:49:44 +02:00
parent 66d8f4569a
commit a9869c212f
13 changed files with 273 additions and 113 deletions

View File

@ -39,6 +39,15 @@ impl Args {
Self { span, items } Self { span, items }
} }
/// Push a positional argument.
pub fn push(&mut self, span: Span, value: Value) {
self.items.push(Arg {
span: self.span,
name: None,
value: Spanned::new(value, span),
})
}
/// Consume and cast the first positional argument if there is one. /// Consume and cast the first positional argument if there is one.
pub fn eat<T>(&mut self) -> TypResult<Option<T>> pub fn eat<T>(&mut self) -> TypResult<Option<T>>
where where

View File

@ -2,6 +2,7 @@
use super::{Args, Machine, Regex, StrExt, Value}; use super::{Args, Machine, Regex, StrExt, Value};
use crate::diag::{At, TypResult}; use crate::diag::{At, TypResult};
use crate::model::{Content, Group};
use crate::syntax::Span; use crate::syntax::Span;
use crate::util::EcoString; use crate::util::EcoString;
@ -66,18 +67,23 @@ pub fn call(
_ => missing()?, _ => missing()?,
}, },
Value::Dyn(dynamic) => { Value::Dyn(dynamic) => match method {
if let Some(regex) = dynamic.downcast::<Regex>() {
match method {
"matches" => { "matches" => {
if let Some(regex) = dynamic.downcast::<Regex>() {
Value::Bool(regex.matches(&args.expect::<EcoString>("text")?)) Value::Bool(regex.matches(&args.expect::<EcoString>("text")?))
}
_ => missing()?,
}
} else { } else {
missing()? missing()?
} }
} }
"entry" => {
if let Some(group) = dynamic.downcast::<Group>() {
Value::Content(Content::Locate(group.entry(args.expect("recipe")?)))
} else {
missing()?
}
}
_ => missing()?,
},
_ => missing()?, _ => missing()?,
}; };

View File

@ -2,7 +2,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use super::{Dynamic, RawAlign, RawLength, RawStroke, Smart, StrExt, Value}; use super::{RawAlign, RawLength, RawStroke, Smart, StrExt, Value};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::geom::{Numeric, Relative, Spec, SpecAxis}; use crate::geom::{Numeric, Relative, Spec, SpecAxis};
use crate::model; use crate::model;
@ -94,10 +94,10 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Dict(a), Dict(b)) => Dict(a + b), (Dict(a), Dict(b)) => Dict(a + b),
(Color(color), Length(thickness)) | (Length(thickness), Color(color)) => { (Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
Dyn(Dynamic::new(RawStroke { Value::dynamic(RawStroke {
paint: Smart::Custom(color.into()), paint: Smart::Custom(color.into()),
thickness: Smart::Custom(thickness), thickness: Smart::Custom(thickness),
})) })
} }
(Dyn(a), Dyn(b)) => { (Dyn(a), Dyn(b)) => {
@ -106,10 +106,10 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(a.downcast::<RawAlign>(), b.downcast::<RawAlign>()) (a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
{ {
if a.axis() != b.axis() { if a.axis() != b.axis() {
Dyn(Dynamic::new(match a.axis() { Value::dynamic(match a.axis() {
SpecAxis::Horizontal => Spec { x: a, y: b }, SpecAxis::Horizontal => Spec { x: a, y: b },
SpecAxis::Vertical => Spec { x: b, y: a }, SpecAxis::Vertical => Spec { x: b, y: a },
})) })
} else { } else {
return Err(format!("cannot add two {:?} alignments", a.axis())); return Err(format!("cannot add two {:?} alignments", a.axis()));
} }

View File

@ -11,7 +11,7 @@ use crate::geom::{
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides, Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
}; };
use crate::library::text::RawNode; use crate::library::text::RawNode;
use crate::model::{Content, Layout, LayoutNode, Pattern}; use crate::model::{Content, Group, Layout, LayoutNode, Pattern};
use crate::syntax::Spanned; use crate::syntax::Spanned;
use crate::util::EcoString; use crate::util::EcoString;
@ -73,6 +73,14 @@ impl Value {
Self::Content(Content::block(node)) Self::Content(Content::block(node))
} }
/// Create a new dynamic value.
pub fn dynamic<T>(any: T) -> Self
where
T: Type + Debug + PartialEq + Hash + Sync + Send + 'static,
{
Self::Dyn(Dynamic::new(any))
}
/// The name of the stored value's type. /// The name of the stored value's type.
pub fn type_name(&self) -> &'static str { pub fn type_name(&self) -> &'static str {
match self { match self {
@ -653,6 +661,10 @@ dynamic! {
Regex: "regular expression", Regex: "regular expression",
} }
dynamic! {
Group: "group",
}
castable! { castable! {
usize, usize,
Expected: "non-negative integer", Expected: "non-negative integer",

View File

@ -204,7 +204,9 @@ impl<'a> GridLayouter<'a> {
/// Determines the columns sizes and then layouts the grid row-by-row. /// Determines the columns sizes and then layouts the grid row-by-row.
pub fn layout(mut self) -> TypResult<Vec<Arc<Frame>>> { pub fn layout(mut self) -> TypResult<Vec<Arc<Frame>>> {
self.ctx.pins.freeze();
self.measure_columns()?; self.measure_columns()?;
self.ctx.pins.unfreeze();
for y in 0 .. self.rows.len() { for y in 0 .. self.rows.len() {
// Skip to next region if current one is full, but only for content // Skip to next region if current one is full, but only for content
@ -370,10 +372,12 @@ impl<'a> GridLayouter<'a> {
pod.base.x = self.regions.base.x; pod.base.x = self.regions.base.x;
} }
self.ctx.pins.freeze();
let mut sizes = node let mut sizes = node
.layout(self.ctx, &pod, self.styles)? .layout(self.ctx, &pod, self.styles)?
.into_iter() .into_iter()
.map(|frame| frame.size.y); .map(|frame| frame.size.y);
self.ctx.pins.unfreeze();
// For each region, we want to know the maximum height any // For each region, we want to know the maximum height any
// column requires. // column requires.

View File

@ -1,8 +1,14 @@
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::model::LocateNode; use crate::model::{Group, LocateNode};
/// Format content with access to its location on the page. /// Format content with access to its location on the page.
pub fn locate(_: &mut Machine, args: &mut Args) -> TypResult<Value> { pub fn locate(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
let node = LocateNode::new(args.expect("recipe")?); let node = LocateNode::new(args.expect("recipe")?);
Ok(Value::Content(Content::Locate(node))) Ok(Value::Content(Content::Locate(node)))
} }
/// Create a new group of locatable elements.
pub fn group(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
let key = args.expect("key")?;
Ok(Value::dynamic(Group::new(key)))
}

View File

@ -5,6 +5,7 @@ mod columns;
mod container; mod container;
mod flow; mod flow;
mod grid; mod grid;
mod locate;
mod pad; mod pad;
mod page; mod page;
mod place; mod place;
@ -16,6 +17,7 @@ pub use columns::*;
pub use container::*; pub use container::*;
pub use flow::*; pub use flow::*;
pub use grid::*; pub use grid::*;
pub use locate::*;
pub use pad::*; pub use pad::*;
pub use page::*; pub use page::*;
pub use place::*; pub use place::*;

View File

@ -54,6 +54,8 @@ pub fn new() -> Scope {
std.def_node::<layout::ColumnsNode>("columns"); std.def_node::<layout::ColumnsNode>("columns");
std.def_node::<layout::ColbreakNode>("colbreak"); std.def_node::<layout::ColbreakNode>("colbreak");
std.def_node::<layout::PlaceNode>("place"); std.def_node::<layout::PlaceNode>("place");
std.def_fn("locate", layout::locate);
std.def_fn("group", layout::group);
// Graphics. // Graphics.
std.def_node::<graphics::ImageNode>("image"); std.def_node::<graphics::ImageNode>("image");
@ -92,7 +94,6 @@ pub fn new() -> Scope {
std.def_fn("roman", utility::roman); std.def_fn("roman", utility::roman);
std.def_fn("symbol", utility::symbol); std.def_fn("symbol", utility::symbol);
std.def_fn("lorem", utility::lorem); std.def_fn("lorem", utility::lorem);
std.def_fn("locate", utility::locate);
// Predefined colors. // Predefined colors.
std.def_const("black", Color::BLACK); std.def_const("black", Color::BLACK);

View File

@ -9,8 +9,8 @@ pub use typst_macros::node;
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult}; pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
pub use crate::eval::{ pub use crate::eval::{
Arg, Args, Array, Cast, Dict, Func, Machine, Node, RawAlign, RawLength, RawStroke, Arg, Args, Array, Cast, Dict, Dynamic, Func, Machine, Node, RawAlign, RawLength,
Scope, Smart, Value, RawStroke, Scope, Smart, Value,
}; };
pub use crate::frame::*; pub use crate::frame::*;
pub use crate::geom::*; pub use crate::geom::*;

View File

@ -194,10 +194,11 @@ impl LinebreakNode {
/// Range of a substring of text. /// Range of a substring of text.
type Range = std::ops::Range<usize>; type Range = std::ops::Range<usize>;
// The characters by which spacing and nodes are replaced in the paragraph's // The characters by which spacing, nodes and pins are replaced in the
// full text. // paragraph's full text.
const SPACING_REPLACE: char = ' '; const SPACING_REPLACE: char = ' '; // Space
const NODE_REPLACE: char = '\u{FFFC}'; const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
const PIN_REPLACE: char = '\u{200D}'; // Zero Width Joiner
/// A paragraph representation in which children are already layouted and text /// A paragraph representation in which children are already layouted and text
/// is already preshaped. /// is already preshaped.
@ -287,8 +288,9 @@ impl Segment<'_> {
fn len(&self) -> usize { fn len(&self) -> usize {
match *self { match *self {
Self::Text(len) => len, Self::Text(len) => len,
Self::Spacing(_) | Self::Pin(_) => SPACING_REPLACE.len_utf8(), Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
Self::Node(_) => NODE_REPLACE.len_utf8(), Self::Node(_) => NODE_REPLACE.len_utf8(),
Self::Pin(_) => PIN_REPLACE.len_utf8(),
} }
} }
} }
@ -323,10 +325,9 @@ impl<'a> Item<'a> {
fn len(&self) -> usize { fn len(&self) -> usize {
match self { match self {
Self::Text(shaped) => shaped.text.len(), Self::Text(shaped) => shaped.text.len(),
Self::Absolute(_) | Self::Fractional(_) | Self::Pin(_) => { Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(),
SPACING_REPLACE.len_utf8()
}
Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(), Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(),
Self::Pin(_) => PIN_REPLACE.len_utf8(),
} }
} }
@ -465,8 +466,9 @@ fn collect<'a>(
let peeked = iter.peek().and_then(|(child, _)| match child { let peeked = iter.peek().and_then(|(child, _)| match child {
ParChild::Text(text) => text.chars().next(), ParChild::Text(text) => text.chars().next(),
ParChild::Quote { .. } => Some('"'), ParChild::Quote { .. } => Some('"'),
ParChild::Spacing(_) | ParChild::Pin(_) => Some(SPACING_REPLACE), ParChild::Spacing(_) => Some(SPACING_REPLACE),
ParChild::Node(_) => Some(NODE_REPLACE), ParChild::Node(_) => Some(NODE_REPLACE),
ParChild::Pin(_) => Some(PIN_REPLACE),
}); });
full.push_str(quoter.quote(&quotes, double, peeked)); full.push_str(quoter.quote(&quotes, double, peeked));
@ -484,7 +486,7 @@ fn collect<'a>(
Segment::Node(node) Segment::Node(node)
} }
&ParChild::Pin(idx) => { &ParChild::Pin(idx) => {
full.push(SPACING_REPLACE); full.push(PIN_REPLACE);
Segment::Pin(idx) Segment::Pin(idx)
} }
}; };

View File

@ -1,12 +1,10 @@
//! Computational utility functions. //! Computational utility functions.
mod color; mod color;
mod locate;
mod math; mod math;
mod string; mod string;
pub use color::*; pub use color::*;
pub use locate::*;
pub use math::*; pub use math::*;
pub use string::*; pub use string::*;

View File

@ -44,6 +44,8 @@ pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>
} }
} }
// println!("Took {pass} passes");
Ok(frames) Ok(frames)
} }

View File

@ -1,117 +1,122 @@
use std::fmt::{self, Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
use super::Content; use super::Content;
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::eval::{Args, Func, Value}; use crate::eval::{Args, Dict, Func, Value};
use crate::frame::{Element, Frame}; use crate::frame::{Element, Frame};
use crate::geom::{Point, Transform}; use crate::geom::{Point, Transform};
use crate::syntax::Spanned; use crate::syntax::Spanned;
use crate::util::EcoString;
use crate::Context; 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<Func>) -> LocateNode {
LocateNode { recipe, group: Some(self.clone()) }
}
}
impl Debug for Group {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "group({:?})", self.0)
}
}
/// A node that can realize itself with its own location. /// A node that can realize itself with its own location.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct LocateNode(Spanned<Func>); pub struct LocateNode {
recipe: Spanned<Func>,
group: Option<Group>,
}
impl LocateNode { impl LocateNode {
/// Create a new locate node. /// Create a new locate node.
pub fn new(recipe: Spanned<Func>) -> Self { pub fn new(recipe: Spanned<Func>) -> Self {
Self(recipe) Self { recipe, group: None }
} }
/// Realize the node. /// Realize the node.
pub fn realize(&self, ctx: &mut Context) -> TypResult<Content> { pub fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
let idx = ctx.pins.cursor(); let idx = ctx.pins.cursor();
let location = ctx.pins.next(); let pin = ctx.pins.next(self.group.clone());
let dict = dict! {
"page" => Value::Int(location.page as i64),
"x" => Value::Length(location.pos.x.into()),
"y" => Value::Length(location.pos.y.into()),
};
let args = Args::new(self.0.span, [Value::Dict(dict)]); // Determine the index among the peers.
Ok(Content::Pin(idx) + self.0.v.call_detached(ctx, args)?.display()) let index = self.group.as_ref().map(|_| {
ctx.pins
.iter()
.filter(|other| {
other.group == self.group && other.loc.flow < pin.loc.flow
})
.count()
});
let dict = pin.encode(index);
let mut args = Args::new(self.recipe.span, [Value::Dict(dict)]);
// Collect all members if requested.
if self.group.is_some() && self.recipe.v.argc() == Some(2) {
let mut all: Vec<_> =
ctx.pins.iter().filter(|other| other.group == self.group).collect();
all.sort_by_key(|pin| pin.loc.flow);
let array = all
.iter()
.enumerate()
.map(|(index, member)| Value::Dict(member.encode(Some(index))))
.collect();
args.push(self.recipe.span, Value::Array(array))
}
Ok(Content::Pin(idx) + self.recipe.v.call_detached(ctx, args)?.display())
} }
} }
/// Manages ordered pins. /// Manages pins.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, Hash)]
pub struct PinBoard { pub struct PinBoard {
/// All currently pinned locations. /// All currently active pins.
pins: Vec<Location>, pins: 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.
frozen: usize,
} }
impl PinBoard { /// A document pin.
/// Create an empty pin board. #[derive(Debug, Default, Clone, PartialEq, Hash)]
pub fn new() -> Self { pub struct Pin {
Self { pins: vec![], cursor: 0 } /// The physical location of the pin in the document.
loc: Location,
/// The group the pin belongs to, if any.
group: Option<Group>,
}
impl Pin {
/// Encode into a user-facing dictionary.
fn encode(&self, index: Option<usize>) -> 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()),
};
if let Some(index) = index {
dict.insert("index".into(), Value::Int(index as i64));
} }
/// The number of pins on the board. dict
pub fn len(&self) -> usize {
self.pins.len()
}
/// 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()
}
/// Access the next pin location.
pub fn next(&mut self) -> Location {
let cursor = self.cursor;
self.jump(self.cursor + 1);
self.pins[cursor]
}
/// The current cursor.
pub fn cursor(&self) -> usize {
self.cursor
}
/// Set the current cursor.
pub fn jump(&mut self, cursor: usize) {
if cursor >= self.pins.len() {
let loc = self.pins.last().copied().unwrap_or_default();
self.pins.resize(cursor + 1, loc);
}
self.cursor = cursor;
}
/// Reset the cursor and remove all unused pins.
pub fn reset(&mut self) {
self.pins.truncate(self.cursor);
self.cursor = 0;
}
/// Locate all pins in the frames.
pub fn locate(&mut self, frames: &[Arc<Frame>]) {
for (i, frame) in frames.iter().enumerate() {
self.locate_impl(1 + i, frame, Transform::identity());
}
}
/// Locate all pins in a frame.
fn locate_impl(&mut self, page: usize, 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);
self.locate_impl(page, &group.frame, ts);
}
Element::Pin(idx) => {
let pin = &mut self.pins[*idx];
pin.page = page;
pin.pos = pos.transform(ts);
}
_ => {}
}
}
} }
} }
@ -122,4 +127,117 @@ pub struct Location {
pub page: usize, pub 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, pub pos: Point,
/// The flow index.
pub flow: 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<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>) -> Pin {
if self.frozen > 0 {
return Pin::default();
}
let cursor = self.cursor;
self.jump(self.cursor + 1);
self.pins[cursor].group = group;
self.pins[cursor].clone()
}
/// The current cursor.
pub fn cursor(&self) -> usize {
self.cursor
}
/// Set the current cursor.
pub fn jump(&mut self, cursor: usize) {
if self.frozen > 0 {
return;
}
self.cursor = cursor;
if cursor >= self.pins.len() {
self.pins.resize(cursor, Pin::default());
}
}
/// Reset the cursor and remove all unused pins.
pub fn reset(&mut self) {
self.pins.truncate(self.cursor);
self.cursor = 0;
}
/// Locate all pins in the frames.
pub fn locate(&mut self, frames: &[Arc<Frame>]) {
let mut flow = 0;
for (i, frame) in frames.iter().enumerate() {
locate_impl(
&mut self.pins,
&mut flow,
1 + i,
frame,
Transform::identity(),
);
}
}
/// 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()
}
}
/// Locate all pins in a frame.
fn locate_impl(
pins: &mut [Pin],
flow: &mut usize,
page: usize,
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_impl(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;
*flow += 1;
}
_ => {}
}
}
} }