mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Remove pins and memoization
This commit is contained in:
parent
e5f958b921
commit
4ec3bcee48
@ -3,7 +3,7 @@ use std::num::NonZeroUsize;
|
|||||||
use super::{Regex, Value};
|
use super::{Regex, Value};
|
||||||
use crate::diag::{with_alternative, StrResult};
|
use crate::diag::{with_alternative, StrResult};
|
||||||
use crate::geom::{Corners, Dir, Paint, Sides};
|
use crate::geom::{Corners, Dir, Paint, Sides};
|
||||||
use crate::model::{Content, Group, Layout, LayoutNode, Pattern};
|
use crate::model::{Content, Layout, LayoutNode, Pattern};
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -128,10 +128,6 @@ dynamic! {
|
|||||||
Regex: "regular expression",
|
Regex: "regular expression",
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic! {
|
|
||||||
Group: "group",
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
usize,
|
usize,
|
||||||
Expected: "non-negative integer",
|
Expected: "non-negative integer",
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use super::{Args, Machine, Value};
|
use super::{Args, Machine, 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;
|
||||||
|
|
||||||
@ -109,22 +108,6 @@ pub fn call(
|
|||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
},
|
},
|
||||||
|
|
||||||
Value::Dyn(dynamic) => {
|
|
||||||
if let Some(group) = dynamic.downcast::<Group>() {
|
|
||||||
match method {
|
|
||||||
"entry" => Value::Content(Content::Locate(
|
|
||||||
group.entry(args.expect("recipe")?, args.named("value")?),
|
|
||||||
)),
|
|
||||||
"all" => {
|
|
||||||
Value::Content(Content::Locate(group.all(args.expect("recipe")?)))
|
|
||||||
}
|
|
||||||
_ => return missing(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return missing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,15 +62,6 @@ pub fn evaluate(
|
|||||||
panic!("Tried to cyclicly evaluate {}", path);
|
panic!("Tried to cyclicly evaluate {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the module was already evaluated.
|
|
||||||
if let Some(module) = ctx.modules.get(&id) {
|
|
||||||
if module.valid(&ctx.sources) {
|
|
||||||
return Ok(module.clone());
|
|
||||||
} else {
|
|
||||||
ctx.modules.remove(&id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
route.push(id);
|
route.push(id);
|
||||||
|
|
||||||
// Parse the file.
|
// Parse the file.
|
||||||
@ -91,16 +82,11 @@ pub fn evaluate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assemble the module.
|
// Assemble the module.
|
||||||
let module = Module {
|
Ok(Module {
|
||||||
scope: vm.scopes.top,
|
scope: vm.scopes.top,
|
||||||
content: result?,
|
content: result?,
|
||||||
deps: vm.deps,
|
deps: vm.deps,
|
||||||
};
|
})
|
||||||
|
|
||||||
// Save the evaluated module.
|
|
||||||
ctx.modules.insert(id, module.clone());
|
|
||||||
|
|
||||||
Ok(module)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An evaluated module, ready for importing or layouting.
|
/// An evaluated module, ready for importing or layouting.
|
||||||
|
@ -246,16 +246,8 @@ fn render_outline_glyph(
|
|||||||
// Rasterize the glyph with `pixglyph`.
|
// Rasterize the glyph with `pixglyph`.
|
||||||
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
// Try to retrieve a prepared glyph or prepare it from scratch if it
|
||||||
// doesn't exist, yet.
|
// doesn't exist, yet.
|
||||||
let bitmap = crate::memo::memoized_ref(
|
let glyph = pixglyph::Glyph::load(ctx.fonts.get(text.face_id).ttf(), id)?;
|
||||||
(&ctx.fonts, text.face_id, id),
|
let bitmap = glyph.rasterize(ts.tx, ts.ty, ppem);
|
||||||
|(fonts, face_id, id)| {
|
|
||||||
(
|
|
||||||
pixglyph::Glyph::load(fonts.get(face_id).ttf(), id),
|
|
||||||
((), (), ()),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|glyph| glyph.as_ref().map(|g| g.rasterize(ts.tx, ts.ty, ppem)),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let cw = canvas.width() as i32;
|
let cw = canvas.width() as i32;
|
||||||
let ch = canvas.height() as i32;
|
let ch = canvas.height() as i32;
|
||||||
|
@ -234,10 +234,6 @@ fn shared_prefix_words(left: &str, right: &str) -> usize {
|
|||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_track_empty!(FontStore);
|
|
||||||
impl_track_hash!(FaceId);
|
|
||||||
impl_track_hash!(GlyphId);
|
|
||||||
|
|
||||||
/// A font face.
|
/// A font face.
|
||||||
pub struct Face {
|
pub struct Face {
|
||||||
/// The raw face data, possibly shared with other faces from the same
|
/// The raw face data, possibly shared with other faces from the same
|
||||||
|
27
src/lib.rs
27
src/lib.rs
@ -34,8 +34,6 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod memo;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod geom;
|
pub mod geom;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod diag;
|
pub mod diag;
|
||||||
@ -52,19 +50,16 @@ pub mod parse;
|
|||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::hash::Hasher;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{Module, Scope};
|
use crate::eval::Scope;
|
||||||
use crate::font::FontStore;
|
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::memo::Track;
|
use crate::model::StyleMap;
|
||||||
use crate::model::{PinBoard, PinConstraint, 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.
|
||||||
@ -89,10 +84,6 @@ pub struct Context {
|
|||||||
pub images: ImageStore,
|
pub images: ImageStore,
|
||||||
/// The context's configuration.
|
/// The context's configuration.
|
||||||
config: Config,
|
config: Config,
|
||||||
/// Stores evaluated modules.
|
|
||||||
modules: HashMap<SourceId, Module>,
|
|
||||||
/// Stores document pins.
|
|
||||||
pins: PinBoard,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
@ -104,24 +95,10 @@ impl Context {
|
|||||||
fonts: FontStore::new(Arc::clone(&loader)),
|
fonts: FontStore::new(Arc::clone(&loader)),
|
||||||
images: ImageStore::new(loader),
|
images: ImageStore::new(loader),
|
||||||
config,
|
config,
|
||||||
modules: HashMap::new(),
|
|
||||||
pins: PinBoard::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Track for &mut Context {
|
|
||||||
type Constraint = PinConstraint;
|
|
||||||
|
|
||||||
fn key<H: Hasher>(&self, hasher: &mut H) {
|
|
||||||
self.pins.key(hasher);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, constraint: &Self::Constraint) -> bool {
|
|
||||||
self.pins.matches(constraint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compilation configuration.
|
/// Compilation configuration.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The compilation root.
|
/// The compilation root.
|
||||||
|
@ -204,9 +204,7 @@ 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<Frame>> {
|
pub fn layout(mut self) -> TypResult<Vec<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
|
||||||
@ -372,12 +370,10 @@ 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.height());
|
.map(|frame| frame.height());
|
||||||
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,14 +0,0 @@
|
|||||||
use crate::library::prelude::*;
|
|
||||||
use crate::model::{Group, LocateNode};
|
|
||||||
|
|
||||||
/// Format content with access to its location on the page.
|
|
||||||
pub fn locate(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|
||||||
let node = LocateNode::single(args.expect("recipe")?);
|
|
||||||
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,7 +5,6 @@ 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;
|
||||||
@ -17,7 +16,6 @@ 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::*;
|
||||||
|
@ -57,8 +57,6 @@ 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");
|
||||||
|
203
src/memo.rs
203
src/memo.rs
@ -1,203 +0,0 @@
|
|||||||
//! Function memoization.
|
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
use std::hash::Hasher;
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
/// The thread-local cache.
|
|
||||||
static CACHE: RefCell<Cache> = RefCell::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A map from hashes to cache entries.
|
|
||||||
type Cache = HashMap<u64, CacheEntry>;
|
|
||||||
|
|
||||||
/// Access the cache mutably.
|
|
||||||
fn with<F, R>(f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Cache) -> R,
|
|
||||||
{
|
|
||||||
CACHE.with(|cell| f(&mut cell.borrow_mut()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An entry in the cache.
|
|
||||||
struct CacheEntry {
|
|
||||||
/// The memoized function's result plus constraints on the input in the form
|
|
||||||
/// `(O, I::Contrast)`.
|
|
||||||
data: Box<dyn Any>,
|
|
||||||
/// How many evictions have passed since the entry has been last used.
|
|
||||||
age: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a memoized function call.
|
|
||||||
///
|
|
||||||
/// This [tracks](Track) all inputs to the function and then either returns a
|
|
||||||
/// cached version from the thread-local cache or executes the function and
|
|
||||||
/// saves a copy of the results in the cache.
|
|
||||||
///
|
|
||||||
/// Note that `f` must be a pure function.
|
|
||||||
pub fn memoized<I, O>(input: I, f: fn(input: I) -> (O, I::Constraint)) -> O
|
|
||||||
where
|
|
||||||
I: Track,
|
|
||||||
O: Clone + 'static,
|
|
||||||
{
|
|
||||||
memoized_ref(input, f, Clone::clone)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a function and then call another function with a reference to the
|
|
||||||
/// result.
|
|
||||||
///
|
|
||||||
/// This [tracks](Track) all inputs to the function and then either
|
|
||||||
/// - calls `g` with a cached version from the thread-local cache,
|
|
||||||
/// - or executes `f`, calls `g` with the fresh version and saves the result in
|
|
||||||
/// the cache.
|
|
||||||
///
|
|
||||||
/// Note that `f` must be a pure function, while `g` does not need to be pure.
|
|
||||||
pub fn memoized_ref<I, O, G, R>(
|
|
||||||
input: I,
|
|
||||||
f: fn(input: I) -> (O, I::Constraint),
|
|
||||||
g: G,
|
|
||||||
) -> R
|
|
||||||
where
|
|
||||||
I: Track,
|
|
||||||
O: 'static,
|
|
||||||
G: Fn(&O) -> R,
|
|
||||||
{
|
|
||||||
let mut state = fxhash::FxHasher64::default();
|
|
||||||
input.key(&mut state);
|
|
||||||
|
|
||||||
let key = state.finish();
|
|
||||||
let result = with(|cache| {
|
|
||||||
let entry = cache.get_mut(&key)?;
|
|
||||||
entry.age = 0;
|
|
||||||
entry
|
|
||||||
.data
|
|
||||||
.downcast_ref::<(O, I::Constraint)>()
|
|
||||||
.filter(|(_, constraint)| input.matches(constraint))
|
|
||||||
.map(|(output, _)| g(output))
|
|
||||||
});
|
|
||||||
|
|
||||||
result.unwrap_or_else(|| {
|
|
||||||
let output = f(input);
|
|
||||||
let result = g(&output.0);
|
|
||||||
let entry = CacheEntry {
|
|
||||||
data: Box::new(output) as Box<(O, I::Constraint)> as Box<dyn Any>,
|
|
||||||
age: 0,
|
|
||||||
};
|
|
||||||
with(|cache| cache.insert(key, entry));
|
|
||||||
result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Garbage-collect the thread-local cache.
|
|
||||||
///
|
|
||||||
/// This deletes elements which haven't been used in a while and returns details
|
|
||||||
/// about the eviction.
|
|
||||||
pub fn evict() -> Eviction {
|
|
||||||
with(|cache| {
|
|
||||||
const MAX_AGE: usize = 5;
|
|
||||||
|
|
||||||
let before = cache.len();
|
|
||||||
cache.retain(|_, entry| {
|
|
||||||
entry.age += 1;
|
|
||||||
entry.age <= MAX_AGE
|
|
||||||
});
|
|
||||||
|
|
||||||
Eviction { before, after: cache.len() }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Details about a cache eviction.
|
|
||||||
pub struct Eviction {
|
|
||||||
/// The number of items in the cache before the eviction.
|
|
||||||
pub before: usize,
|
|
||||||
/// The number of items in the cache after the eviction.
|
|
||||||
pub after: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Eviction {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
writeln!(f, "Before: {}", self.before)?;
|
|
||||||
writeln!(f, "Evicted: {}", self.before - self.after)?;
|
|
||||||
writeln!(f, "After: {}", self.after)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tracks input dependencies of a memoized function.
|
|
||||||
pub trait Track {
|
|
||||||
/// The type of constraint generated by this input.
|
|
||||||
type Constraint: 'static;
|
|
||||||
|
|
||||||
/// Feed the key portion of the input into a hasher.
|
|
||||||
fn key<H: Hasher>(&self, hasher: &mut H);
|
|
||||||
|
|
||||||
/// Whether this instance matches the given constraint.
|
|
||||||
fn matches(&self, constraint: &Self::Constraint) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Track> Track for &T {
|
|
||||||
type Constraint = T::Constraint;
|
|
||||||
|
|
||||||
fn key<H: Hasher>(&self, hasher: &mut H) {
|
|
||||||
Track::key(*self, hasher)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, constraint: &Self::Constraint) -> bool {
|
|
||||||
Track::matches(*self, constraint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_track_empty {
|
|
||||||
($ty:ty) => {
|
|
||||||
impl $crate::memo::Track for $ty {
|
|
||||||
type Constraint = ();
|
|
||||||
|
|
||||||
fn key<H: std::hash::Hasher>(&self, _: &mut H) {}
|
|
||||||
|
|
||||||
fn matches(&self, _: &Self::Constraint) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_track_hash {
|
|
||||||
($ty:ty) => {
|
|
||||||
impl $crate::memo::Track for $ty {
|
|
||||||
type Constraint = ();
|
|
||||||
|
|
||||||
fn key<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
|
||||||
std::hash::Hash::hash(self, hasher)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, _: &Self::Constraint) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_track_tuple {
|
|
||||||
($($idx:tt: $field:ident),*) => {
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
impl<$($field: Track),*> Track for ($($field,)*) {
|
|
||||||
type Constraint = ($($field::Constraint,)*);
|
|
||||||
|
|
||||||
fn key<H: Hasher>(&self, hasher: &mut H) {
|
|
||||||
$(self.$idx.key(hasher);)*
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, constraint: &Self::Constraint) -> bool {
|
|
||||||
true $(&& self.$idx.matches(&constraint.$idx))*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_track_tuple! {}
|
|
||||||
impl_track_tuple! { 0: A }
|
|
||||||
impl_track_tuple! { 0: A, 1: B }
|
|
||||||
impl_track_tuple! { 0: A, 1: B, 2: C }
|
|
||||||
impl_track_tuple! { 0: A, 1: B, 2: C, 3: D }
|
|
@ -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, LocateNode,
|
Barrier, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Property, Show,
|
||||||
Property, Show, ShowNode, StyleEntry, StyleMap, StyleVecBuilder, Target,
|
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};
|
||||||
@ -23,30 +23,6 @@ use crate::util::EcoString;
|
|||||||
///
|
///
|
||||||
/// Relayouts until all pinned locations are converged.
|
/// Relayouts until all pinned locations are converged.
|
||||||
pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Frame>> {
|
pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<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);
|
|
||||||
|
|
||||||
// Quit if we're done or if we've had five passes.
|
|
||||||
let unresolved = ctx.pins.unresolved(&prev);
|
|
||||||
if unresolved == 0 || pass >= 5 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(frames)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout content into a collection of pages once.
|
|
||||||
fn layout_once(ctx: &mut Context, content: &Content) -> TypResult<Vec<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();
|
||||||
@ -114,8 +90,6 @@ 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.
|
/// A pin identified by index.
|
||||||
Pin(usize),
|
Pin(usize),
|
||||||
/// Content with attached styles.
|
/// Content with attached styles.
|
||||||
@ -307,7 +281,6 @@ 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::Pin(idx) => write!(f, "Pin({idx})"),
|
||||||
Self::Styled(styled) => {
|
Self::Styled(styled) => {
|
||||||
let (sub, map) = styled.as_ref();
|
let (sub, map) = styled.as_ref();
|
||||||
@ -425,7 +398,6 @@ 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),
|
||||||
|
|
||||||
@ -474,12 +446,6 @@ 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),
|
||||||
|
@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Barrier, NodeId, PinConstraint, Resolve, StyleChain, StyleEntry};
|
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{RawAlign, RawLength};
|
use crate::eval::{RawAlign, RawLength};
|
||||||
use crate::frame::{Element, Frame};
|
use crate::frame::{Element, Frame};
|
||||||
@ -131,8 +131,6 @@ impl Regions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_track_hash!(Regions);
|
|
||||||
|
|
||||||
/// A type-erased layouting node with a precomputed hash.
|
/// A type-erased layouting node with a precomputed hash.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
|
pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
|
||||||
@ -222,43 +220,17 @@ impl Layout for LayoutNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Frame>> {
|
) -> TypResult<Vec<Frame>> {
|
||||||
let prev = ctx.pins.dirty.replace(false);
|
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
||||||
|
let styles = barrier.chain(&styles);
|
||||||
|
|
||||||
let (result, at, fresh, dirty) = crate::memo::memoized(
|
let mut frames = self.0.layout(ctx, regions, styles)?;
|
||||||
(self, &mut *ctx, regions, styles),
|
if let Some(role) = styles.role() {
|
||||||
|(node, ctx, regions, styles)| {
|
for frame in &mut frames {
|
||||||
let hash = fxhash::hash64(&ctx.pins);
|
frame.apply_role(role);
|
||||||
let at = ctx.pins.cursor();
|
}
|
||||||
|
|
||||||
let barrier = StyleEntry::Barrier(Barrier::new(node.id()));
|
|
||||||
let styles = barrier.chain(&styles);
|
|
||||||
|
|
||||||
let mut result = node.0.layout(ctx, regions, styles);
|
|
||||||
if let Some(role) = styles.role() {
|
|
||||||
result = result.map(|mut frames| {
|
|
||||||
for frame in frames.iter_mut() {
|
|
||||||
frame.apply_role(role);
|
|
||||||
}
|
|
||||||
frames
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let fresh = ctx.pins.from(at);
|
|
||||||
let dirty = ctx.pins.dirty.get();
|
|
||||||
let constraint = PinConstraint(dirty.then(|| hash));
|
|
||||||
((result, at, fresh, dirty), ((), constraint, (), ()))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.pins.dirty.replace(prev || dirty);
|
|
||||||
|
|
||||||
// Replay the side effect in case of caching. This should currently be
|
|
||||||
// more or less the only relevant side effect on the context.
|
|
||||||
if dirty {
|
|
||||||
ctx.pins.replay(at, fresh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
fn pack(self) -> LayoutNode {
|
||||||
@ -266,8 +238,6 @@ impl Layout for LayoutNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_track_hash!(LayoutNode);
|
|
||||||
|
|
||||||
impl Default for LayoutNode {
|
impl Default for LayoutNode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
EmptyNode.pack()
|
EmptyNode.pack()
|
||||||
|
@ -1,397 +0,0 @@
|
|||||||
use std::cell::Cell;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::Content;
|
|
||||||
use crate::diag::TypResult;
|
|
||||||
use crate::eval::{Args, Array, Dict, Func, Value};
|
|
||||||
use crate::frame::{Element, Frame, Location};
|
|
||||||
use crate::geom::{Point, Transform};
|
|
||||||
use crate::memo::Track;
|
|
||||||
use crate::syntax::Spanned;
|
|
||||||
use crate::util::EcoString;
|
|
||||||
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>, value: Option<Value>) -> LocateNode {
|
|
||||||
LocateNode::entry(self.clone(), recipe, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do something with all entries of a group.
|
|
||||||
pub fn all(&self, recipe: Spanned<Func>) -> LocateNode {
|
|
||||||
LocateNode::all(self.clone(), recipe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Group {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "group({:?})", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that can be realized with pinned document locations.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
pub struct LocateNode(Arc<Repr>);
|
|
||||||
|
|
||||||
impl LocateNode {
|
|
||||||
/// Create a new locatable single node.
|
|
||||||
pub fn single(recipe: Spanned<Func>) -> Self {
|
|
||||||
Self(Arc::new(Repr::Single(SingleNode(recipe))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new locatable group entry node.
|
|
||||||
pub fn entry(group: Group, recipe: Spanned<Func>, value: Option<Value>) -> Self {
|
|
||||||
Self(Arc::new(Repr::Entry(EntryNode { group, recipe, value })))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new node with access to all of a group's members.
|
|
||||||
pub fn all(group: Group, recipe: Spanned<Func>) -> Self {
|
|
||||||
Self(Arc::new(Repr::All(AllNode { group, recipe })))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Realize the node.
|
|
||||||
pub fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
|
||||||
match self.0.as_ref() {
|
|
||||||
Repr::Single(single) => single.realize(ctx),
|
|
||||||
Repr::Entry(entry) => entry.realize(ctx),
|
|
||||||
Repr::All(all) => all.realize(ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The different kinds of locate nodes.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
enum Repr {
|
|
||||||
/// A single `locate(me => ...)`.
|
|
||||||
Single(SingleNode),
|
|
||||||
/// A locatable group entry.
|
|
||||||
Entry(EntryNode),
|
|
||||||
/// A recipe for all entries of a group.
|
|
||||||
All(AllNode),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An ungrouped locatable node.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
struct SingleNode(Spanned<Func>);
|
|
||||||
|
|
||||||
impl SingleNode {
|
|
||||||
fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
|
||||||
let idx = ctx.pins.cursor;
|
|
||||||
let pin = ctx.pins.get_or_create(None, None);
|
|
||||||
let dict = pin.encode(None);
|
|
||||||
let args = Args::new(self.0.span, [Value::Dict(dict)]);
|
|
||||||
Ok(Content::Pin(idx) + self.0.v.call_detached(ctx, args)?.display())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A locatable grouped node which can interact with its peers' details.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
struct EntryNode {
|
|
||||||
/// Which group the node belongs to.
|
|
||||||
group: Group,
|
|
||||||
/// The recipe to execute.
|
|
||||||
recipe: Spanned<Func>,
|
|
||||||
/// An arbitrary attached value.
|
|
||||||
value: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EntryNode {
|
|
||||||
fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
|
||||||
let idx = ctx.pins.cursor;
|
|
||||||
let pin = ctx.pins.get_or_create(Some(self.group.clone()), self.value.clone());
|
|
||||||
|
|
||||||
// Determine the index among the peers.
|
|
||||||
let index = ctx
|
|
||||||
.pins
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|&(k, other)| {
|
|
||||||
other.is_in(&self.group)
|
|
||||||
&& if k < idx {
|
|
||||||
other.flow <= pin.flow
|
|
||||||
} else {
|
|
||||||
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 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Content::Pin(idx) + self.recipe.v.call_detached(ctx, args)?.display())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node with access to a group's members.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
struct AllNode {
|
|
||||||
/// Which group the node has access to.
|
|
||||||
group: Group,
|
|
||||||
/// The recipe to execute.
|
|
||||||
recipe: Spanned<Func>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AllNode {
|
|
||||||
fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
|
||||||
let all = ctx.pins.encode_group(&self.group);
|
|
||||||
let args = Args::new(self.recipe.span, [Value::Array(all)]);
|
|
||||||
Ok(self.recipe.v.call_detached(ctx, args)?.display())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manages document pins.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PinBoard {
|
|
||||||
/// All currently active pins.
|
|
||||||
list: Vec<Pin>,
|
|
||||||
/// The index of the next pin, in order.
|
|
||||||
cursor: usize,
|
|
||||||
/// 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,
|
|
||||||
/// Whether the board was accessed.
|
|
||||||
pub(super) dirty: Cell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PinBoard {
|
|
||||||
/// Create an empty pin board.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
list: vec![],
|
|
||||||
cursor: 0,
|
|
||||||
frozen: 0,
|
|
||||||
dirty: Cell::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal methods for implementation of locatable nodes.
|
|
||||||
impl PinBoard {
|
|
||||||
/// Access or create the next pin.
|
|
||||||
fn get_or_create(&mut self, group: Option<Group>, value: Option<Value>) -> Pin {
|
|
||||||
self.dirty.set(true);
|
|
||||||
if self.frozen() {
|
|
||||||
return Pin::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
let cursor = self.cursor;
|
|
||||||
self.cursor += 1;
|
|
||||||
if self.cursor >= self.list.len() {
|
|
||||||
self.list.resize(self.cursor, Pin::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
let pin = &mut self.list[cursor];
|
|
||||||
pin.group = group;
|
|
||||||
pin.value = value;
|
|
||||||
pin.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encode a group into a user-facing array.
|
|
||||||
fn encode_group(&self, group: &Group) -> Array {
|
|
||||||
self.dirty.set(true);
|
|
||||||
let mut all: Vec<_> = self.iter().filter(|pin| pin.is_in(group)).collect();
|
|
||||||
all.sort_by_key(|pin| pin.flow);
|
|
||||||
all.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, member)| Value::Dict(member.encode(Some(index))))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all pins on the board.
|
|
||||||
fn iter(&self) -> std::slice::Iter<Pin> {
|
|
||||||
self.dirty.set(true);
|
|
||||||
self.list.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Caching related methods.
|
|
||||||
impl PinBoard {
|
|
||||||
/// The current cursor.
|
|
||||||
pub fn cursor(&self) -> usize {
|
|
||||||
self.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
/// All pins from `prev` to the current cursor.
|
|
||||||
pub fn from(&self, prev: usize) -> Vec<Pin> {
|
|
||||||
self.list[prev .. self.cursor].to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add the given pins at the given location and set the cursor behind them.
|
|
||||||
pub fn replay(&mut self, at: usize, pins: Vec<Pin>) {
|
|
||||||
if !self.frozen() {
|
|
||||||
self.cursor = at + pins.len();
|
|
||||||
let end = self.cursor.min(self.list.len());
|
|
||||||
self.list.splice(at .. end, pins);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Control methods that are called during layout.
|
|
||||||
impl PinBoard {
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the board is currently frozen.
|
|
||||||
pub fn frozen(&self) -> bool {
|
|
||||||
self.frozen > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Methods that are called in between layout passes.
|
|
||||||
impl PinBoard {
|
|
||||||
/// Reset the cursor and remove all unused pins.
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.list.truncate(self.cursor);
|
|
||||||
self.cursor = 0;
|
|
||||||
self.dirty.set(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Locate all pins in the frames.
|
|
||||||
pub fn locate(&mut self, frames: &[Frame]) {
|
|
||||||
let mut flow = 0;
|
|
||||||
for (i, frame) in frames.iter().enumerate() {
|
|
||||||
locate_in_frame(
|
|
||||||
&mut self.list,
|
|
||||||
&mut flow,
|
|
||||||
NonZeroUsize::new(1 + i).unwrap(),
|
|
||||||
frame,
|
|
||||||
Transform::identity(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Locate all pins in a frame.
|
|
||||||
fn locate_in_frame(
|
|
||||||
pins: &mut [Pin],
|
|
||||||
flow: &mut usize,
|
|
||||||
page: NonZeroUsize,
|
|
||||||
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_in_frame(pins, flow, page, &group.frame, ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
Element::Pin(idx) => {
|
|
||||||
let pin = &mut pins[*idx];
|
|
||||||
pin.loc.page = page;
|
|
||||||
pin.loc.pos = pos.transform(ts);
|
|
||||||
pin.flow = *flow;
|
|
||||||
*flow += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for PinBoard {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.list.hash(state);
|
|
||||||
self.cursor.hash(state);
|
|
||||||
self.frozen.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes pin usage.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub struct PinConstraint(pub Option<u64>);
|
|
||||||
|
|
||||||
impl Track for PinBoard {
|
|
||||||
type Constraint = PinConstraint;
|
|
||||||
|
|
||||||
fn key<H: Hasher>(&self, _: &mut H) {}
|
|
||||||
|
|
||||||
fn matches(&self, constraint: &Self::Constraint) -> bool {
|
|
||||||
match constraint.0 {
|
|
||||||
Some(hash) => fxhash::hash64(self) == hash,
|
|
||||||
None => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A document pin.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
pub 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<Group>,
|
|
||||||
/// An arbitrary attached value.
|
|
||||||
value: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pin {
|
|
||||||
/// Whether the pin is part of the given group.
|
|
||||||
fn is_in(&self, group: &Group) -> bool {
|
|
||||||
self.group.as_ref() == Some(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encode into a user-facing dictionary.
|
|
||||||
fn encode(&self, index: Option<usize>) -> Dict {
|
|
||||||
let mut dict = self.loc.encode();
|
|
||||||
|
|
||||||
if let Some(value) = &self.value {
|
|
||||||
dict.insert("value".into(), value.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
|
||||||
dict.insert("index".into(), Value::Int(index as i64));
|
|
||||||
}
|
|
||||||
|
|
||||||
dict
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Pin {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
loc: Location {
|
|
||||||
page: NonZeroUsize::new(1).unwrap(),
|
|
||||||
pos: Point::zero(),
|
|
||||||
},
|
|
||||||
flow: 0,
|
|
||||||
group: None,
|
|
||||||
value: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ 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;
|
||||||
@ -13,7 +12,6 @@ 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::*;
|
||||||
|
@ -413,8 +413,6 @@ impl PartialEq for StyleChain<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_track_hash!(StyleChain<'_>);
|
|
||||||
|
|
||||||
/// An iterator over the values in a style chain.
|
/// An iterator over the values in a style chain.
|
||||||
struct Values<'a, K> {
|
struct Values<'a, K> {
|
||||||
entries: Entries<'a>,
|
entries: Entries<'a>,
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
// Test locate with crazy pagebreaks.
|
|
||||||
|
|
||||||
---
|
|
||||||
#set page(height: 10pt)
|
|
||||||
{3 * locate(me => me.page * pagebreak())}
|
|
@ -1,89 +0,0 @@
|
|||||||
// 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 in seen { continue }
|
|
||||||
(num(all, item.value), item.value)
|
|
||||||
seen.push(item.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
))}
|
|
||||||
|
|
||||||
As shown in #cite("abc") and #cite("def") and #cite("abc") ...
|
|
||||||
|
|
||||||
---
|
|
||||||
// Test lovely sidebar.
|
|
||||||
#let lovely = group("lovely")
|
|
||||||
#let words = ("Juliet", "soft", "fair", "maid")
|
|
||||||
#let regex = regex(words.map(p => "(" + p + ")").join("|"))
|
|
||||||
#show word: regex as underline(word) + lovely.entry(_ => {})
|
|
||||||
#set page(
|
|
||||||
paper: "a8",
|
|
||||||
margins: (left: 25pt, rest: 15pt),
|
|
||||||
foreground: lovely.all(entries => {
|
|
||||||
let seen = ()
|
|
||||||
for y in entries.map(it => it.y) {
|
|
||||||
if y in seen { continue }
|
|
||||||
let line = entries.filter(it => it.y == y)
|
|
||||||
for i, it in line {
|
|
||||||
let x = 10pt - 4pt * (line.len() - i - 1)
|
|
||||||
place(dx: x, dy: it.y - 8pt, [💗])
|
|
||||||
}
|
|
||||||
seen.push(y)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
But, soft! what light through yonder window breaks? It is the east, and Juliet
|
|
||||||
is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
|
|
||||||
pale with grief, That thou her maid art far more fair than she: Be not her maid,
|
|
||||||
since she is envious.
|
|
||||||
|
|
||||||
---
|
|
||||||
// Test that `all` contains `me`.
|
|
||||||
// Ref: false
|
|
||||||
#show it: heading as group("headings").entry(
|
|
||||||
(me, all) => {
|
|
||||||
let last
|
|
||||||
for prev in all {
|
|
||||||
last = prev
|
|
||||||
if prev.index == me.index {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(last == me)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
= A
|
|
||||||
== B
|
|
@ -1,22 +0,0 @@
|
|||||||
// 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