mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Measurement and introspection rework
This commit is contained in:
parent
c7f4d6b12e
commit
0ba99ab8aa
@ -118,8 +118,9 @@ compilation.
|
|||||||
typst --watch file.typ
|
typst --watch file.typ
|
||||||
```
|
```
|
||||||
|
|
||||||
If you prefer an integrated IDE-like experience, you can also check out the
|
If you prefer an integrated IDE-like experience with autocompletion and instant
|
||||||
[Typst web app][app], which is currently in public beta.
|
preview, you can also check out the [Typst web app][app], which is currently in
|
||||||
|
public beta.
|
||||||
|
|
||||||
## Build from source
|
## Build from source
|
||||||
To build Typst yourself, you need to have the [latest stable Rust][rust]
|
To build Typst yourself, you need to have the [latest stable Rust][rust]
|
||||||
|
@ -88,7 +88,7 @@ fantasy encyclopedia.
|
|||||||
#set text(font: "Inria Serif")
|
#set text(font: "Inria Serif")
|
||||||
\~ #emph(it.body)
|
\~ #emph(it.body)
|
||||||
#(counter(heading)
|
#(counter(heading)
|
||||||
.get(it.numbering)) \~
|
.display(it.numbering)) \~
|
||||||
]
|
]
|
||||||
|
|
||||||
= Dragon
|
= Dragon
|
||||||
|
20
library/src/layout/measure.rs
Normal file
20
library/src/layout/measure.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// Measure the size of content.
|
||||||
|
///
|
||||||
|
/// Display: Measure
|
||||||
|
/// Category: layout
|
||||||
|
/// Returns: array
|
||||||
|
#[func]
|
||||||
|
pub fn measure(
|
||||||
|
/// The content whose size to measure.
|
||||||
|
content: Content,
|
||||||
|
/// The styles with which to layout the content.
|
||||||
|
styles: StyleMap,
|
||||||
|
) -> Value {
|
||||||
|
let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false));
|
||||||
|
let styles = StyleChain::new(&styles);
|
||||||
|
let frame = content.measure(&mut vm.vt, styles, pod)?.into_frame();
|
||||||
|
let Size { x, y } = frame.size();
|
||||||
|
Value::Array(array![x, y])
|
||||||
|
}
|
@ -10,6 +10,7 @@ mod fragment;
|
|||||||
mod grid;
|
mod grid;
|
||||||
mod hide;
|
mod hide;
|
||||||
mod list;
|
mod list;
|
||||||
|
mod measure;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod page;
|
mod page;
|
||||||
mod par;
|
mod par;
|
||||||
@ -31,6 +32,7 @@ pub use self::fragment::*;
|
|||||||
pub use self::grid::*;
|
pub use self::grid::*;
|
||||||
pub use self::hide::*;
|
pub use self::hide::*;
|
||||||
pub use self::list::*;
|
pub use self::list::*;
|
||||||
|
pub use self::measure::*;
|
||||||
pub use self::pad::*;
|
pub use self::pad::*;
|
||||||
pub use self::page::*;
|
pub use self::page::*;
|
||||||
pub use self::par::*;
|
pub use self::par::*;
|
||||||
|
@ -2,7 +2,7 @@ use std::ptr;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::{AlignNode, ColumnsNode};
|
use super::{AlignNode, ColumnsNode};
|
||||||
use crate::meta::{Counter, CounterAction, CounterKey, CounterNode, Numbering};
|
use crate::meta::{Counter, CounterKey, Numbering};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
@ -214,8 +214,10 @@ pub struct PageNode {
|
|||||||
/// footer: [
|
/// footer: [
|
||||||
/// #set align(right)
|
/// #set align(right)
|
||||||
/// #set text(8pt)
|
/// #set text(8pt)
|
||||||
/// #counter(page).get("1") of
|
/// #counter(page).display(
|
||||||
/// #counter(page).final("I")
|
/// "1 of I",
|
||||||
|
/// both: true,
|
||||||
|
/// )
|
||||||
/// ]
|
/// ]
|
||||||
/// )
|
/// )
|
||||||
///
|
///
|
||||||
@ -311,11 +313,12 @@ impl PageNode {
|
|||||||
let header_ascent = self.header_ascent(styles);
|
let header_ascent = self.header_ascent(styles);
|
||||||
let footer = self.footer(styles).or_else(|| {
|
let footer = self.footer(styles).or_else(|| {
|
||||||
self.numbering(styles).map(|numbering| {
|
self.numbering(styles).map(|numbering| {
|
||||||
CounterNode::new(
|
let both = match &numbering {
|
||||||
Counter::new(CounterKey::Page),
|
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
|
||||||
CounterAction::Both(numbering),
|
Numbering::Func(_) => true,
|
||||||
)
|
};
|
||||||
.pack()
|
Counter::new(CounterKey::Page)
|
||||||
|
.display(numbering, both)
|
||||||
.aligned(self.number_align(styles))
|
.aligned(self.number_align(styles))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -74,6 +74,7 @@ fn global(math: Module, calc: Module) -> Module {
|
|||||||
global.define("scale", layout::ScaleNode::id());
|
global.define("scale", layout::ScaleNode::id());
|
||||||
global.define("rotate", layout::RotateNode::id());
|
global.define("rotate", layout::RotateNode::id());
|
||||||
global.define("hide", layout::HideNode::id());
|
global.define("hide", layout::HideNode::id());
|
||||||
|
global.define("measure", layout::measure);
|
||||||
|
|
||||||
// Visualize.
|
// Visualize.
|
||||||
global.define("image", visualize::ImageNode::id());
|
global.define("image", visualize::ImageNode::id());
|
||||||
@ -92,6 +93,8 @@ fn global(math: Module, calc: Module) -> Module {
|
|||||||
global.define("figure", meta::FigureNode::id());
|
global.define("figure", meta::FigureNode::id());
|
||||||
global.define("cite", meta::CiteNode::id());
|
global.define("cite", meta::CiteNode::id());
|
||||||
global.define("bibliography", meta::BibliographyNode::id());
|
global.define("bibliography", meta::BibliographyNode::id());
|
||||||
|
global.define("locate", meta::locate);
|
||||||
|
global.define("style", meta::style);
|
||||||
global.define("counter", meta::counter);
|
global.define("counter", meta::counter);
|
||||||
global.define("numbering", meta::numbering);
|
global.define("numbering", meta::numbering);
|
||||||
global.define("state", meta::state);
|
global.define("state", meta::state);
|
||||||
@ -228,11 +231,11 @@ fn items() -> LangItems {
|
|||||||
math::AccentNode::new(base, math::Accent::new(accent)).pack()
|
math::AccentNode::new(base, math::Accent::new(accent)).pack()
|
||||||
},
|
},
|
||||||
math_frac: |num, denom| math::FracNode::new(num, denom).pack(),
|
math_frac: |num, denom| math::FracNode::new(num, denom).pack(),
|
||||||
library_method: |dynamic, method, args, span| {
|
library_method: |vm, dynamic, method, args, span| {
|
||||||
if let Some(counter) = dynamic.downcast().cloned() {
|
if let Some(counter) = dynamic.downcast::<meta::Counter>().cloned() {
|
||||||
meta::counter_method(counter, method, args, span)
|
counter.call_method(vm, method, args, span)
|
||||||
} else if let Some(state) = dynamic.downcast().cloned() {
|
} else if let Some(state) = dynamic.downcast::<meta::State>().cloned() {
|
||||||
meta::state_method(state, method, args, span)
|
state.call_method(vm, method, args, span)
|
||||||
} else {
|
} else {
|
||||||
Err(format!("type {} has no method `{method}`", dynamic.type_name()))
|
Err(format!("type {} has no method `{method}`", dynamic.type_name()))
|
||||||
.at(span)
|
.at(span)
|
||||||
|
@ -39,9 +39,7 @@ use self::fragment::*;
|
|||||||
use self::row::*;
|
use self::row::*;
|
||||||
use self::spacing::*;
|
use self::spacing::*;
|
||||||
use crate::layout::{HNode, ParNode, Spacing};
|
use crate::layout::{HNode, ParNode, Spacing};
|
||||||
use crate::meta::{
|
use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering};
|
||||||
Count, Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering,
|
|
||||||
};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
families, variant, FontFamily, FontList, LinebreakNode, SpaceNode, TextNode, TextSize,
|
families, variant, FontFamily, FontList, LinebreakNode, SpaceNode, TextNode, TextSize,
|
||||||
@ -217,29 +215,29 @@ impl Layout for EquationNode {
|
|||||||
if block {
|
if block {
|
||||||
if let Some(numbering) = self.numbering(styles) {
|
if let Some(numbering) = self.numbering(styles) {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let counter = CounterNode::new(
|
let counter = Counter::of(Self::id())
|
||||||
Counter::of(Self::id()),
|
.display(numbering, false)
|
||||||
CounterAction::Get(numbering),
|
.layout(vt, styles, pod)?
|
||||||
);
|
.into_frame();
|
||||||
|
|
||||||
let sub = counter.pack().layout(vt, styles, pod)?.into_frame();
|
|
||||||
let width = if regions.size.x.is_finite() {
|
let width = if regions.size.x.is_finite() {
|
||||||
regions.size.x
|
regions.size.x
|
||||||
} else {
|
} else {
|
||||||
frame.width() + 2.0 * (sub.width() + NUMBER_GUTTER.resolve(styles))
|
frame.width()
|
||||||
|
+ 2.0 * (counter.width() + NUMBER_GUTTER.resolve(styles))
|
||||||
};
|
};
|
||||||
|
|
||||||
let height = frame.height().max(sub.height());
|
let height = frame.height().max(counter.height());
|
||||||
frame.resize(Size::new(width, height), Align::CENTER_HORIZON);
|
frame.resize(Size::new(width, height), Align::CENTER_HORIZON);
|
||||||
|
|
||||||
let x = if TextNode::dir_in(styles).is_positive() {
|
let x = if TextNode::dir_in(styles).is_positive() {
|
||||||
frame.width() - sub.width()
|
frame.width() - counter.width()
|
||||||
} else {
|
} else {
|
||||||
Abs::zero()
|
Abs::zero()
|
||||||
};
|
};
|
||||||
let y = (frame.height() - sub.height()) / 2.0;
|
let y = (frame.height() - counter.height()) / 2.0;
|
||||||
|
|
||||||
frame.push_frame(Point::new(x, y), sub)
|
frame.push_frame(Point::new(x, y), counter)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let slack = ParNode::leading_in(styles) * 0.7;
|
let slack = ParNode::leading_in(styles) * 0.7;
|
||||||
|
66
library/src/meta/context.rs
Normal file
66
library/src/meta/context.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// Provide access to the location of content.
|
||||||
|
///
|
||||||
|
/// Display: Locate
|
||||||
|
/// Category: meta
|
||||||
|
/// Returns: content
|
||||||
|
#[func]
|
||||||
|
pub fn locate(
|
||||||
|
/// The function to call with the location.
|
||||||
|
func: Func,
|
||||||
|
) -> Value {
|
||||||
|
LocateNode::new(func).pack().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a `locate` call.
|
||||||
|
///
|
||||||
|
/// Display: Styled
|
||||||
|
/// Category: special
|
||||||
|
#[node(Locatable, Show)]
|
||||||
|
struct LocateNode {
|
||||||
|
/// The function to call with the location.
|
||||||
|
#[required]
|
||||||
|
func: Func,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for LocateNode {
|
||||||
|
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
|
if !vt.introspector.init() {
|
||||||
|
return Ok(Content::empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = self.0.stable_id().unwrap();
|
||||||
|
Ok(self.func().call_vt(vt, [id.into()])?.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide access to active styles.
|
||||||
|
///
|
||||||
|
/// Display: Styled
|
||||||
|
/// Category: layout
|
||||||
|
/// Returns: content
|
||||||
|
#[func]
|
||||||
|
pub fn style(
|
||||||
|
/// The function to call with the styles.
|
||||||
|
func: Func,
|
||||||
|
) -> Value {
|
||||||
|
StyleNode::new(func).pack().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a style access.
|
||||||
|
///
|
||||||
|
/// Display: Style
|
||||||
|
/// Category: special
|
||||||
|
#[node(Show)]
|
||||||
|
struct StyleNode {
|
||||||
|
/// The function to call with the styles.
|
||||||
|
#[required]
|
||||||
|
func: Func,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for StyleNode {
|
||||||
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
|
Ok(self.func().call_vt(vt, [styles.to_map().into()])?.display())
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,192 @@ pub fn counter(
|
|||||||
Value::dynamic(Counter::new(key))
|
Value::dynamic(Counter::new(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Counts through pages, elements, and more.
|
||||||
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
|
pub struct Counter(CounterKey);
|
||||||
|
|
||||||
|
impl Counter {
|
||||||
|
/// Create a new counter from a key.
|
||||||
|
pub fn new(key: CounterKey) -> Self {
|
||||||
|
Self(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The counter for the given node.
|
||||||
|
pub fn of(id: NodeId) -> Self {
|
||||||
|
Self::new(CounterKey::Selector(Selector::Node(id, None)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a method on counter.
|
||||||
|
pub fn call_method(
|
||||||
|
self,
|
||||||
|
vm: &mut Vm,
|
||||||
|
method: &str,
|
||||||
|
mut args: Args,
|
||||||
|
span: Span,
|
||||||
|
) -> SourceResult<Value> {
|
||||||
|
let pattern = |s| NumberingPattern::from_str(s).unwrap().into();
|
||||||
|
let value = match method {
|
||||||
|
"display" => self
|
||||||
|
.display(
|
||||||
|
args.eat()?.unwrap_or_else(|| pattern("1.1")),
|
||||||
|
args.named("both")?.unwrap_or(false),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
"at" => self.at(&mut vm.vt, args.expect("location")?)?.into(),
|
||||||
|
"final" => self.final_(&mut vm.vt, args.expect("location")?)?.into(),
|
||||||
|
"update" => self.update(args.expect("value or function")?).into(),
|
||||||
|
"step" => self
|
||||||
|
.update(CounterUpdate::Step(
|
||||||
|
args.named("level")?.unwrap_or(NonZeroUsize::ONE),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
_ => bail!(span, "type counter has no method `{}`", method),
|
||||||
|
};
|
||||||
|
args.finish()?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display the current value of the counter.
|
||||||
|
pub fn display(self, numbering: Numbering, both: bool) -> Content {
|
||||||
|
DisplayNode::new(self, numbering, both).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the state at the given location.
|
||||||
|
pub fn at(&self, vt: &mut Vt, id: StableId) -> SourceResult<CounterState> {
|
||||||
|
let sequence = self.sequence(vt)?;
|
||||||
|
let offset = vt.introspector.query_before(self.selector(), id).len();
|
||||||
|
let (mut state, page) = sequence[offset].clone();
|
||||||
|
if self.is_page() {
|
||||||
|
let delta = vt.introspector.page(id).get() - page.get();
|
||||||
|
state.step(NonZeroUsize::ONE, delta);
|
||||||
|
}
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the state at the final location.
|
||||||
|
pub fn final_(&self, vt: &mut Vt, _: StableId) -> SourceResult<CounterState> {
|
||||||
|
let sequence = self.sequence(vt)?;
|
||||||
|
let (mut state, page) = sequence.last().unwrap().clone();
|
||||||
|
if self.is_page() {
|
||||||
|
let delta = vt.introspector.pages().get() - page.get();
|
||||||
|
state.step(NonZeroUsize::ONE, delta);
|
||||||
|
}
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current and final value of the state combined in one state.
|
||||||
|
pub fn both(&self, vt: &mut Vt, id: StableId) -> SourceResult<CounterState> {
|
||||||
|
let sequence = self.sequence(vt)?;
|
||||||
|
let offset = vt.introspector.query_before(self.selector(), id).len();
|
||||||
|
let (mut at_state, at_page) = sequence[offset].clone();
|
||||||
|
let (mut final_state, final_page) = sequence.last().unwrap().clone();
|
||||||
|
if self.is_page() {
|
||||||
|
let at_delta = vt.introspector.page(id).get() - at_page.get();
|
||||||
|
at_state.step(NonZeroUsize::ONE, at_delta);
|
||||||
|
let final_delta = vt.introspector.pages().get() - final_page.get();
|
||||||
|
final_state.step(NonZeroUsize::ONE, final_delta);
|
||||||
|
}
|
||||||
|
Ok(CounterState(smallvec![at_state.first(), final_state.first()]))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produce content that performs a state update.
|
||||||
|
pub fn update(self, update: CounterUpdate) -> Content {
|
||||||
|
UpdateNode::new(self, update).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produce the whole sequence of counter states.
|
||||||
|
///
|
||||||
|
/// This has to happen just once for all counters, cutting down the number
|
||||||
|
/// of counter updates from quadratic to linear.
|
||||||
|
fn sequence(
|
||||||
|
&self,
|
||||||
|
vt: &mut Vt,
|
||||||
|
) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
|
||||||
|
self.sequence_impl(
|
||||||
|
vt.world,
|
||||||
|
TrackedMut::reborrow_mut(&mut vt.tracer),
|
||||||
|
TrackedMut::reborrow_mut(&mut vt.provider),
|
||||||
|
vt.introspector,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Memoized implementation of `sequence`.
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn sequence_impl(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
tracer: TrackedMut<Tracer>,
|
||||||
|
provider: TrackedMut<StabilityProvider>,
|
||||||
|
introspector: Tracked<Introspector>,
|
||||||
|
) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
|
||||||
|
let mut vt = Vt { world, tracer, provider, introspector };
|
||||||
|
let mut state = CounterState(match &self.0 {
|
||||||
|
CounterKey::Selector(_) => smallvec![],
|
||||||
|
_ => smallvec![NonZeroUsize::ONE],
|
||||||
|
});
|
||||||
|
let mut page = NonZeroUsize::ONE;
|
||||||
|
let mut stops = eco_vec![(state.clone(), page)];
|
||||||
|
|
||||||
|
for node in introspector.query(self.selector()) {
|
||||||
|
if self.is_page() {
|
||||||
|
let id = node.stable_id().unwrap();
|
||||||
|
let prev = page;
|
||||||
|
page = introspector.page(id);
|
||||||
|
|
||||||
|
let delta = page.get() - prev.get();
|
||||||
|
if delta > 0 {
|
||||||
|
state.step(NonZeroUsize::ONE, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(update) = match node.to::<UpdateNode>() {
|
||||||
|
Some(node) => Some(node.update()),
|
||||||
|
None => match node.with::<dyn Count>() {
|
||||||
|
Some(countable) => countable.update(),
|
||||||
|
None => Some(CounterUpdate::Step(NonZeroUsize::ONE)),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
state.update(&mut vt, update)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
stops.push((state.clone(), page));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(stops)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The selector relevant for this counter's updates.
|
||||||
|
fn selector(&self) -> Selector {
|
||||||
|
let mut selector = Selector::Node(
|
||||||
|
NodeId::of::<UpdateNode>(),
|
||||||
|
Some(dict! { "counter" => self.clone() }),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let CounterKey::Selector(key) = &self.0 {
|
||||||
|
selector = Selector::Any(eco_vec![selector, key.clone()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
selector
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this is the page counter.
|
||||||
|
fn is_page(&self) -> bool {
|
||||||
|
self.0 == CounterKey::Page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Counter {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_str("counter(")?;
|
||||||
|
self.0.fmt(f)?;
|
||||||
|
f.write_char(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
Counter: "counter",
|
||||||
|
}
|
||||||
|
|
||||||
/// Identifies a counter.
|
/// Identifies a counter.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub enum CounterKey {
|
pub enum CounterKey {
|
||||||
@ -65,118 +251,8 @@ impl Debug for CounterKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a method on counter.
|
|
||||||
pub fn counter_method(
|
|
||||||
counter: Counter,
|
|
||||||
method: &str,
|
|
||||||
mut args: Args,
|
|
||||||
span: Span,
|
|
||||||
) -> SourceResult<Value> {
|
|
||||||
let pattern = |s| NumberingPattern::from_str(s).unwrap().into();
|
|
||||||
let action = match method {
|
|
||||||
"get" => CounterAction::Get(args.eat()?.unwrap_or_else(|| pattern("1.1"))),
|
|
||||||
"final" => CounterAction::Final(args.eat()?.unwrap_or_else(|| pattern("1.1"))),
|
|
||||||
"both" => CounterAction::Both(args.eat()?.unwrap_or_else(|| pattern("1/1"))),
|
|
||||||
"step" => CounterAction::Update(CounterUpdate::Step(
|
|
||||||
args.named("level")?.unwrap_or(NonZeroUsize::ONE),
|
|
||||||
)),
|
|
||||||
"update" => CounterAction::Update(args.expect("value or function")?),
|
|
||||||
_ => bail!(span, "type counter has no method `{}`", method),
|
|
||||||
};
|
|
||||||
|
|
||||||
args.finish()?;
|
|
||||||
|
|
||||||
let content = CounterNode::new(counter, action).pack();
|
|
||||||
Ok(Value::Content(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes an action on a counter.
|
|
||||||
///
|
|
||||||
/// Display: Counter
|
|
||||||
/// Category: special
|
|
||||||
#[node(Locatable, Show)]
|
|
||||||
pub struct CounterNode {
|
|
||||||
/// The counter key.
|
|
||||||
#[required]
|
|
||||||
pub counter: Counter,
|
|
||||||
|
|
||||||
/// The action.
|
|
||||||
#[required]
|
|
||||||
pub action: CounterAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for CounterNode {
|
|
||||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
match self.action() {
|
|
||||||
CounterAction::Get(numbering) => {
|
|
||||||
self.counter().resolve(vt, self.0.stable_id(), &numbering)
|
|
||||||
}
|
|
||||||
CounterAction::Final(numbering) => {
|
|
||||||
self.counter().resolve(vt, None, &numbering)
|
|
||||||
}
|
|
||||||
CounterAction::Both(numbering) => {
|
|
||||||
let both = match &numbering {
|
|
||||||
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let counter = self.counter();
|
|
||||||
let id = self.0.stable_id();
|
|
||||||
if !both {
|
|
||||||
return counter.resolve(vt, id, &numbering);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sequence = counter.sequence(
|
|
||||||
vt.world,
|
|
||||||
TrackedMut::reborrow_mut(&mut vt.tracer),
|
|
||||||
TrackedMut::reborrow_mut(&mut vt.provider),
|
|
||||||
vt.introspector,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(match (sequence.single(id), sequence.single(None)) {
|
|
||||||
(Some(current), Some(total)) => {
|
|
||||||
numbering.apply_vt(vt, &[current, total])?.display()
|
|
||||||
}
|
|
||||||
_ => Content::empty(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
CounterAction::Update(_) => Ok(Content::empty()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The action to perform on a counter.
|
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
|
||||||
pub enum CounterAction {
|
|
||||||
/// Displays the current value.
|
|
||||||
Get(Numbering),
|
|
||||||
/// Displays the final value.
|
|
||||||
Final(Numbering),
|
|
||||||
/// If given a pattern with at least two parts, displays the current value
|
|
||||||
/// together with the final value. Otherwise, displays just the current
|
|
||||||
/// value.
|
|
||||||
Both(Numbering),
|
|
||||||
/// Updates the value, possibly based on the previous one.
|
|
||||||
Update(CounterUpdate),
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_from_value! {
|
|
||||||
CounterAction: "counter action",
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for CounterAction {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Get(_) => f.pad("get(..)"),
|
|
||||||
Self::Final(_) => f.pad("final(..)"),
|
|
||||||
Self::Both(_) => f.pad("both(..)"),
|
|
||||||
Self::Update(_) => f.pad("update(..)"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An update to perform on a counter.
|
/// An update to perform on a counter.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub enum CounterUpdate {
|
pub enum CounterUpdate {
|
||||||
/// Set the counter to the specified state.
|
/// Set the counter to the specified state.
|
||||||
Set(CounterState),
|
Set(CounterState),
|
||||||
@ -186,8 +262,14 @@ pub enum CounterUpdate {
|
|||||||
Func(Func),
|
Func(Func),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for CounterUpdate {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad("..")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
CounterUpdate,
|
CounterUpdate: "counter update",
|
||||||
v: CounterState => Self::Set(v),
|
v: CounterState => Self::Set(v),
|
||||||
v: Func => Self::Func(v),
|
v: Func => Self::Func(v),
|
||||||
}
|
}
|
||||||
@ -198,153 +280,6 @@ pub trait Count {
|
|||||||
fn update(&self) -> Option<CounterUpdate>;
|
fn update(&self) -> Option<CounterUpdate>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counts through pages, elements, and more.
|
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
|
||||||
pub struct Counter {
|
|
||||||
/// The key that identifies the counter.
|
|
||||||
pub key: CounterKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Counter {
|
|
||||||
/// Create a new counter from a key.
|
|
||||||
pub fn new(key: CounterKey) -> Self {
|
|
||||||
Self { key }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The counter for the given node.
|
|
||||||
pub fn of(id: NodeId) -> Self {
|
|
||||||
Self::new(CounterKey::Selector(Selector::Node(id, None)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Display the value of the counter at the postition of the given stable
|
|
||||||
/// id.
|
|
||||||
pub fn resolve(
|
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
stop: Option<StableId>,
|
|
||||||
numbering: &Numbering,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
if !vt.introspector.init() {
|
|
||||||
return Ok(Content::empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
let sequence = self.sequence(
|
|
||||||
vt.world,
|
|
||||||
TrackedMut::reborrow_mut(&mut vt.tracer),
|
|
||||||
TrackedMut::reborrow_mut(&mut vt.provider),
|
|
||||||
vt.introspector,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(match sequence.at(stop) {
|
|
||||||
Some(state) => numbering.apply_vt(vt, &state.0)?.display(),
|
|
||||||
None => Content::empty(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produce the whole sequence of counter states.
|
|
||||||
///
|
|
||||||
/// This has to happen just once for all counters, cutting down the number
|
|
||||||
/// of counter updates from quadratic to linear.
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn sequence(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
tracer: TrackedMut<Tracer>,
|
|
||||||
provider: TrackedMut<StabilityProvider>,
|
|
||||||
introspector: Tracked<Introspector>,
|
|
||||||
) -> SourceResult<CounterSequence> {
|
|
||||||
let mut vt = Vt { world, tracer, provider, introspector };
|
|
||||||
let mut search = Selector::Node(
|
|
||||||
NodeId::of::<CounterNode>(),
|
|
||||||
Some(dict! { "counter" => self.clone() }),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let CounterKey::Selector(selector) = &self.key {
|
|
||||||
search = Selector::Any(eco_vec![search, selector.clone()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut stops = EcoVec::new();
|
|
||||||
let mut state = CounterState(match &self.key {
|
|
||||||
CounterKey::Selector(_) => smallvec![],
|
|
||||||
_ => smallvec![NonZeroUsize::ONE],
|
|
||||||
});
|
|
||||||
|
|
||||||
let is_page = self.key == CounterKey::Page;
|
|
||||||
let mut prev_page = NonZeroUsize::ONE;
|
|
||||||
|
|
||||||
for node in introspector.query(search) {
|
|
||||||
let id = node.stable_id().unwrap();
|
|
||||||
if is_page {
|
|
||||||
let page = introspector.page(id);
|
|
||||||
let delta = page.get() - prev_page.get();
|
|
||||||
if delta > 0 {
|
|
||||||
state.step(NonZeroUsize::ONE, delta);
|
|
||||||
}
|
|
||||||
prev_page = page;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(update) = match node.to::<CounterNode>() {
|
|
||||||
Some(counter) => match counter.action() {
|
|
||||||
CounterAction::Update(update) => Some(update),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
None => match node.with::<dyn Count>() {
|
|
||||||
Some(countable) => countable.update(),
|
|
||||||
None => Some(CounterUpdate::Step(NonZeroUsize::ONE)),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
state.update(&mut vt, update)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
stops.push((id, state.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(CounterSequence { stops, is_page })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Counter {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.write_str("counter(")?;
|
|
||||||
self.key.fmt(f)?;
|
|
||||||
f.write_char(')')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_from_value! {
|
|
||||||
Counter: "counter",
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sequence of counter values.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct CounterSequence {
|
|
||||||
stops: EcoVec<(StableId, CounterState)>,
|
|
||||||
is_page: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CounterSequence {
|
|
||||||
fn at(&self, stop: Option<StableId>) -> Option<CounterState> {
|
|
||||||
let entry = match stop {
|
|
||||||
Some(stop) => self.stops.iter().find(|&&(id, _)| id == stop),
|
|
||||||
None => self.stops.last(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((_, state)) = entry {
|
|
||||||
return Some(state.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.is_page {
|
|
||||||
return Some(CounterState(smallvec![NonZeroUsize::ONE]));
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn single(&self, stop: Option<StableId>) -> Option<NonZeroUsize> {
|
|
||||||
Some(*self.at(stop)?.0.first()?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Counts through elements with different levels.
|
/// Counts through elements with different levels.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub struct CounterState(pub SmallVec<[NonZeroUsize; 3]>);
|
pub struct CounterState(pub SmallVec<[NonZeroUsize; 3]>);
|
||||||
@ -378,6 +313,16 @@ impl CounterState {
|
|||||||
self.0.push(NonZeroUsize::ONE);
|
self.0.push(NonZeroUsize::ONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the first number of the state.
|
||||||
|
pub fn first(&self) -> NonZeroUsize {
|
||||||
|
self.0.first().copied().unwrap_or(NonZeroUsize::ONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display the counter state with a numbering.
|
||||||
|
pub fn display(&self, vt: &mut Vt, numbering: &Numbering) -> SourceResult<Content> {
|
||||||
|
Ok(numbering.apply_vt(vt, &self.0)?.display())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
@ -388,3 +333,57 @@ cast_from_value! {
|
|||||||
.map(Value::cast)
|
.map(Value::cast)
|
||||||
.collect::<StrResult<_>>()?),
|
.collect::<StrResult<_>>()?),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: CounterState => Value::Array(v.0.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a display of a state.
|
||||||
|
///
|
||||||
|
/// Display: State
|
||||||
|
/// Category: special
|
||||||
|
#[node(Locatable, Show)]
|
||||||
|
struct DisplayNode {
|
||||||
|
/// The counter.
|
||||||
|
#[required]
|
||||||
|
counter: Counter,
|
||||||
|
|
||||||
|
/// The numbering to display the counter with.
|
||||||
|
#[required]
|
||||||
|
numbering: Numbering,
|
||||||
|
|
||||||
|
/// Whether to display both the current and final value.
|
||||||
|
#[required]
|
||||||
|
both: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for DisplayNode {
|
||||||
|
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
|
let id = self.0.stable_id().unwrap();
|
||||||
|
let counter = self.counter();
|
||||||
|
let numbering = self.numbering();
|
||||||
|
let state = if self.both() { counter.both(vt, id) } else { counter.at(vt, id) }?;
|
||||||
|
state.display(vt, &numbering)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a display of a state.
|
||||||
|
///
|
||||||
|
/// Display: State
|
||||||
|
/// Category: special
|
||||||
|
#[node(Locatable, Show)]
|
||||||
|
struct UpdateNode {
|
||||||
|
/// The counter.
|
||||||
|
#[required]
|
||||||
|
counter: Counter,
|
||||||
|
|
||||||
|
/// The update to perform on the counter.
|
||||||
|
#[required]
|
||||||
|
update: CounterUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for UpdateNode {
|
||||||
|
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
|
Ok(Content::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::{
|
use super::{Count, Counter, CounterUpdate, LocalName, Numbering, NumberingPattern};
|
||||||
Count, Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering,
|
|
||||||
NumberingPattern,
|
|
||||||
};
|
|
||||||
use crate::layout::{BlockNode, VNode};
|
use crate::layout::{BlockNode, VNode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::TextNode;
|
use crate::text::TextNode;
|
||||||
@ -59,11 +56,8 @@ impl Show for FigureNode {
|
|||||||
if let Some(numbering) = self.numbering(styles) {
|
if let Some(numbering) = self.numbering(styles) {
|
||||||
let name = self.local_name(TextNode::lang_in(styles));
|
let name = self.local_name(TextNode::lang_in(styles));
|
||||||
caption = TextNode::packed(eco_format!("{name}\u{a0}"))
|
caption = TextNode::packed(eco_format!("{name}\u{a0}"))
|
||||||
+ CounterNode::new(
|
+ Counter::of(Self::id())
|
||||||
Counter::of(Self::id()),
|
.display(numbering, false)
|
||||||
CounterAction::Get(numbering),
|
|
||||||
)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span())
|
.spanned(self.span())
|
||||||
+ TextNode::packed(": ")
|
+ TextNode::packed(": ")
|
||||||
+ caption;
|
+ caption;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use typst::font::FontWeight;
|
use typst::font::FontWeight;
|
||||||
|
|
||||||
use super::{Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering};
|
use super::{Counter, CounterUpdate, LocalName, Numbering};
|
||||||
use crate::layout::{BlockNode, HNode, VNode};
|
use crate::layout::{BlockNode, HNode, VNode};
|
||||||
use crate::meta::Count;
|
use crate::meta::Count;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -92,9 +92,7 @@ impl Show for HeadingNode {
|
|||||||
let mut realized = self.body();
|
let mut realized = self.body();
|
||||||
if let Some(numbering) = self.numbering(styles) {
|
if let Some(numbering) = self.numbering(styles) {
|
||||||
realized =
|
realized =
|
||||||
CounterNode::new(Counter::of(Self::id()), CounterAction::Get(numbering))
|
Counter::of(Self::id()).display(numbering, false).spanned(self.span())
|
||||||
.pack()
|
|
||||||
.spanned(self.span())
|
|
||||||
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
||||||
+ realized;
|
+ realized;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Interaction between document parts.
|
//! Interaction between document parts.
|
||||||
|
|
||||||
mod bibliography;
|
mod bibliography;
|
||||||
|
mod context;
|
||||||
mod counter;
|
mod counter;
|
||||||
mod document;
|
mod document;
|
||||||
mod figure;
|
mod figure;
|
||||||
@ -13,6 +14,7 @@ mod reference;
|
|||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub use self::bibliography::*;
|
pub use self::bibliography::*;
|
||||||
|
pub use self::context::*;
|
||||||
pub use self::counter::*;
|
pub use self::counter::*;
|
||||||
pub use self::document::*;
|
pub use self::document::*;
|
||||||
pub use self::figure::*;
|
pub use self::figure::*;
|
||||||
|
@ -120,11 +120,9 @@ impl Show for OutlineNode {
|
|||||||
let mut hidden = Content::empty();
|
let mut hidden = Content::empty();
|
||||||
for ancestor in &ancestors {
|
for ancestor in &ancestors {
|
||||||
if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
|
if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
|
||||||
let numbers = Counter::of(HeadingNode::id()).resolve(
|
let numbers = Counter::of(HeadingNode::id())
|
||||||
vt,
|
.at(vt, ancestor.0.stable_id().unwrap())?
|
||||||
ancestor.0.stable_id(),
|
.display(vt, &numbering)?;
|
||||||
&numbering,
|
|
||||||
)?;
|
|
||||||
hidden += numbers + SpaceNode::new().pack();
|
hidden += numbers + SpaceNode::new().pack();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -138,11 +136,9 @@ impl Show for OutlineNode {
|
|||||||
// Format the numbering.
|
// Format the numbering.
|
||||||
let mut start = heading.body();
|
let mut start = heading.body();
|
||||||
if let Some(numbering) = heading.numbering(StyleChain::default()) {
|
if let Some(numbering) = heading.numbering(StyleChain::default()) {
|
||||||
let numbers = Counter::of(HeadingNode::id()).resolve(
|
let numbers = Counter::of(HeadingNode::id())
|
||||||
vt,
|
.at(vt, stable_id)?
|
||||||
Some(stable_id),
|
.display(vt, &numbering)?;
|
||||||
&numbering,
|
|
||||||
)?;
|
|
||||||
start = numbers + SpaceNode::new().pack() + start;
|
start = numbers + SpaceNode::new().pack() + start;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,10 +9,29 @@ use crate::prelude::*;
|
|||||||
pub fn query(
|
pub fn query(
|
||||||
/// The thing to search for.
|
/// The thing to search for.
|
||||||
target: Target,
|
target: Target,
|
||||||
/// A function to format the results with.
|
/// The location.
|
||||||
format: Func,
|
#[external]
|
||||||
|
location: StableId,
|
||||||
|
/// The location before which to query.
|
||||||
|
#[named]
|
||||||
|
#[external]
|
||||||
|
before: StableId,
|
||||||
|
/// The location after which to query.
|
||||||
|
#[named]
|
||||||
|
#[external]
|
||||||
|
after: StableId,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
QueryNode::new(target.0, format).pack().into()
|
let selector = target.0;
|
||||||
|
let introspector = vm.vt.introspector;
|
||||||
|
let elements = if let Some(id) = args.named("before")? {
|
||||||
|
introspector.query_before(selector, id)
|
||||||
|
} else if let Some(id) = args.named("after")? {
|
||||||
|
introspector.query_after(selector, id)
|
||||||
|
} else {
|
||||||
|
let _: StableId = args.expect("id")?;
|
||||||
|
introspector.query(selector)
|
||||||
|
};
|
||||||
|
elements.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A query target.
|
/// A query target.
|
||||||
@ -33,31 +52,3 @@ cast_from_value! {
|
|||||||
Self(Selector::Node(id, None))
|
Self(Selector::Node(id, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a query.
|
|
||||||
///
|
|
||||||
/// Display: Query
|
|
||||||
/// Category: special
|
|
||||||
#[node(Locatable, Show)]
|
|
||||||
struct QueryNode {
|
|
||||||
/// The thing to search for.
|
|
||||||
#[required]
|
|
||||||
target: Selector,
|
|
||||||
|
|
||||||
/// The function to format the results with.
|
|
||||||
#[required]
|
|
||||||
format: Func,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for QueryNode {
|
|
||||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
if !vt.introspector.init() {
|
|
||||||
return Ok(Content::empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = self.0.stable_id().unwrap();
|
|
||||||
let target = self.target();
|
|
||||||
let (before, after) = vt.introspector.query_split(target, id);
|
|
||||||
Ok(self.format().call_vt(vt, [before.into(), after.into()])?.display())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -114,8 +114,9 @@ impl Show for RefNode {
|
|||||||
bail!(self.span(), "only numbered elements can be referenced");
|
bail!(self.span(), "only numbered elements can be referenced");
|
||||||
};
|
};
|
||||||
|
|
||||||
let numbers =
|
let numbers = Counter::of(node.id())
|
||||||
Counter::of(node.id()).resolve(vt, node.stable_id(), &numbering.trimmed())?;
|
.at(vt, node.stable_id().unwrap())?
|
||||||
|
.display(vt, &numbering.trimmed())?;
|
||||||
|
|
||||||
Ok((supplement + numbers).linked(Link::Node(node.stable_id().unwrap())))
|
Ok((supplement + numbers).linked(Link::Node(node.stable_id().unwrap())))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
|
|
||||||
use ecow::EcoVec;
|
use ecow::{eco_vec, EcoVec};
|
||||||
use typst::eval::Tracer;
|
use typst::eval::Tracer;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -21,91 +21,6 @@ pub fn state(
|
|||||||
Value::dynamic(State { key, init })
|
Value::dynamic(State { key, init })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a method on a state.
|
|
||||||
pub fn state_method(
|
|
||||||
state: State,
|
|
||||||
method: &str,
|
|
||||||
mut args: Args,
|
|
||||||
span: Span,
|
|
||||||
) -> SourceResult<Value> {
|
|
||||||
let action = match method {
|
|
||||||
"get" => StateAction::Get(args.eat()?),
|
|
||||||
"final" => StateAction::Final(args.eat()?),
|
|
||||||
"update" => StateAction::Update(args.expect("value or function")?),
|
|
||||||
_ => bail!(span, "type state has no method `{}`", method),
|
|
||||||
};
|
|
||||||
|
|
||||||
args.finish()?;
|
|
||||||
|
|
||||||
let content = StateNode::new(state, action).pack();
|
|
||||||
Ok(Value::Content(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes an action on a state.
|
|
||||||
///
|
|
||||||
/// Display: State
|
|
||||||
/// Category: special
|
|
||||||
#[node(Locatable, Show)]
|
|
||||||
pub struct StateNode {
|
|
||||||
/// The state.
|
|
||||||
#[required]
|
|
||||||
pub state: State,
|
|
||||||
|
|
||||||
/// The action.
|
|
||||||
#[required]
|
|
||||||
pub action: StateAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for StateNode {
|
|
||||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
match self.action() {
|
|
||||||
StateAction::Get(func) => self.state().resolve(vt, self.0.stable_id(), func),
|
|
||||||
StateAction::Final(func) => self.state().resolve(vt, None, func),
|
|
||||||
StateAction::Update(_) => Ok(Content::empty()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The action to perform on the state.
|
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
|
||||||
pub enum StateAction {
|
|
||||||
/// Displays the current state.
|
|
||||||
Get(Option<Func>),
|
|
||||||
/// Displays the final state.
|
|
||||||
Final(Option<Func>),
|
|
||||||
/// Updates the state, possibly based on the previous one.
|
|
||||||
Update(StateUpdate),
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_from_value! {
|
|
||||||
StateAction: "state action",
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for StateAction {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Get(_) => f.pad("get(..)"),
|
|
||||||
Self::Final(_) => f.pad("final(..)"),
|
|
||||||
Self::Update(_) => f.pad("update(..)"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An update to perform on a state.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
pub enum StateUpdate {
|
|
||||||
/// Set the state to the specified value.
|
|
||||||
Set(Value),
|
|
||||||
/// Apply the given function to the state.
|
|
||||||
Func(Func),
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_from_value! {
|
|
||||||
StateUpdate,
|
|
||||||
v: Func => Self::Func(v),
|
|
||||||
v: Value => Self::Set(v),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A state.
|
/// A state.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
@ -116,72 +31,92 @@ pub struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Display the state at the postition of the given stable id.
|
/// Call a method on a state.
|
||||||
fn resolve(
|
pub fn call_method(
|
||||||
&self,
|
self,
|
||||||
vt: &mut Vt,
|
vm: &mut Vm,
|
||||||
stop: Option<StableId>,
|
method: &str,
|
||||||
func: Option<Func>,
|
mut args: Args,
|
||||||
) -> SourceResult<Content> {
|
span: Span,
|
||||||
if !vt.introspector.init() {
|
) -> SourceResult<Value> {
|
||||||
return Ok(Content::empty());
|
let value = match method {
|
||||||
|
"display" => self.display(args.eat()?).into(),
|
||||||
|
"at" => self.at(&mut vm.vt, args.expect("location")?)?,
|
||||||
|
"final" => self.final_(&mut vm.vt, args.expect("location")?)?,
|
||||||
|
"update" => self.update(args.expect("value or function")?).into(),
|
||||||
|
_ => bail!(span, "type state has no method `{}`", method),
|
||||||
|
};
|
||||||
|
args.finish()?;
|
||||||
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
let sequence = self.sequence(
|
/// Display the current value of the state.
|
||||||
vt.world,
|
pub fn display(self, func: Option<Func>) -> Content {
|
||||||
TrackedMut::reborrow_mut(&mut vt.tracer),
|
DisplayNode::new(self, func).pack()
|
||||||
TrackedMut::reborrow_mut(&mut vt.provider),
|
}
|
||||||
vt.introspector,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(match sequence.at(stop) {
|
/// Get the value of the state at the given location.
|
||||||
Some(value) => {
|
pub fn at(self, vt: &mut Vt, id: StableId) -> SourceResult<Value> {
|
||||||
if let Some(func) = func {
|
let sequence = self.sequence(vt)?;
|
||||||
func.call_vt(vt, [value])?.display()
|
let offset = vt.introspector.query_before(self.selector(), id).len();
|
||||||
} else {
|
Ok(sequence[offset].clone())
|
||||||
value.display()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the value of the state at the final location.
|
||||||
|
pub fn final_(self, vt: &mut Vt, _: StableId) -> SourceResult<Value> {
|
||||||
|
let sequence = self.sequence(vt)?;
|
||||||
|
Ok(sequence.last().unwrap().clone())
|
||||||
}
|
}
|
||||||
None => Content::empty(),
|
|
||||||
})
|
/// Produce content that performs a state update.
|
||||||
|
pub fn update(self, update: StateUpdate) -> Content {
|
||||||
|
UpdateNode::new(self, update).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce the whole sequence of states.
|
/// Produce the whole sequence of states.
|
||||||
///
|
///
|
||||||
/// This has to happen just once for all states, cutting down the number
|
/// This has to happen just once for all states, cutting down the number
|
||||||
/// of state updates from quadratic to linear.
|
/// of state updates from quadratic to linear.
|
||||||
|
fn sequence(&self, vt: &mut Vt) -> SourceResult<EcoVec<Value>> {
|
||||||
|
self.sequence_impl(
|
||||||
|
vt.world,
|
||||||
|
TrackedMut::reborrow_mut(&mut vt.tracer),
|
||||||
|
TrackedMut::reborrow_mut(&mut vt.provider),
|
||||||
|
vt.introspector,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Memoized implementation of `sequence`.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn sequence(
|
fn sequence_impl(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
tracer: TrackedMut<Tracer>,
|
tracer: TrackedMut<Tracer>,
|
||||||
provider: TrackedMut<StabilityProvider>,
|
provider: TrackedMut<StabilityProvider>,
|
||||||
introspector: Tracked<Introspector>,
|
introspector: Tracked<Introspector>,
|
||||||
) -> SourceResult<StateSequence> {
|
) -> SourceResult<EcoVec<Value>> {
|
||||||
let mut vt = Vt { world, tracer, provider, introspector };
|
let mut vt = Vt { world, tracer, provider, introspector };
|
||||||
let search = Selector::Node(
|
|
||||||
NodeId::of::<StateNode>(),
|
|
||||||
Some(dict! { "state" => self.clone() }),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut stops = EcoVec::new();
|
|
||||||
let mut state = self.init.clone();
|
let mut state = self.init.clone();
|
||||||
|
let mut stops = eco_vec![state.clone()];
|
||||||
|
|
||||||
for node in introspector.query(search) {
|
for node in introspector.query(self.selector()) {
|
||||||
let id = node.stable_id().unwrap();
|
let node = node.to::<UpdateNode>().unwrap();
|
||||||
let node = node.to::<StateNode>().unwrap();
|
match node.update() {
|
||||||
|
|
||||||
if let StateAction::Update(update) = node.action() {
|
|
||||||
match update {
|
|
||||||
StateUpdate::Set(value) => state = value,
|
StateUpdate::Set(value) => state = value,
|
||||||
StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?,
|
StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?,
|
||||||
}
|
}
|
||||||
|
stops.push(state.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
stops.push((id, state.clone()));
|
Ok(stops)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(StateSequence(stops))
|
/// The selector for this state's updates.
|
||||||
|
fn selector(&self) -> Selector {
|
||||||
|
Selector::Node(
|
||||||
|
NodeId::of::<UpdateNode>(),
|
||||||
|
Some(dict! { "state" => self.clone() }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,17 +134,70 @@ cast_from_value! {
|
|||||||
State: "state",
|
State: "state",
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sequence of state values.
|
/// An update to perform on a state.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
struct StateSequence(EcoVec<(StableId, Value)>);
|
pub enum StateUpdate {
|
||||||
|
/// Set the state to the specified value.
|
||||||
|
Set(Value),
|
||||||
|
/// Apply the given function to the state.
|
||||||
|
Func(Func),
|
||||||
|
}
|
||||||
|
|
||||||
impl StateSequence {
|
impl Debug for StateUpdate {
|
||||||
fn at(&self, stop: Option<StableId>) -> Option<Value> {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
let entry = match stop {
|
f.pad("..")
|
||||||
Some(stop) => self.0.iter().find(|&&(id, _)| id == stop),
|
}
|
||||||
None => self.0.last(),
|
}
|
||||||
};
|
|
||||||
|
cast_from_value! {
|
||||||
entry.map(|(_, value)| value.clone())
|
StateUpdate: "state update",
|
||||||
|
v: Func => Self::Func(v),
|
||||||
|
v: Value => Self::Set(v),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a display of a state.
|
||||||
|
///
|
||||||
|
/// Display: State
|
||||||
|
/// Category: special
|
||||||
|
#[node(Locatable, Show)]
|
||||||
|
struct DisplayNode {
|
||||||
|
/// The state.
|
||||||
|
#[required]
|
||||||
|
state: State,
|
||||||
|
|
||||||
|
/// The function to display the state with.
|
||||||
|
#[required]
|
||||||
|
func: Option<Func>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for DisplayNode {
|
||||||
|
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
|
let id = self.0.stable_id().unwrap();
|
||||||
|
let value = self.state().at(vt, id)?;
|
||||||
|
Ok(match self.func() {
|
||||||
|
Some(func) => func.call_vt(vt, [value])?.display(),
|
||||||
|
None => value.display(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a display of a state.
|
||||||
|
///
|
||||||
|
/// Display: State
|
||||||
|
/// Category: special
|
||||||
|
#[node(Locatable, Show)]
|
||||||
|
struct UpdateNode {
|
||||||
|
/// The state.
|
||||||
|
#[required]
|
||||||
|
state: State,
|
||||||
|
|
||||||
|
/// The update to perform on the state.
|
||||||
|
#[required]
|
||||||
|
update: StateUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for UpdateNode {
|
||||||
|
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
|
Ok(Content::empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use comemo::Tracked;
|
|||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
use super::{Args, Dynamic, Module, Value};
|
use super::{Args, Dynamic, Module, Value, Vm};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::doc::Document;
|
use crate::doc::Document;
|
||||||
use crate::geom::{Abs, Dir};
|
use crate::geom::{Abs, Dir};
|
||||||
@ -92,6 +92,7 @@ pub struct LangItems {
|
|||||||
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
||||||
/// Dispatch a method on a library value.
|
/// Dispatch a method on a library value.
|
||||||
pub library_method: fn(
|
pub library_method: fn(
|
||||||
|
vm: &mut Vm,
|
||||||
dynamic: &Dynamic,
|
dynamic: &Dynamic,
|
||||||
method: &str,
|
method: &str,
|
||||||
args: Args,
|
args: Args,
|
||||||
|
@ -4,6 +4,7 @@ use ecow::EcoString;
|
|||||||
|
|
||||||
use super::{Args, Str, Value, Vm};
|
use super::{Args, Str, Value, Vm};
|
||||||
use crate::diag::{At, SourceResult};
|
use crate::diag::{At, SourceResult};
|
||||||
|
use crate::model::StableId;
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
|
|
||||||
/// Call a method on a value.
|
/// Call a method on a value.
|
||||||
@ -73,8 +74,11 @@ pub fn call(
|
|||||||
"func" => Value::Func(content.id().into()),
|
"func" => Value::Func(content.id().into()),
|
||||||
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
|
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
|
||||||
"at" => content.at(&args.expect::<EcoString>("field")?).at(span)?.clone(),
|
"at" => content.at(&args.expect::<EcoString>("field")?).at(span)?.clone(),
|
||||||
"page" => content.page(&vm.vt).at(span)?.into(),
|
"id" => content
|
||||||
"location" => content.location(&vm.vt).at(span)?.into(),
|
.stable_id()
|
||||||
|
.ok_or("this method can only be called on content returned by query()")
|
||||||
|
.at(span)?
|
||||||
|
.into(),
|
||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -137,7 +141,15 @@ pub fn call(
|
|||||||
},
|
},
|
||||||
|
|
||||||
Value::Dyn(dynamic) => {
|
Value::Dyn(dynamic) => {
|
||||||
return (vm.items.library_method)(&dynamic, method, args, span);
|
if let Some(&id) = dynamic.downcast::<StableId>() {
|
||||||
|
match method {
|
||||||
|
"page" => vm.vt.introspector.page(id).into(),
|
||||||
|
"location" => vm.vt.introspector.location(id).into(),
|
||||||
|
_ => return missing(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (vm.items.library_method)(vm, &dynamic, method, args, span);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
@ -251,13 +263,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
|||||||
("starts-with", true),
|
("starts-with", true),
|
||||||
("trim", true),
|
("trim", true),
|
||||||
],
|
],
|
||||||
"content" => &[
|
"content" => &[("func", false), ("has", true), ("at", true), ("id", false)],
|
||||||
("func", false),
|
|
||||||
("has", true),
|
|
||||||
("at", true),
|
|
||||||
("page", false),
|
|
||||||
("location", false),
|
|
||||||
],
|
|
||||||
"array" => &[
|
"array" => &[
|
||||||
("all", true),
|
("all", true),
|
||||||
("any", true),
|
("any", true),
|
||||||
@ -293,14 +299,15 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
|||||||
],
|
],
|
||||||
"function" => &[("where", true), ("with", true)],
|
"function" => &[("where", true), ("with", true)],
|
||||||
"arguments" => &[("named", false), ("pos", false)],
|
"arguments" => &[("named", false), ("pos", false)],
|
||||||
|
"stable id" => &[("page", false), ("location", false)],
|
||||||
"counter" => &[
|
"counter" => &[
|
||||||
("get", true),
|
("display", true),
|
||||||
|
("at", true),
|
||||||
("final", true),
|
("final", true),
|
||||||
("both", true),
|
|
||||||
("step", true),
|
("step", true),
|
||||||
("update", true),
|
("update", true),
|
||||||
],
|
],
|
||||||
"state" => &[("get", true), ("final", true), ("update", true)],
|
"state" => &[("display", true), ("at", true), ("final", true), ("update", true)],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ use std::any::TypeId;
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::{self, Sum};
|
use std::iter::{self, Sum};
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
use std::ops::{Add, AddAssign, Deref};
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString, EcoVec};
|
use ecow::{eco_format, EcoString, EcoVec};
|
||||||
@ -10,10 +9,10 @@ use once_cell::sync::Lazy;
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap,
|
node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap,
|
||||||
Synthesize, Vt,
|
Synthesize,
|
||||||
};
|
};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::doc::{Location, Meta};
|
use crate::doc::Meta;
|
||||||
use crate::eval::{
|
use crate::eval::{
|
||||||
cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm,
|
cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm,
|
||||||
};
|
};
|
||||||
@ -186,22 +185,6 @@ impl Content {
|
|||||||
self.field(field).ok_or_else(|| missing_field(field))
|
self.field(field).ok_or_else(|| missing_field(field))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the page of this content.
|
|
||||||
pub fn page(&self, vt: &Vt) -> StrResult<NonZeroUsize> {
|
|
||||||
match self.stable_id() {
|
|
||||||
Some(id) => Ok(vt.introspector.page(id)),
|
|
||||||
None => Err("this method can only be called on queried content".into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine the location of this content.
|
|
||||||
pub fn location(&self, vt: &Vt) -> StrResult<Location> {
|
|
||||||
match self.stable_id() {
|
|
||||||
Some(id) => Ok(vt.introspector.location(id)),
|
|
||||||
None => Err("this method can only be called on queried content".into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The content's label.
|
/// The content's label.
|
||||||
pub fn label(&self) -> Option<&Label> {
|
pub fn label(&self) -> Option<&Label> {
|
||||||
match self.field("label")? {
|
match self.field("label")? {
|
||||||
|
@ -78,13 +78,7 @@ impl PartialEq for StyleMap {
|
|||||||
|
|
||||||
impl Debug for StyleMap {
|
impl Debug for StyleMap {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if let [style] = self.0.as_slice() {
|
f.pad("..")
|
||||||
return style.fmt(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
let pieces: Vec<_> =
|
|
||||||
self.0.iter().map(|value| eco_format!("{value:?}")).collect();
|
|
||||||
f.write_str(&pretty_array_like(&pieces, false))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ use comemo::{Constraint, Track, Tracked, TrackedMut};
|
|||||||
use super::{Content, Selector, StyleChain};
|
use super::{Content, Selector, StyleChain};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::doc::{Document, Element, Frame, Location, Meta};
|
use crate::doc::{Document, Element, Frame, Location, Meta};
|
||||||
use crate::eval::Tracer;
|
use crate::eval::{cast_from_value, Tracer};
|
||||||
use crate::geom::{Point, Transform};
|
use crate::geom::{Point, Transform};
|
||||||
use crate::util::NonZeroExt;
|
use crate::util::NonZeroExt;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -116,7 +117,7 @@ impl StabilityProvider {
|
|||||||
/// Stably identifies a call site across multiple layout passes.
|
/// Stably identifies a call site across multiple layout passes.
|
||||||
///
|
///
|
||||||
/// This struct is created by [`StabilityProvider::identify`].
|
/// This struct is created by [`StabilityProvider::identify`].
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct StableId(u128, usize, usize);
|
pub struct StableId(u128, usize, usize);
|
||||||
|
|
||||||
impl StableId {
|
impl StableId {
|
||||||
@ -126,16 +127,27 @@ impl StableId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for StableId {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad("..")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
StableId: "stable id",
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides access to information about the document.
|
/// Provides access to information about the document.
|
||||||
pub struct Introspector {
|
pub struct Introspector {
|
||||||
init: bool,
|
init: bool,
|
||||||
|
pages: usize,
|
||||||
nodes: Vec<(Content, Location)>,
|
nodes: Vec<(Content, Location)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Introspector {
|
impl Introspector {
|
||||||
/// Create a new introspector.
|
/// Create a new introspector.
|
||||||
pub fn new(frames: &[Frame]) -> Self {
|
pub fn new(frames: &[Frame]) -> Self {
|
||||||
let mut introspector = Self { init: false, nodes: vec![] };
|
let mut introspector = Self { init: false, pages: frames.len(), nodes: vec![] };
|
||||||
for (i, frame) in frames.iter().enumerate() {
|
for (i, frame) in frames.iter().enumerate() {
|
||||||
let page = NonZeroUsize::new(1 + i).unwrap();
|
let page = NonZeroUsize::new(1 + i).unwrap();
|
||||||
introspector.extract(frame, page, Transform::identity());
|
introspector.extract(frame, page, Transform::identity());
|
||||||
@ -180,26 +192,37 @@ impl Introspector {
|
|||||||
self.init
|
self.init
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query for all metadata matches for the given selector.
|
/// Query for all nodes for the given selector.
|
||||||
pub fn query(&self, selector: Selector) -> Vec<Content> {
|
pub fn query(&self, selector: Selector) -> Vec<Content> {
|
||||||
self.all().filter(|node| selector.matches(node)).cloned().collect()
|
self.all().filter(|node| selector.matches(node)).cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query for all metadata matches before the given id.
|
/// Query for all nodes up to the given id.
|
||||||
pub fn query_split(
|
pub fn query_before(&self, selector: Selector, id: StableId) -> Vec<Content> {
|
||||||
&self,
|
let mut matches = vec![];
|
||||||
selector: Selector,
|
for node in self.all() {
|
||||||
id: StableId,
|
if selector.matches(node) {
|
||||||
) -> (Vec<Content>, Vec<Content>) {
|
matches.push(node.clone());
|
||||||
let mut iter = self.all();
|
}
|
||||||
let before = iter
|
if node.stable_id() == Some(id) {
|
||||||
.by_ref()
|
break;
|
||||||
.take_while(|node| node.stable_id() != Some(id))
|
}
|
||||||
|
}
|
||||||
|
matches
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query for all nodes starting from the given id.
|
||||||
|
pub fn query_after(&self, selector: Selector, id: StableId) -> Vec<Content> {
|
||||||
|
self.all()
|
||||||
|
.skip_while(|node| node.stable_id() != Some(id))
|
||||||
.filter(|node| selector.matches(node))
|
.filter(|node| selector.matches(node))
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect()
|
||||||
let after = iter.filter(|node| selector.matches(node)).cloned().collect();
|
}
|
||||||
(before, after)
|
|
||||||
|
/// The total number pages.
|
||||||
|
pub fn pages(&self) -> NonZeroUsize {
|
||||||
|
NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the page number for the given stable id.
|
/// Find the page number for the given stable id.
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 435 KiB |
Binary file not shown.
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 51 KiB |
@ -6,8 +6,8 @@
|
|||||||
h(1fr)
|
h(1fr)
|
||||||
text(0.8em)[_Chapter 1_]
|
text(0.8em)[_Chapter 1_]
|
||||||
},
|
},
|
||||||
footer: align(center)[\~ #counter(page).get() \~],
|
footer: align(center)[\~ #counter(page).display() \~],
|
||||||
background: counter(page).get(n => if n <= 2 {
|
background: counter(page).display(n => if n <= 2 {
|
||||||
place(center + horizon, circle(radius: 1cm, fill: luma(90%)))
|
place(center + horizon, circle(radius: 1cm, fill: luma(90%)))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -4,21 +4,21 @@
|
|||||||
// Count with string key.
|
// Count with string key.
|
||||||
#let mine = counter("mine!")
|
#let mine = counter("mine!")
|
||||||
|
|
||||||
Final: #mine.final() \
|
Final: #locate(loc => mine.final(loc).at(0)) \
|
||||||
#mine.step()
|
#mine.step()
|
||||||
First: #mine.get() \
|
First: #mine.display() \
|
||||||
#mine.update(7)
|
#mine.update(7)
|
||||||
#mine.both("1 of 1") \
|
#mine.display("1 of 1", both: true) \
|
||||||
#mine.step()
|
#mine.step()
|
||||||
#mine.step()
|
#mine.step()
|
||||||
Second: #mine.get("I")
|
Second: #mine.display("I")
|
||||||
#mine.update(n => n * 2)
|
#mine.update(n => n * 2)
|
||||||
#mine.step()
|
#mine.step()
|
||||||
|
|
||||||
---
|
---
|
||||||
// Count labels.
|
// Count labels.
|
||||||
#let label = <heya>
|
#let label = <heya>
|
||||||
#let count = counter(label).get()
|
#let count = counter(label).display()
|
||||||
#let elem(it) = [#box(it) #label]
|
#let elem(it) = [#box(it) #label]
|
||||||
|
|
||||||
#elem[hey, there!] #count \
|
#elem[hey, there!] #count \
|
||||||
@ -31,13 +31,19 @@ Second: #mine.get("I")
|
|||||||
#counter(heading).step()
|
#counter(heading).step()
|
||||||
|
|
||||||
= Alpha
|
= Alpha
|
||||||
|
In #counter(heading).display().
|
||||||
|
|
||||||
== Beta
|
== Beta
|
||||||
In #counter(heading).get().
|
|
||||||
|
|
||||||
#set heading(numbering: none)
|
#set heading(numbering: none)
|
||||||
= Gamma
|
= Gamma
|
||||||
#heading(numbering: "I.")[Delta]
|
#heading(numbering: "I.")[Delta]
|
||||||
|
|
||||||
|
At Beta, it was #locate(loc => {
|
||||||
|
let it = query(heading, loc).find(it => it.body == [Beta])
|
||||||
|
numbering(it.numbering, ..counter(heading).at(it.id()))
|
||||||
|
})
|
||||||
|
|
||||||
---
|
---
|
||||||
// Count figures.
|
// Count figures.
|
||||||
#figure(numbering: "A", caption: [Four 'A's])[_AAAA!_]
|
#figure(numbering: "A", caption: [Four 'A's])[_AAAA!_]
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
header: {
|
header: {
|
||||||
smallcaps[Typst Academy]
|
smallcaps[Typst Academy]
|
||||||
h(1fr)
|
h(1fr)
|
||||||
query(heading, (before, after) => {
|
locate(it => {
|
||||||
|
let after = query(heading, after: it)
|
||||||
|
let before = query(heading, before: it)
|
||||||
let elem = if before.len() != 0 {
|
let elem = if before.len() != 0 {
|
||||||
before.last()
|
before.last()
|
||||||
} else if after.len() != 0 {
|
} else if after.len() != 0 {
|
||||||
@ -28,3 +30,41 @@
|
|||||||
|
|
||||||
= Approach
|
= Approach
|
||||||
#lorem(60)
|
#lorem(60)
|
||||||
|
|
||||||
|
---
|
||||||
|
#set page(
|
||||||
|
paper: "a7",
|
||||||
|
numbering: "1 / 1",
|
||||||
|
margin: (bottom: 1cm, rest: 0.5cm),
|
||||||
|
)
|
||||||
|
|
||||||
|
#set figure(numbering: "I")
|
||||||
|
#show figure: set image(width: 80%)
|
||||||
|
|
||||||
|
= List of Figures
|
||||||
|
#locate(it => {
|
||||||
|
let elements = query(figure, after: it)
|
||||||
|
for it in elements [
|
||||||
|
Figure
|
||||||
|
#numbering(it.numbering,
|
||||||
|
..counter(figure).at(it.id())):
|
||||||
|
#it.caption
|
||||||
|
#box(width: 1fr, repeat[.])
|
||||||
|
#counter(page).at(it.id()).first() \
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
image("/glacier.jpg"),
|
||||||
|
caption: [Glacier melting],
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
rect[Just some stand-in text],
|
||||||
|
caption: [Stand-in text],
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
image("/tiger.jpg"),
|
||||||
|
caption: [Tiger world],
|
||||||
|
)
|
||||||
|
@ -1,18 +1,33 @@
|
|||||||
// Test state.
|
// Test state.
|
||||||
|
|
||||||
|
---
|
||||||
|
#let s = state("hey", "a")
|
||||||
|
#let double(it) = 2 * it
|
||||||
|
|
||||||
|
#s.update(double)
|
||||||
|
#s.update(double)
|
||||||
|
$ 2 + 3 $
|
||||||
|
#s.update(double)
|
||||||
|
|
||||||
|
Is: #s.display(),
|
||||||
|
Was: #locate(id => {
|
||||||
|
let it = query(math.equation, id).first()
|
||||||
|
s.at(it.id())
|
||||||
|
}).
|
||||||
|
|
||||||
---
|
---
|
||||||
#set page(width: 200pt)
|
#set page(width: 200pt)
|
||||||
#set text(8pt)
|
#set text(8pt)
|
||||||
|
|
||||||
#let ls = state("lorem", lorem(1000).split("."))
|
#let ls = state("lorem", lorem(1000).split("."))
|
||||||
#let loremum(count) = {
|
#let loremum(count) = {
|
||||||
ls.get(list => list.slice(0, count).join(".").trim() + ".")
|
ls.display(list => list.slice(0, count).join(".").trim() + ".")
|
||||||
ls.update(list => list.slice(count))
|
ls.update(list => list.slice(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
#let fs = state("fader", red)
|
#let fs = state("fader", red)
|
||||||
#let trait(title) = block[
|
#let trait(title) = block[
|
||||||
#fs.get(color => text(fill: color)[
|
#fs.display(color => text(fill: color)[
|
||||||
*#title:* #loremum(1)
|
*#title:* #loremum(1)
|
||||||
])
|
])
|
||||||
#fs.update(color => color.lighten(30%))
|
#fs.update(color => color.lighten(30%))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user