diff options
Diffstat (limited to 'crates/typst-library/src/shared')
| -rw-r--r-- | crates/typst-library/src/shared/behave.rs | 109 | ||||
| -rw-r--r-- | crates/typst-library/src/shared/ext.rs | 92 | ||||
| -rw-r--r-- | crates/typst-library/src/shared/mod.rs | 7 |
3 files changed, 208 insertions, 0 deletions
diff --git a/crates/typst-library/src/shared/behave.rs b/crates/typst-library/src/shared/behave.rs new file mode 100644 index 00000000..6a1aa127 --- /dev/null +++ b/crates/typst-library/src/shared/behave.rs @@ -0,0 +1,109 @@ +//! Element interaction. + +use typst::model::{Behave, Behaviour, Content, StyleChain, StyleVec, StyleVecBuilder}; + +/// A wrapper around a [`StyleVecBuilder`] that allows elements to interact. +#[derive(Debug)] +pub struct BehavedBuilder<'a> { + /// The internal builder. + builder: StyleVecBuilder<'a, Content>, + /// Staged weak and ignorant elements that we can't yet commit to the + /// builder. The option is `Some(_)` for weak elements and `None` for + /// ignorant elements. + staged: Vec<(Content, Behaviour, StyleChain<'a>)>, + /// What the last non-ignorant item was. + last: Behaviour, +} + +impl<'a> BehavedBuilder<'a> { + /// Create a new style-vec builder. + pub fn new() -> Self { + Self { + builder: StyleVecBuilder::new(), + staged: vec![], + last: Behaviour::Destructive, + } + } + + /// Whether the builder is totally empty. + pub fn is_empty(&self) -> bool { + self.builder.is_empty() && self.staged.is_empty() + } + + /// Whether the builder is empty except for some weak elements that will + /// probably collapse. + pub fn is_basically_empty(&self) -> bool { + self.builder.is_empty() + && self + .staged + .iter() + .all(|(_, behaviour, _)| matches!(behaviour, Behaviour::Weak(_))) + } + + /// Push an item into the sequence. + pub fn push(&mut self, elem: Content, styles: StyleChain<'a>) { + let interaction = elem + .with::<dyn Behave>() + .map_or(Behaviour::Supportive, Behave::behaviour); + + match interaction { + Behaviour::Weak(level) => { + if matches!(self.last, Behaviour::Weak(_)) { + let item = elem.with::<dyn Behave>().unwrap(); + let i = self.staged.iter().position(|prev| { + let Behaviour::Weak(prev_level) = prev.1 else { return false }; + level < prev_level + || (level == prev_level && item.larger(&prev.0)) + }); + let Some(i) = i else { return }; + self.staged.remove(i); + } + + if self.last != Behaviour::Destructive { + self.staged.push((elem, interaction, styles)); + self.last = interaction; + } + } + Behaviour::Supportive => { + self.flush(true); + self.builder.push(elem, styles); + self.last = interaction; + } + Behaviour::Destructive => { + self.flush(false); + self.builder.push(elem, styles); + self.last = interaction; + } + Behaviour::Ignorant => { + self.staged.push((elem, interaction, styles)); + } + } + } + + /// Iterate over the contained elements. + pub fn elems(&self) -> impl DoubleEndedIterator<Item = &Content> { + self.builder.elems().chain(self.staged.iter().map(|(item, ..)| item)) + } + + /// Return the finish style vec and the common prefix chain. + pub fn finish(mut self) -> (StyleVec<Content>, StyleChain<'a>) { + self.flush(false); + self.builder.finish() + } + + /// Push the staged elements, filtering out weak elements if `supportive` is + /// false. + fn flush(&mut self, supportive: bool) { + for (item, interaction, styles) in self.staged.drain(..) { + if supportive || interaction == Behaviour::Ignorant { + self.builder.push(item, styles); + } + } + } +} + +impl<'a> Default for BehavedBuilder<'a> { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/typst-library/src/shared/ext.rs b/crates/typst-library/src/shared/ext.rs new file mode 100644 index 00000000..d7c80a30 --- /dev/null +++ b/crates/typst-library/src/shared/ext.rs @@ -0,0 +1,92 @@ +//! Extension traits. + +use crate::layout::{AlignElem, MoveElem, PadElem}; +use crate::prelude::*; +use crate::text::{EmphElem, FontFamily, FontList, StrongElem, TextElem, UnderlineElem}; + +/// Additional methods on content. +pub trait ContentExt { + /// Make this content strong. + fn strong(self) -> Self; + + /// Make this content emphasized. + fn emph(self) -> Self; + + /// Underline this content. + fn underlined(self) -> Self; + + /// Link the content somewhere. + fn linked(self, dest: Destination) -> Self; + + /// Make the content linkable by `.linked(Destination::Location(loc))`. + /// + /// Should be used in combination with [`Location::variant`]. + fn backlinked(self, loc: Location) -> Self; + + /// Set alignments for this content. + fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self; + + /// Pad this content at the sides. + fn padded(self, padding: Sides<Rel<Length>>) -> Self; + + /// Transform this content's contents without affecting layout. + fn moved(self, delta: Axes<Rel<Length>>) -> Self; +} + +impl ContentExt for Content { + fn strong(self) -> Self { + StrongElem::new(self).pack() + } + + fn emph(self) -> Self { + EmphElem::new(self).pack() + } + + fn underlined(self) -> Self { + UnderlineElem::new(self).pack() + } + + fn linked(self, dest: Destination) -> Self { + self.styled(MetaElem::set_data(vec![Meta::Link(dest)])) + } + + fn backlinked(self, loc: Location) -> Self { + let mut backlink = Content::empty(); + backlink.set_location(loc); + self.styled(MetaElem::set_data(vec![Meta::Elem(backlink)])) + } + + fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self { + self.styled(AlignElem::set_alignment(aligns)) + } + + fn padded(self, padding: Sides<Rel<Length>>) -> Self { + PadElem::new(self) + .with_left(padding.left) + .with_top(padding.top) + .with_right(padding.right) + .with_bottom(padding.bottom) + .pack() + } + + fn moved(self, delta: Axes<Rel<Length>>) -> Self { + MoveElem::new(self).with_dx(delta.x).with_dy(delta.y).pack() + } +} + +/// Additional methods for style lists. +pub trait StylesExt { + /// Set a font family composed of a preferred family and existing families + /// from a style chain. + fn set_family(&mut self, preferred: FontFamily, existing: StyleChain); +} + +impl StylesExt for Styles { + fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) { + self.set(TextElem::set_font(FontList( + std::iter::once(preferred) + .chain(TextElem::font_in(existing)) + .collect(), + ))); + } +} diff --git a/crates/typst-library/src/shared/mod.rs b/crates/typst-library/src/shared/mod.rs new file mode 100644 index 00000000..f54241cf --- /dev/null +++ b/crates/typst-library/src/shared/mod.rs @@ -0,0 +1,7 @@ +//! Shared definitions for the standard library. + +mod behave; +mod ext; + +pub use behave::*; +pub use ext::*; |
