mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Locate me!
This commit is contained in:
parent
99cb655832
commit
66d8f4569a
@ -797,7 +797,7 @@ impl Eval for ShowExpr {
|
|||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Recipe { pattern, func, span })
|
Ok(Recipe { pattern, func: Spanned::new(func, span) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,6 +490,7 @@ impl<'a> PageExporter<'a> {
|
|||||||
Element::Shape(ref shape) => self.write_shape(x, y, shape),
|
Element::Shape(ref shape) => self.write_shape(x, y, shape),
|
||||||
Element::Image(id, size) => self.write_image(x, y, id, size),
|
Element::Image(id, size) => self.write_image(x, y, id, size),
|
||||||
Element::Link(ref dest, size) => self.write_link(pos, dest, size),
|
Element::Link(ref dest, size) => self.write_link(pos, dest, size),
|
||||||
|
Element::Pin(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ fn render_frame(
|
|||||||
render_image(canvas, ts, mask, ctx.images.get(id), size);
|
render_image(canvas, ts, mask, ctx.images.get(id), size);
|
||||||
}
|
}
|
||||||
Element::Link(_, _) => {}
|
Element::Link(_, _) => {}
|
||||||
|
Element::Pin(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,6 +218,8 @@ pub enum Element {
|
|||||||
Image(ImageId, Size),
|
Image(ImageId, Size),
|
||||||
/// A link to an external resource and its trigger region.
|
/// A link to an external resource and its trigger region.
|
||||||
Link(Destination, Size),
|
Link(Destination, Size),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Element {
|
impl Debug for Element {
|
||||||
@ -227,7 +229,8 @@ impl Debug for Element {
|
|||||||
Self::Text(text) => write!(f, "{text:?}"),
|
Self::Text(text) => write!(f, "{text:?}"),
|
||||||
Self::Shape(shape) => write!(f, "{shape:?}"),
|
Self::Shape(shape) => write!(f, "{shape:?}"),
|
||||||
Self::Image(image, _) => write!(f, "{image:?}"),
|
Self::Image(image, _) => write!(f, "{image:?}"),
|
||||||
Self::Link(target, _) => write!(f, "Link({target:?})"),
|
Self::Link(dest, _) => write!(f, "Link({dest:?})"),
|
||||||
|
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/lib.rs
13
src/lib.rs
@ -61,7 +61,7 @@ use crate::font::FontStore;
|
|||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
use crate::model::StyleMap;
|
use crate::model::{PinBoard, StyleMap};
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
|
|
||||||
/// Typeset a source file into a collection of layouted frames.
|
/// Typeset a source file into a collection of layouted frames.
|
||||||
@ -76,27 +76,30 @@ pub fn typeset(ctx: &mut Context, id: SourceId) -> TypResult<Vec<Arc<Frame>>> {
|
|||||||
|
|
||||||
/// The core context which holds the configuration and stores.
|
/// The core context which holds the configuration and stores.
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
/// The context's configuration.
|
|
||||||
pub config: Config,
|
|
||||||
/// Stores loaded source files.
|
/// Stores loaded source files.
|
||||||
pub sources: SourceStore,
|
pub sources: SourceStore,
|
||||||
/// Stores parsed font faces.
|
/// Stores parsed font faces.
|
||||||
pub fonts: FontStore,
|
pub fonts: FontStore,
|
||||||
/// Stores decoded images.
|
/// Stores decoded images.
|
||||||
pub images: ImageStore,
|
pub images: ImageStore,
|
||||||
|
/// The context's configuration.
|
||||||
|
config: Config,
|
||||||
/// Stores evaluated modules.
|
/// Stores evaluated modules.
|
||||||
pub modules: HashMap<SourceId, Module>,
|
modules: HashMap<SourceId, Module>,
|
||||||
|
/// Stores document pins.
|
||||||
|
pins: PinBoard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Create a new context.
|
/// Create a new context.
|
||||||
pub fn new(loader: Arc<dyn Loader>, config: Config) -> Self {
|
pub fn new(loader: Arc<dyn Loader>, config: Config) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config,
|
|
||||||
sources: SourceStore::new(Arc::clone(&loader)),
|
sources: SourceStore::new(Arc::clone(&loader)),
|
||||||
fonts: FontStore::new(Arc::clone(&loader)),
|
fonts: FontStore::new(Arc::clone(&loader)),
|
||||||
images: ImageStore::new(loader),
|
images: ImageStore::new(loader),
|
||||||
|
config,
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
|
pins: PinBoard::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,7 @@ 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);
|
||||||
|
@ -26,6 +26,8 @@ pub enum ParChild {
|
|||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level node.
|
/// An arbitrary inline-level node.
|
||||||
Node(LayoutNode),
|
Node(LayoutNode),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
@ -100,6 +102,7 @@ impl Debug for ParChild {
|
|||||||
Self::Quote { double } => write!(f, "Quote({double})"),
|
Self::Quote { double } => write!(f, "Quote({double})"),
|
||||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||||
Self::Node(node) => node.fmt(f),
|
Self::Node(node) => node.fmt(f),
|
||||||
|
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,6 +278,8 @@ enum Segment<'a> {
|
|||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level layout node.
|
/// An arbitrary inline-level layout node.
|
||||||
Node(&'a LayoutNode),
|
Node(&'a LayoutNode),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Segment<'_> {
|
impl Segment<'_> {
|
||||||
@ -282,7 +287,7 @@ impl Segment<'_> {
|
|||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
match *self {
|
match *self {
|
||||||
Self::Text(len) => len,
|
Self::Text(len) => len,
|
||||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
Self::Spacing(_) | Self::Pin(_) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Node(_) => NODE_REPLACE.len_utf8(),
|
Self::Node(_) => NODE_REPLACE.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,6 +306,8 @@ enum Item<'a> {
|
|||||||
Frame(Arc<Frame>),
|
Frame(Arc<Frame>),
|
||||||
/// A repeating node.
|
/// A repeating node.
|
||||||
Repeat(&'a RepeatNode, StyleChain<'a>),
|
Repeat(&'a RepeatNode, StyleChain<'a>),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Item<'a> {
|
impl<'a> Item<'a> {
|
||||||
@ -316,7 +323,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(_) => SPACING_REPLACE.len_utf8(),
|
Self::Absolute(_) | Self::Fractional(_) | Self::Pin(_) => {
|
||||||
|
SPACING_REPLACE.len_utf8()
|
||||||
|
}
|
||||||
Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(),
|
Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,10 +333,10 @@ impl<'a> Item<'a> {
|
|||||||
/// The natural width of the item.
|
/// The natural width of the item.
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
match self {
|
match self {
|
||||||
Item::Text(shaped) => shaped.width,
|
Self::Text(shaped) => shaped.width,
|
||||||
Item::Absolute(v) => *v,
|
Self::Absolute(v) => *v,
|
||||||
Item::Fractional(_) | Self::Repeat(_, _) => Length::zero(),
|
Self::Frame(frame) => frame.size.x,
|
||||||
Item::Frame(frame) => frame.size.x,
|
Self::Fractional(_) | Self::Repeat(_, _) | Self::Pin(_) => Length::zero(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,7 +456,7 @@ fn collect<'a>(
|
|||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
}
|
}
|
||||||
ParChild::Quote { double } => {
|
&ParChild::Quote { double } => {
|
||||||
let prev = full.len();
|
let prev = full.len();
|
||||||
if styles.get(TextNode::SMART_QUOTES) {
|
if styles.get(TextNode::SMART_QUOTES) {
|
||||||
let lang = styles.get(TextNode::LANG);
|
let lang = styles.get(TextNode::LANG);
|
||||||
@ -456,24 +465,28 @@ 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(_) => Some(SPACING_REPLACE),
|
ParChild::Spacing(_) | ParChild::Pin(_) => Some(SPACING_REPLACE),
|
||||||
ParChild::Node(_) => Some(NODE_REPLACE),
|
ParChild::Node(_) => Some(NODE_REPLACE),
|
||||||
});
|
});
|
||||||
|
|
||||||
full.push_str(quoter.quote("es, *double, peeked));
|
full.push_str(quoter.quote("es, double, peeked));
|
||||||
} else {
|
} else {
|
||||||
full.push(if *double { '"' } else { '\'' });
|
full.push(if double { '"' } else { '\'' });
|
||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
}
|
}
|
||||||
ParChild::Spacing(spacing) => {
|
&ParChild::Spacing(spacing) => {
|
||||||
full.push(SPACING_REPLACE);
|
full.push(SPACING_REPLACE);
|
||||||
Segment::Spacing(*spacing)
|
Segment::Spacing(spacing)
|
||||||
}
|
}
|
||||||
ParChild::Node(node) => {
|
ParChild::Node(node) => {
|
||||||
full.push(NODE_REPLACE);
|
full.push(NODE_REPLACE);
|
||||||
Segment::Node(node)
|
Segment::Node(node)
|
||||||
}
|
}
|
||||||
|
&ParChild::Pin(idx) => {
|
||||||
|
full.push(SPACING_REPLACE);
|
||||||
|
Segment::Pin(idx)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(last) = full.chars().last() {
|
if let Some(last) = full.chars().last() {
|
||||||
@ -540,6 +553,7 @@ fn prepare<'a>(
|
|||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Segment::Pin(idx) => items.push(Item::Pin(idx)),
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = end;
|
cursor = end;
|
||||||
@ -1171,6 +1185,11 @@ fn commit(
|
|||||||
}
|
}
|
||||||
offset = before + width;
|
offset = before + width;
|
||||||
}
|
}
|
||||||
|
Item::Pin(idx) => {
|
||||||
|
let mut frame = Frame::new(Size::zero());
|
||||||
|
frame.push(Point::zero(), Element::Pin(*idx));
|
||||||
|
push(&mut offset, MaybeShared::Owned(frame));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
src/library/utility/locate.rs
Normal file
8
src/library/utility/locate.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use crate::library::prelude::*;
|
||||||
|
use crate::model::LocateNode;
|
||||||
|
|
||||||
|
/// Format content with access to its location on the page.
|
||||||
|
pub fn locate(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||||
|
let node = LocateNode::new(args.expect("recipe")?);
|
||||||
|
Ok(Value::Content(Content::Locate(node)))
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
//! 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::*;
|
||||||
|
|
||||||
|
20
src/memo.rs
20
src/memo.rs
@ -4,7 +4,7 @@ use std::any::Any;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
/// The thread-local cache.
|
/// The thread-local cache.
|
||||||
@ -60,7 +60,7 @@ where
|
|||||||
O: 'static,
|
O: 'static,
|
||||||
G: Fn(&O) -> R,
|
G: Fn(&O) -> R,
|
||||||
{
|
{
|
||||||
let hash = fxhash::hash64(&input);
|
let hash = fxhash::hash64(&(f, &input));
|
||||||
let result = with(|cache| {
|
let result = with(|cache| {
|
||||||
let entry = cache.get_mut(&hash)?;
|
let entry = cache.get_mut(&hash)?;
|
||||||
entry.age = 0;
|
entry.age = 0;
|
||||||
@ -111,13 +111,13 @@ impl Display for Eviction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// These impls are temporary and incorrect.
|
// These impls are temporary and incorrect.
|
||||||
macro_rules! skip {
|
|
||||||
($ty:ty) => {
|
impl Hash for crate::font::FontStore {
|
||||||
impl Hash for $ty {
|
fn hash<H: Hasher>(&self, _: &mut H) {}
|
||||||
fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
skip!(crate::font::FontStore);
|
impl Hash for crate::Context {
|
||||||
skip!(crate::Context);
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.pins.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,8 +7,8 @@ use std::ops::{Add, AddAssign};
|
|||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Barrier, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Property, Show,
|
Barrier, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, LocateNode,
|
||||||
ShowNode, StyleEntry, StyleMap, StyleVecBuilder, Target,
|
Property, Show, ShowNode, StyleEntry, StyleMap, StyleVecBuilder, Target,
|
||||||
};
|
};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
|
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
|
||||||
@ -20,7 +20,35 @@ use crate::library::text::{
|
|||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Layout content into a collection of pages.
|
/// Layout content into a collection of pages.
|
||||||
|
///
|
||||||
|
/// Relayouts until all pinned locations are converged.
|
||||||
pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>> {
|
pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
|
let mut pass = 0;
|
||||||
|
let mut frames;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let prev = ctx.pins.clone();
|
||||||
|
let result = layout_once(ctx, content);
|
||||||
|
ctx.pins.reset();
|
||||||
|
frames = result?;
|
||||||
|
pass += 1;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(frames)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout content into a collection of pages once.
|
||||||
|
fn layout_once(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let copy = ctx.config.styles.clone();
|
let copy = ctx.config.styles.clone();
|
||||||
let styles = StyleChain::with_root(©);
|
let styles = StyleChain::with_root(©);
|
||||||
let scratch = Scratch::default();
|
let scratch = Scratch::default();
|
||||||
@ -88,6 +116,10 @@ pub enum Content {
|
|||||||
/// A node that can be realized with styles, optionally with attached
|
/// A node that can be realized with styles, optionally with attached
|
||||||
/// properties.
|
/// properties.
|
||||||
Show(ShowNode, Option<Dict>),
|
Show(ShowNode, Option<Dict>),
|
||||||
|
/// A node that can be realized with its location on the page.
|
||||||
|
Locate(LocateNode),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
/// Content with attached styles.
|
/// Content with attached styles.
|
||||||
Styled(Arc<(Self, StyleMap)>),
|
Styled(Arc<(Self, StyleMap)>),
|
||||||
/// A sequence of multiple nodes.
|
/// A sequence of multiple nodes.
|
||||||
@ -272,6 +304,8 @@ impl Debug for Content {
|
|||||||
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
|
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
|
||||||
Self::Page(page) => page.fmt(f),
|
Self::Page(page) => page.fmt(f),
|
||||||
Self::Show(node, _) => node.fmt(f),
|
Self::Show(node, _) => node.fmt(f),
|
||||||
|
Self::Locate(node) => node.fmt(f),
|
||||||
|
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||||
Self::Styled(styled) => {
|
Self::Styled(styled) => {
|
||||||
let (sub, map) = styled.as_ref();
|
let (sub, map) = styled.as_ref();
|
||||||
map.fmt(f)?;
|
map.fmt(f)?;
|
||||||
@ -388,6 +422,7 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Content::Show(node, _) => return self.show(node, styles),
|
Content::Show(node, _) => return self.show(node, styles),
|
||||||
|
Content::Locate(node) => return self.locate(node, styles),
|
||||||
Content::Styled(styled) => return self.styled(styled, styles),
|
Content::Styled(styled) => return self.styled(styled, styles),
|
||||||
Content::Sequence(seq) => return self.sequence(seq, styles),
|
Content::Sequence(seq) => return self.sequence(seq, styles),
|
||||||
|
|
||||||
@ -436,6 +471,12 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn locate(&mut self, node: &LocateNode, styles: StyleChain<'a>) -> TypResult<()> {
|
||||||
|
let realized = node.realize(self.ctx)?;
|
||||||
|
let stored = self.scratch.templates.alloc(realized);
|
||||||
|
self.accept(stored, styles)
|
||||||
|
}
|
||||||
|
|
||||||
fn styled(
|
fn styled(
|
||||||
&mut self,
|
&mut self,
|
||||||
(content, map): &'a (Content, StyleMap),
|
(content, map): &'a (Content, StyleMap),
|
||||||
@ -641,6 +682,9 @@ impl<'a> ParBuilder<'a> {
|
|||||||
Content::Inline(node) => {
|
Content::Inline(node) => {
|
||||||
self.0.supportive(ParChild::Node(node.clone()), styles);
|
self.0.supportive(ParChild::Node(node.clone()), styles);
|
||||||
}
|
}
|
||||||
|
&Content::Pin(idx) => {
|
||||||
|
self.0.ignorant(ParChild::Pin(idx), styles);
|
||||||
|
}
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,7 +704,7 @@ impl<'a> ParBuilder<'a> {
|
|||||||
&& children
|
&& children
|
||||||
.items()
|
.items()
|
||||||
.find_map(|child| match child {
|
.find_map(|child| match child {
|
||||||
ParChild::Spacing(_) => None,
|
ParChild::Spacing(_) | ParChild::Pin(_) => None,
|
||||||
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
||||||
ParChild::Node(_) => Some(false),
|
ParChild::Node(_) => Some(false),
|
||||||
})
|
})
|
||||||
|
@ -221,13 +221,19 @@ impl Layout for LayoutNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
crate::memo::memoized(
|
let (result, cursor) = crate::memo::memoized(
|
||||||
(self, ctx, regions, styles),
|
(self, &mut *ctx, regions, styles),
|
||||||
|(node, ctx, regions, styles)| {
|
|(node, ctx, regions, styles)| {
|
||||||
let entry = StyleEntry::Barrier(Barrier::new(node.id()));
|
let entry = StyleEntry::Barrier(Barrier::new(node.id()));
|
||||||
node.0.layout(ctx, regions, entry.chain(&styles))
|
let result = node.0.layout(ctx, regions, entry.chain(&styles));
|
||||||
|
(result, ctx.pins.cursor())
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// Replay the side effect in case of caching. This should currently be
|
||||||
|
// more or less the only relevant side effect on the context.
|
||||||
|
ctx.pins.jump(cursor);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
fn pack(self) -> LayoutNode {
|
||||||
|
125
src/model/locate.rs
Normal file
125
src/model/locate.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::Content;
|
||||||
|
use crate::diag::TypResult;
|
||||||
|
use crate::eval::{Args, Func, Value};
|
||||||
|
use crate::frame::{Element, Frame};
|
||||||
|
use crate::geom::{Point, Transform};
|
||||||
|
use crate::syntax::Spanned;
|
||||||
|
use crate::Context;
|
||||||
|
|
||||||
|
/// A node that can realize itself with its own location.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub struct LocateNode(Spanned<Func>);
|
||||||
|
|
||||||
|
impl LocateNode {
|
||||||
|
/// Create a new locate node.
|
||||||
|
pub fn new(recipe: Spanned<Func>) -> Self {
|
||||||
|
Self(recipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Realize the node.
|
||||||
|
pub fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
||||||
|
let idx = ctx.pins.cursor();
|
||||||
|
let location = ctx.pins.next();
|
||||||
|
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)]);
|
||||||
|
Ok(Content::Pin(idx) + self.0.v.call_detached(ctx, args)?.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manages ordered pins.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub struct PinBoard {
|
||||||
|
/// All currently pinned locations.
|
||||||
|
pins: Vec<Location>,
|
||||||
|
/// The index of the next pin in order.
|
||||||
|
cursor: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinBoard {
|
||||||
|
/// Create an empty pin board.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { pins: vec![], cursor: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of pins on the board.
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A physical location in a document.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
|
||||||
|
pub struct Location {
|
||||||
|
/// The page, starting at 1.
|
||||||
|
pub page: usize,
|
||||||
|
/// The exact coordinates on the page (from the top left, as usual).
|
||||||
|
pub pos: Point,
|
||||||
|
}
|
@ -5,6 +5,7 @@ mod styles;
|
|||||||
mod collapse;
|
mod collapse;
|
||||||
mod content;
|
mod content;
|
||||||
mod layout;
|
mod layout;
|
||||||
|
mod locate;
|
||||||
mod property;
|
mod property;
|
||||||
mod recipe;
|
mod recipe;
|
||||||
mod show;
|
mod show;
|
||||||
@ -12,6 +13,7 @@ mod show;
|
|||||||
pub use collapse::*;
|
pub use collapse::*;
|
||||||
pub use content::*;
|
pub use content::*;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
|
pub use locate::*;
|
||||||
pub use property::*;
|
pub use property::*;
|
||||||
pub use recipe::*;
|
pub use recipe::*;
|
||||||
pub use show::*;
|
pub use show::*;
|
||||||
|
@ -4,7 +4,7 @@ use super::{Content, Interruption, NodeId, Show, ShowNode, StyleChain, StyleEntr
|
|||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{Args, Func, Regex, Value};
|
use crate::eval::{Args, Func, Regex, Value};
|
||||||
use crate::library::structure::{EnumNode, ListNode};
|
use crate::library::structure::{EnumNode, ListNode};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Spanned;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
@ -13,9 +13,7 @@ pub struct Recipe {
|
|||||||
/// The patterns to customize.
|
/// The patterns to customize.
|
||||||
pub pattern: Pattern,
|
pub pattern: Pattern,
|
||||||
/// The function that defines the recipe.
|
/// The function that defines the recipe.
|
||||||
pub func: Func,
|
pub func: Spanned<Func>,
|
||||||
/// The span to report all erros with.
|
|
||||||
pub span: Span,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recipe {
|
impl Recipe {
|
||||||
@ -81,13 +79,13 @@ impl Recipe {
|
|||||||
where
|
where
|
||||||
F: FnOnce() -> Value,
|
F: FnOnce() -> Value,
|
||||||
{
|
{
|
||||||
let args = if self.func.argc() == Some(0) {
|
let args = if self.func.v.argc() == Some(0) {
|
||||||
Args::new(self.span, [])
|
Args::new(self.func.span, [])
|
||||||
} else {
|
} else {
|
||||||
Args::new(self.span, [arg()])
|
Args::new(self.func.span, [arg()])
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(self.func.call_detached(ctx, args)?.display())
|
Ok(self.func.v.call_detached(ctx, args)?.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What kind of structure the property interrupts.
|
/// What kind of structure the property interrupts.
|
||||||
@ -104,7 +102,11 @@ impl Recipe {
|
|||||||
|
|
||||||
impl Debug for Recipe {
|
impl Debug for Recipe {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "Recipe matching {:?} from {:?}", self.pattern, self.span)
|
write!(
|
||||||
|
f,
|
||||||
|
"Recipe matching {:?} from {:?}",
|
||||||
|
self.pattern, self.func.span
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
tests/ref/layout/locate-break.png
Normal file
BIN
tests/ref/layout/locate-break.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 B |
BIN
tests/ref/layout/locate.png
Normal file
BIN
tests/ref/layout/locate.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
5
tests/typ/layout/locate-break.typ
Normal file
5
tests/typ/layout/locate-break.typ
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Test locate with crazy pagebreaks.
|
||||||
|
|
||||||
|
---
|
||||||
|
#set page(height: 10pt)
|
||||||
|
{3 * locate(me => me.page * pagebreak())}
|
22
tests/typ/layout/locate.typ
Normal file
22
tests/typ/layout/locate.typ
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Test locate me.
|
||||||
|
|
||||||
|
---
|
||||||
|
#set page(height: 60pt)
|
||||||
|
#let pin = locate(me => box({
|
||||||
|
let c(length) = str(int(length / 1pt ) )
|
||||||
|
square(size: 1.5pt, fill: blue)
|
||||||
|
h(0.15em)
|
||||||
|
text(0.5em)[{me.page}, #c(me.x), #c(me.y)]
|
||||||
|
}))
|
||||||
|
|
||||||
|
#place(rotate(origin: top + left, 25deg, move(dx: 40pt, pin)))
|
||||||
|
|
||||||
|
#pin
|
||||||
|
#h(10pt)
|
||||||
|
#box(pin) \
|
||||||
|
#pin
|
||||||
|
|
||||||
|
#place(bottom + right, pin)
|
||||||
|
|
||||||
|
#pagebreak()
|
||||||
|
#align(center + horizon, pin + [\ ] + pin)
|
Loading…
x
Reference in New Issue
Block a user