Measurement and introspection rework
This commit is contained in:
parent
c7f4d6b12e
commit
0ba99ab8aa
@ -118,8 +118,9 @@ compilation.
|
||||
typst --watch file.typ
|
||||
```
|
||||
|
||||
If you prefer an integrated IDE-like experience, you can also check out the
|
||||
[Typst web app][app], which is currently in public beta.
|
||||
If you prefer an integrated IDE-like experience with autocompletion and instant
|
||||
preview, you can also check out the [Typst web app][app], which is currently in
|
||||
public beta.
|
||||
|
||||
## Build from source
|
||||
To build Typst yourself, you need to have the [latest stable Rust][rust]
|
||||
|
@ -88,7 +88,7 @@ fantasy encyclopedia.
|
||||
#set text(font: "Inria Serif")
|
||||
\~ #emph(it.body)
|
||||
#(counter(heading)
|
||||
.get(it.numbering)) \~
|
||||
.display(it.numbering)) \~
|
||||
]
|
||||
|
||||
= 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 hide;
|
||||
mod list;
|
||||
mod measure;
|
||||
mod pad;
|
||||
mod page;
|
||||
mod par;
|
||||
@ -31,6 +32,7 @@ pub use self::fragment::*;
|
||||
pub use self::grid::*;
|
||||
pub use self::hide::*;
|
||||
pub use self::list::*;
|
||||
pub use self::measure::*;
|
||||
pub use self::pad::*;
|
||||
pub use self::page::*;
|
||||
pub use self::par::*;
|
||||
|
@ -2,7 +2,7 @@ use std::ptr;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{AlignNode, ColumnsNode};
|
||||
use crate::meta::{Counter, CounterAction, CounterKey, CounterNode, Numbering};
|
||||
use crate::meta::{Counter, CounterKey, Numbering};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Layouts its child onto one or multiple pages.
|
||||
@ -214,8 +214,10 @@ pub struct PageNode {
|
||||
/// footer: [
|
||||
/// #set align(right)
|
||||
/// #set text(8pt)
|
||||
/// #counter(page).get("1") of
|
||||
/// #counter(page).final("I")
|
||||
/// #counter(page).display(
|
||||
/// "1 of I",
|
||||
/// both: true,
|
||||
/// )
|
||||
/// ]
|
||||
/// )
|
||||
///
|
||||
@ -311,12 +313,13 @@ impl PageNode {
|
||||
let header_ascent = self.header_ascent(styles);
|
||||
let footer = self.footer(styles).or_else(|| {
|
||||
self.numbering(styles).map(|numbering| {
|
||||
CounterNode::new(
|
||||
Counter::new(CounterKey::Page),
|
||||
CounterAction::Both(numbering),
|
||||
)
|
||||
.pack()
|
||||
.aligned(self.number_align(styles))
|
||||
let both = match &numbering {
|
||||
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
|
||||
Numbering::Func(_) => true,
|
||||
};
|
||||
Counter::new(CounterKey::Page)
|
||||
.display(numbering, both)
|
||||
.aligned(self.number_align(styles))
|
||||
})
|
||||
});
|
||||
let footer_descent = self.footer_descent(styles);
|
||||
|
@ -74,6 +74,7 @@ fn global(math: Module, calc: Module) -> Module {
|
||||
global.define("scale", layout::ScaleNode::id());
|
||||
global.define("rotate", layout::RotateNode::id());
|
||||
global.define("hide", layout::HideNode::id());
|
||||
global.define("measure", layout::measure);
|
||||
|
||||
// Visualize.
|
||||
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("cite", meta::CiteNode::id());
|
||||
global.define("bibliography", meta::BibliographyNode::id());
|
||||
global.define("locate", meta::locate);
|
||||
global.define("style", meta::style);
|
||||
global.define("counter", meta::counter);
|
||||
global.define("numbering", meta::numbering);
|
||||
global.define("state", meta::state);
|
||||
@ -228,11 +231,11 @@ fn items() -> LangItems {
|
||||
math::AccentNode::new(base, math::Accent::new(accent)).pack()
|
||||
},
|
||||
math_frac: |num, denom| math::FracNode::new(num, denom).pack(),
|
||||
library_method: |dynamic, method, args, span| {
|
||||
if let Some(counter) = dynamic.downcast().cloned() {
|
||||
meta::counter_method(counter, method, args, span)
|
||||
} else if let Some(state) = dynamic.downcast().cloned() {
|
||||
meta::state_method(state, method, args, span)
|
||||
library_method: |vm, dynamic, method, args, span| {
|
||||
if let Some(counter) = dynamic.downcast::<meta::Counter>().cloned() {
|
||||
counter.call_method(vm, method, args, span)
|
||||
} else if let Some(state) = dynamic.downcast::<meta::State>().cloned() {
|
||||
state.call_method(vm, method, args, span)
|
||||
} else {
|
||||
Err(format!("type {} has no method `{method}`", dynamic.type_name()))
|
||||
.at(span)
|
||||
|
@ -39,9 +39,7 @@ use self::fragment::*;
|
||||
use self::row::*;
|
||||
use self::spacing::*;
|
||||
use crate::layout::{HNode, ParNode, Spacing};
|
||||
use crate::meta::{
|
||||
Count, Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering,
|
||||
};
|
||||
use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering};
|
||||
use crate::prelude::*;
|
||||
use crate::text::{
|
||||
families, variant, FontFamily, FontList, LinebreakNode, SpaceNode, TextNode, TextSize,
|
||||
@ -217,29 +215,29 @@ impl Layout for EquationNode {
|
||||
if block {
|
||||
if let Some(numbering) = self.numbering(styles) {
|
||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||
let counter = CounterNode::new(
|
||||
Counter::of(Self::id()),
|
||||
CounterAction::Get(numbering),
|
||||
);
|
||||
let counter = Counter::of(Self::id())
|
||||
.display(numbering, false)
|
||||
.layout(vt, styles, pod)?
|
||||
.into_frame();
|
||||
|
||||
let sub = counter.pack().layout(vt, styles, pod)?.into_frame();
|
||||
let width = if regions.size.x.is_finite() {
|
||||
regions.size.x
|
||||
} 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);
|
||||
|
||||
let x = if TextNode::dir_in(styles).is_positive() {
|
||||
frame.width() - sub.width()
|
||||
frame.width() - counter.width()
|
||||
} else {
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
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.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum CounterUpdate {
|
||||
/// Set the counter to the specified state.
|
||||
Set(CounterState),
|
||||
@ -186,8 +262,14 @@ pub enum CounterUpdate {
|
||||
Func(Func),
|
||||
}
|
||||
|
||||
impl Debug for CounterUpdate {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("..")
|
||||
}
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
CounterUpdate,
|
||||
CounterUpdate: "counter update",
|
||||
v: CounterState => Self::Set(v),
|
||||
v: Func => Self::Func(v),
|
||||
}
|
||||
@ -198,153 +280,6 @@ pub trait Count {
|
||||
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.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct CounterState(pub SmallVec<[NonZeroUsize; 3]>);
|
||||
@ -378,6 +313,16 @@ impl CounterState {
|
||||
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! {
|
||||
@ -388,3 +333,57 @@ cast_from_value! {
|
||||
.map(Value::cast)
|
||||
.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 super::{
|
||||
Count, Counter, CounterAction, CounterNode, CounterUpdate, LocalName, Numbering,
|
||||
NumberingPattern,
|
||||
};
|
||||
use super::{Count, Counter, CounterUpdate, LocalName, Numbering, NumberingPattern};
|
||||
use crate::layout::{BlockNode, VNode};
|
||||
use crate::prelude::*;
|
||||
use crate::text::TextNode;
|
||||
@ -59,12 +56,9 @@ impl Show for FigureNode {
|
||||
if let Some(numbering) = self.numbering(styles) {
|
||||
let name = self.local_name(TextNode::lang_in(styles));
|
||||
caption = TextNode::packed(eco_format!("{name}\u{a0}"))
|
||||
+ CounterNode::new(
|
||||
Counter::of(Self::id()),
|
||||
CounterAction::Get(numbering),
|
||||
)
|
||||
.pack()
|
||||
.spanned(self.span())
|
||||
+ Counter::of(Self::id())
|
||||
.display(numbering, false)
|
||||
.spanned(self.span())
|
||||
+ TextNode::packed(": ")
|
||||
+ caption;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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::meta::Count;
|
||||
use crate::prelude::*;
|
||||
@ -92,9 +92,7 @@ impl Show for HeadingNode {
|
||||
let mut realized = self.body();
|
||||
if let Some(numbering) = self.numbering(styles) {
|
||||
realized =
|
||||
CounterNode::new(Counter::of(Self::id()), CounterAction::Get(numbering))
|
||||
.pack()
|
||||
.spanned(self.span())
|
||||
Counter::of(Self::id()).display(numbering, false).spanned(self.span())
|
||||
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
||||
+ realized;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Interaction between document parts.
|
||||
|
||||
mod bibliography;
|
||||
mod context;
|
||||
mod counter;
|
||||
mod document;
|
||||
mod figure;
|
||||
@ -13,6 +14,7 @@ mod reference;
|
||||
mod state;
|
||||
|
||||
pub use self::bibliography::*;
|
||||
pub use self::context::*;
|
||||
pub use self::counter::*;
|
||||
pub use self::document::*;
|
||||
pub use self::figure::*;
|
||||
|
@ -120,11 +120,9 @@ impl Show for OutlineNode {
|
||||
let mut hidden = Content::empty();
|
||||
for ancestor in &ancestors {
|
||||
if let Some(numbering) = ancestor.numbering(StyleChain::default()) {
|
||||
let numbers = Counter::of(HeadingNode::id()).resolve(
|
||||
vt,
|
||||
ancestor.0.stable_id(),
|
||||
&numbering,
|
||||
)?;
|
||||
let numbers = Counter::of(HeadingNode::id())
|
||||
.at(vt, ancestor.0.stable_id().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
hidden += numbers + SpaceNode::new().pack();
|
||||
};
|
||||
}
|
||||
@ -138,11 +136,9 @@ impl Show for OutlineNode {
|
||||
// Format the numbering.
|
||||
let mut start = heading.body();
|
||||
if let Some(numbering) = heading.numbering(StyleChain::default()) {
|
||||
let numbers = Counter::of(HeadingNode::id()).resolve(
|
||||
vt,
|
||||
Some(stable_id),
|
||||
&numbering,
|
||||
)?;
|
||||
let numbers = Counter::of(HeadingNode::id())
|
||||
.at(vt, stable_id)?
|
||||
.display(vt, &numbering)?;
|
||||
start = numbers + SpaceNode::new().pack() + start;
|
||||
};
|
||||
|
||||
|
@ -9,10 +9,29 @@ use crate::prelude::*;
|
||||
pub fn query(
|
||||
/// The thing to search for.
|
||||
target: Target,
|
||||
/// A function to format the results with.
|
||||
format: Func,
|
||||
/// The location.
|
||||
#[external]
|
||||
location: StableId,
|
||||
/// The location before which to query.
|
||||
#[named]
|
||||
#[external]
|
||||
before: StableId,
|
||||
/// The location after which to query.
|
||||
#[named]
|
||||
#[external]
|
||||
after: StableId,
|
||||
) -> 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.
|
||||
@ -33,31 +52,3 @@ cast_from_value! {
|
||||
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");
|
||||
};
|
||||
|
||||
let numbers =
|
||||
Counter::of(node.id()).resolve(vt, node.stable_id(), &numbering.trimmed())?;
|
||||
let numbers = Counter::of(node.id())
|
||||
.at(vt, node.stable_id().unwrap())?
|
||||
.display(vt, &numbering.trimmed())?;
|
||||
|
||||
Ok((supplement + numbers).linked(Link::Node(node.stable_id().unwrap())))
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::fmt::{self, Debug, Formatter, Write};
|
||||
|
||||
use ecow::EcoVec;
|
||||
use ecow::{eco_vec, EcoVec};
|
||||
use typst::eval::Tracer;
|
||||
|
||||
use crate::prelude::*;
|
||||
@ -21,91 +21,6 @@ pub fn state(
|
||||
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.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub struct State {
|
||||
@ -116,72 +31,92 @@ pub struct State {
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Display the state at the postition of the given stable id.
|
||||
fn resolve(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
stop: Option<StableId>,
|
||||
func: Option<Func>,
|
||||
) -> SourceResult<Content> {
|
||||
if !vt.introspector.init() {
|
||||
return Ok(Content::empty());
|
||||
}
|
||||
/// Call a method on a state.
|
||||
pub fn call_method(
|
||||
self,
|
||||
vm: &mut Vm,
|
||||
method: &str,
|
||||
mut args: Args,
|
||||
span: Span,
|
||||
) -> SourceResult<Value> {
|
||||
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(
|
||||
vt.world,
|
||||
TrackedMut::reborrow_mut(&mut vt.tracer),
|
||||
TrackedMut::reborrow_mut(&mut vt.provider),
|
||||
vt.introspector,
|
||||
)?;
|
||||
/// Display the current value of the state.
|
||||
pub fn display(self, func: Option<Func>) -> Content {
|
||||
DisplayNode::new(self, func).pack()
|
||||
}
|
||||
|
||||
Ok(match sequence.at(stop) {
|
||||
Some(value) => {
|
||||
if let Some(func) = func {
|
||||
func.call_vt(vt, [value])?.display()
|
||||
} else {
|
||||
value.display()
|
||||
}
|
||||
}
|
||||
None => Content::empty(),
|
||||
})
|
||||
/// Get the value of the state at the given location.
|
||||
pub fn at(self, vt: &mut Vt, id: StableId) -> SourceResult<Value> {
|
||||
let sequence = self.sequence(vt)?;
|
||||
let offset = vt.introspector.query_before(self.selector(), id).len();
|
||||
Ok(sequence[offset].clone())
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This has to happen just once for all states, cutting down the number
|
||||
/// 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]
|
||||
fn sequence(
|
||||
fn sequence_impl(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
tracer: TrackedMut<Tracer>,
|
||||
provider: TrackedMut<StabilityProvider>,
|
||||
introspector: Tracked<Introspector>,
|
||||
) -> SourceResult<StateSequence> {
|
||||
) -> SourceResult<EcoVec<Value>> {
|
||||
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 stops = eco_vec![state.clone()];
|
||||
|
||||
for node in introspector.query(search) {
|
||||
let id = node.stable_id().unwrap();
|
||||
let node = node.to::<StateNode>().unwrap();
|
||||
|
||||
if let StateAction::Update(update) = node.action() {
|
||||
match update {
|
||||
StateUpdate::Set(value) => state = value,
|
||||
StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?,
|
||||
}
|
||||
for node in introspector.query(self.selector()) {
|
||||
let node = node.to::<UpdateNode>().unwrap();
|
||||
match node.update() {
|
||||
StateUpdate::Set(value) => state = value,
|
||||
StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?,
|
||||
}
|
||||
|
||||
stops.push((id, state.clone()));
|
||||
stops.push(state.clone());
|
||||
}
|
||||
|
||||
Ok(StateSequence(stops))
|
||||
Ok(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",
|
||||
}
|
||||
|
||||
/// A sequence of state values.
|
||||
#[derive(Debug, Clone)]
|
||||
struct StateSequence(EcoVec<(StableId, Value)>);
|
||||
/// An update to perform on a state.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum StateUpdate {
|
||||
/// Set the state to the specified value.
|
||||
Set(Value),
|
||||
/// Apply the given function to the state.
|
||||
Func(Func),
|
||||
}
|
||||
|
||||
impl StateSequence {
|
||||
fn at(&self, stop: Option<StableId>) -> Option<Value> {
|
||||
let entry = match stop {
|
||||
Some(stop) => self.0.iter().find(|&&(id, _)| id == stop),
|
||||
None => self.0.last(),
|
||||
};
|
||||
|
||||
entry.map(|(_, value)| value.clone())
|
||||
impl Debug for StateUpdate {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad("..")
|
||||
}
|
||||
}
|
||||
|
||||
cast_from_value! {
|
||||
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 once_cell::sync::OnceCell;
|
||||
|
||||
use super::{Args, Dynamic, Module, Value};
|
||||
use super::{Args, Dynamic, Module, Value, Vm};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::doc::Document;
|
||||
use crate::geom::{Abs, Dir};
|
||||
@ -92,6 +92,7 @@ pub struct LangItems {
|
||||
pub math_frac: fn(num: Content, denom: Content) -> Content,
|
||||
/// Dispatch a method on a library value.
|
||||
pub library_method: fn(
|
||||
vm: &mut Vm,
|
||||
dynamic: &Dynamic,
|
||||
method: &str,
|
||||
args: Args,
|
||||
|
@ -4,6 +4,7 @@ use ecow::EcoString;
|
||||
|
||||
use super::{Args, Str, Value, Vm};
|
||||
use crate::diag::{At, SourceResult};
|
||||
use crate::model::StableId;
|
||||
use crate::syntax::Span;
|
||||
|
||||
/// Call a method on a value.
|
||||
@ -73,8 +74,11 @@ pub fn call(
|
||||
"func" => Value::Func(content.id().into()),
|
||||
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
|
||||
"at" => content.at(&args.expect::<EcoString>("field")?).at(span)?.clone(),
|
||||
"page" => content.page(&vm.vt).at(span)?.into(),
|
||||
"location" => content.location(&vm.vt).at(span)?.into(),
|
||||
"id" => content
|
||||
.stable_id()
|
||||
.ok_or("this method can only be called on content returned by query()")
|
||||
.at(span)?
|
||||
.into(),
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
@ -137,7 +141,15 @@ pub fn call(
|
||||
},
|
||||
|
||||
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(),
|
||||
@ -251,13 +263,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
||||
("starts-with", true),
|
||||
("trim", true),
|
||||
],
|
||||
"content" => &[
|
||||
("func", false),
|
||||
("has", true),
|
||||
("at", true),
|
||||
("page", false),
|
||||
("location", false),
|
||||
],
|
||||
"content" => &[("func", false), ("has", true), ("at", true), ("id", false)],
|
||||
"array" => &[
|
||||
("all", true),
|
||||
("any", true),
|
||||
@ -293,14 +299,15 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
||||
],
|
||||
"function" => &[("where", true), ("with", true)],
|
||||
"arguments" => &[("named", false), ("pos", false)],
|
||||
"stable id" => &[("page", false), ("location", false)],
|
||||
"counter" => &[
|
||||
("get", true),
|
||||
("display", true),
|
||||
("at", true),
|
||||
("final", true),
|
||||
("both", true),
|
||||
("step", 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::hash::{Hash, Hasher};
|
||||
use std::iter::{self, Sum};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::{Add, AddAssign, Deref};
|
||||
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
@ -10,10 +9,10 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use super::{
|
||||
node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap,
|
||||
Synthesize, Vt,
|
||||
Synthesize,
|
||||
};
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::doc::{Location, Meta};
|
||||
use crate::doc::Meta;
|
||||
use crate::eval::{
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn label(&self) -> Option<&Label> {
|
||||
match self.field("label")? {
|
||||
|
@ -78,13 +78,7 @@ impl PartialEq for StyleMap {
|
||||
|
||||
impl Debug for StyleMap {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if let [style] = self.0.as_slice() {
|
||||
return style.fmt(f);
|
||||
}
|
||||
|
||||
let pieces: Vec<_> =
|
||||
self.0.iter().map(|value| eco_format!("{value:?}")).collect();
|
||||
f.write_str(&pretty_array_like(&pieces, false))
|
||||
f.pad("..")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
@ -6,7 +7,7 @@ use comemo::{Constraint, Track, Tracked, TrackedMut};
|
||||
use super::{Content, Selector, StyleChain};
|
||||
use crate::diag::SourceResult;
|
||||
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::util::NonZeroExt;
|
||||
use crate::World;
|
||||
@ -116,7 +117,7 @@ impl StabilityProvider {
|
||||
/// Stably identifies a call site across multiple layout passes.
|
||||
///
|
||||
/// 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);
|
||||
|
||||
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.
|
||||
pub struct Introspector {
|
||||
init: bool,
|
||||
pages: usize,
|
||||
nodes: Vec<(Content, Location)>,
|
||||
}
|
||||
|
||||
impl Introspector {
|
||||
/// Create a new introspector.
|
||||
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() {
|
||||
let page = NonZeroUsize::new(1 + i).unwrap();
|
||||
introspector.extract(frame, page, Transform::identity());
|
||||
@ -180,26 +192,37 @@ impl Introspector {
|
||||
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> {
|
||||
self.all().filter(|node| selector.matches(node)).cloned().collect()
|
||||
}
|
||||
|
||||
/// Query for all metadata matches before the given id.
|
||||
pub fn query_split(
|
||||
&self,
|
||||
selector: Selector,
|
||||
id: StableId,
|
||||
) -> (Vec<Content>, Vec<Content>) {
|
||||
let mut iter = self.all();
|
||||
let before = iter
|
||||
.by_ref()
|
||||
.take_while(|node| node.stable_id() != Some(id))
|
||||
/// Query for all nodes up to the given id.
|
||||
pub fn query_before(&self, selector: Selector, id: StableId) -> Vec<Content> {
|
||||
let mut matches = vec![];
|
||||
for node in self.all() {
|
||||
if selector.matches(node) {
|
||||
matches.push(node.clone());
|
||||
}
|
||||
if node.stable_id() == Some(id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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))
|
||||
.cloned()
|
||||
.collect();
|
||||
let after = iter.filter(|node| selector.matches(node)).cloned().collect();
|
||||
(before, after)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
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)
|
||||
text(0.8em)[_Chapter 1_]
|
||||
},
|
||||
footer: align(center)[\~ #counter(page).get() \~],
|
||||
background: counter(page).get(n => if n <= 2 {
|
||||
footer: align(center)[\~ #counter(page).display() \~],
|
||||
background: counter(page).display(n => if n <= 2 {
|
||||
place(center + horizon, circle(radius: 1cm, fill: luma(90%)))
|
||||
})
|
||||
)
|
||||
|
@ -4,21 +4,21 @@
|
||||
// Count with string key.
|
||||
#let mine = counter("mine!")
|
||||
|
||||
Final: #mine.final() \
|
||||
Final: #locate(loc => mine.final(loc).at(0)) \
|
||||
#mine.step()
|
||||
First: #mine.get() \
|
||||
First: #mine.display() \
|
||||
#mine.update(7)
|
||||
#mine.both("1 of 1") \
|
||||
#mine.display("1 of 1", both: true) \
|
||||
#mine.step()
|
||||
#mine.step()
|
||||
Second: #mine.get("I")
|
||||
Second: #mine.display("I")
|
||||
#mine.update(n => n * 2)
|
||||
#mine.step()
|
||||
|
||||
---
|
||||
// Count labels.
|
||||
#let label = <heya>
|
||||
#let count = counter(label).get()
|
||||
#let count = counter(label).display()
|
||||
#let elem(it) = [#box(it) #label]
|
||||
|
||||
#elem[hey, there!] #count \
|
||||
@ -31,13 +31,19 @@ Second: #mine.get("I")
|
||||
#counter(heading).step()
|
||||
|
||||
= Alpha
|
||||
In #counter(heading).display().
|
||||
|
||||
== Beta
|
||||
In #counter(heading).get().
|
||||
|
||||
#set heading(numbering: none)
|
||||
= Gamma
|
||||
#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.
|
||||
#figure(numbering: "A", caption: [Four 'A's])[_AAAA!_]
|
||||
|
@ -7,7 +7,9 @@
|
||||
header: {
|
||||
smallcaps[Typst Academy]
|
||||
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 {
|
||||
before.last()
|
||||
} else if after.len() != 0 {
|
||||
@ -28,3 +30,41 @@
|
||||
|
||||
= Approach
|
||||
#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.
|
||||
|
||||
---
|
||||
#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 text(8pt)
|
||||
|
||||
#let ls = state("lorem", lorem(1000).split("."))
|
||||
#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))
|
||||
}
|
||||
|
||||
#let fs = state("fader", red)
|
||||
#let trait(title) = block[
|
||||
#fs.get(color => text(fill: color)[
|
||||
#fs.display(color => text(fill: color)[
|
||||
*#title:* #loremum(1)
|
||||
])
|
||||
#fs.update(color => color.lighten(30%))
|
||||
|
Loading…
x
Reference in New Issue
Block a user