mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Locatable groups
This commit is contained in:
parent
66d8f4569a
commit
a9869c212f
@ -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
|
||||||
|
@ -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>() {
|
"matches" => {
|
||||||
match method {
|
if let Some(regex) = dynamic.downcast::<Regex>() {
|
||||||
"matches" => {
|
Value::Bool(regex.matches(&args.expect::<EcoString>("text")?))
|
||||||
Value::Bool(regex.matches(&args.expect::<EcoString>("text")?))
|
} else {
|
||||||
}
|
missing()?
|
||||||
_ => missing()?,
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
missing()?
|
|
||||||
}
|
}
|
||||||
}
|
"entry" => {
|
||||||
|
if let Some(group) = dynamic.downcast::<Group>() {
|
||||||
|
Value::Content(Content::Locate(group.entry(args.expect("recipe")?)))
|
||||||
|
} else {
|
||||||
|
missing()?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => missing()?,
|
||||||
|
},
|
||||||
|
|
||||||
_ => missing()?,
|
_ => missing()?,
|
||||||
};
|
};
|
||||||
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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.
|
||||||
|
@ -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)))
|
||||||
|
}
|
@ -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::*;
|
||||||
|
@ -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);
|
||||||
|
@ -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::*;
|
||||||
|
@ -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("es, double, peeked));
|
full.push_str(quoter.quote("es, 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)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// println!("Took {pass} passes");
|
||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>,
|
||||||
|
}
|
||||||
|
|
||||||
/// The number of pins on the board.
|
impl Pin {
|
||||||
pub fn len(&self) -> usize {
|
/// Encode into a user-facing dictionary.
|
||||||
self.pins.len()
|
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()),
|
||||||
|
};
|
||||||
|
|
||||||
/// How many pins are resolved in comparison to an earlier snapshot.
|
if let Some(index) = index {
|
||||||
pub fn resolved(&self, prev: &Self) -> usize {
|
dict.insert("index".into(), Value::Int(index as i64));
|
||||||
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.
|
dict
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user