summaryrefslogtreecommitdiff
path: root/library/src/meta
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-02 19:59:52 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-02 20:07:43 +0200
commitebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch)
tree2bbc24ddb4124c4bb14dec0e536129d4de37b056 /library/src/meta
parent3ab19185093d7709f824b95b979060ce125389d8 (diff)
Move everything into `crates/` directory
Diffstat (limited to 'library/src/meta')
-rw-r--r--library/src/meta/bibliography.rs724
-rw-r--r--library/src/meta/context.rs220
-rw-r--r--library/src/meta/counter.rs683
-rw-r--r--library/src/meta/document.rs86
-rw-r--r--library/src/meta/figure.rs351
-rw-r--r--library/src/meta/footnote.rs299
-rw-r--r--library/src/meta/heading.rs239
-rw-r--r--library/src/meta/link.rs137
-rw-r--r--library/src/meta/mod.rs64
-rw-r--r--library/src/meta/numbering.rs525
-rw-r--r--library/src/meta/outline.rs528
-rw-r--r--library/src/meta/query.rs145
-rw-r--r--library/src/meta/reference.rs276
-rw-r--r--library/src/meta/state.rs440
14 files changed, 0 insertions, 4717 deletions
diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs
deleted file mode 100644
index 0531997d..00000000
--- a/library/src/meta/bibliography.rs
+++ /dev/null
@@ -1,724 +0,0 @@
-use std::collections::HashMap;
-use std::ffi::OsStr;
-use std::path::Path;
-use std::sync::Arc;
-
-use ecow::{eco_vec, EcoVec};
-use hayagriva::io::{BibLaTeXError, YamlBibliographyError};
-use hayagriva::style::{self, Brackets, Citation, Database, DisplayString, Formatting};
-use hayagriva::Entry;
-use typst::diag::FileError;
-use typst::util::{option_eq, Bytes};
-
-use super::{LinkElem, LocalName, RefElem};
-use crate::layout::{BlockElem, GridElem, ParElem, Sizing, TrackSizings, VElem};
-use crate::meta::{FootnoteElem, HeadingElem};
-use crate::prelude::*;
-use crate::text::TextElem;
-
-/// A bibliography / reference listing.
-///
-/// You can create a new bibliography by calling this function with a path
-/// to a bibliography file in either one of two formats:
-///
-/// - A Hayagriva `.yml` file. Hayagriva is a new bibliography file format
-/// designed for use with Typst. Visit its
-/// [documentation](https://github.com/typst/hayagriva/blob/main/docs/file-format.md)
-/// for more details.
-/// - A BibLaTeX `.bib` file.
-///
-/// As soon as you add a bibliography somewhere in your document, you can start
-/// citing things with reference syntax (`[@key]`) or explicit calls to the
-/// [citation]($func/cite) function (`[#cite("key")]`). The bibliography will
-/// only show entries for works that were referenced in the document.
-///
-/// # Example
-/// ```example
-/// This was already noted by
-/// pirates long ago. @arrgh
-///
-/// Multiple sources say ...
-/// #cite("arrgh", "netwok").
-///
-/// #bibliography("works.bib")
-/// ```
-///
-/// Display: Bibliography
-/// Category: meta
-#[element(Locatable, Synthesize, Show, Finalize, LocalName)]
-pub struct BibliographyElem {
- /// Path to a Hayagriva `.yml` or BibLaTeX `.bib` file.
- #[required]
- #[parse(
- let Spanned { v: paths, span } =
- args.expect::<Spanned<BibPaths>>("path to bibliography file")?;
-
- // Load bibliography files.
- let data = paths.0
- .iter()
- .map(|path| {
- let id = vm.location().join(path).at(span)?;
- vm.world().file(id).at(span)
- })
- .collect::<SourceResult<Vec<Bytes>>>()?;
-
- // Check that parsing works.
- let _ = load(&paths, &data).at(span)?;
-
- paths
- )]
- pub path: BibPaths,
-
- /// The raw file buffers.
- #[internal]
- #[required]
- #[parse(data)]
- pub data: Vec<Bytes>,
-
- /// The title of the bibliography.
- ///
- /// - When set to `{auto}`, an appropriate title for the [text
- /// language]($func/text.lang) will be used. This is the default.
- /// - When set to `{none}`, the bibliography will not have a title.
- /// - A custom title can be set by passing content.
- ///
- /// The bibliography's heading will not be numbered by default, but you can
- /// force it to be with a show-set rule:
- /// `{show bibliography: set heading(numbering: "1.")}`
- /// ```
- #[default(Some(Smart::Auto))]
- pub title: Option<Smart<Content>>,
-
- /// The bibliography style.
- #[default(BibliographyStyle::Ieee)]
- pub style: BibliographyStyle,
-}
-
-/// A list of bibliography file paths.
-#[derive(Debug, Default, Clone, Hash)]
-pub struct BibPaths(Vec<EcoString>);
-
-cast! {
- BibPaths,
- self => self.0.into_value(),
- v: EcoString => Self(vec![v]),
- v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
-}
-
-impl BibliographyElem {
- /// Find the document's bibliography.
- pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> {
- let mut iter = introspector.query(&Self::func().select()).into_iter();
- let Some(elem) = iter.next() else {
- bail!("the document does not contain a bibliography");
- };
-
- if iter.next().is_some() {
- bail!("multiple bibliographies are not supported");
- }
-
- Ok(elem.to::<Self>().unwrap().clone())
- }
-
- /// Whether the bibliography contains the given key.
- pub fn has(vt: &Vt, key: &str) -> bool {
- vt.introspector
- .query(&Self::func().select())
- .into_iter()
- .flat_map(|elem| {
- let elem = elem.to::<Self>().unwrap();
- load(&elem.path(), &elem.data())
- })
- .flatten()
- .any(|entry| entry.key() == key)
- }
-
- /// Find all bibliography keys.
- pub fn keys(
- introspector: Tracked<Introspector>,
- ) -> Vec<(EcoString, Option<EcoString>)> {
- Self::find(introspector)
- .and_then(|elem| load(&elem.path(), &elem.data()))
- .into_iter()
- .flatten()
- .map(|entry| {
- let key = entry.key().into();
- let detail =
- entry.title().map(|title| title.canonical.value.as_str().into());
- (key, detail)
- })
- .collect()
- }
-}
-
-impl Synthesize for BibliographyElem {
- fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
- self.push_style(self.style(styles));
- Ok(())
- }
-}
-
-impl Show for BibliographyElem {
- #[tracing::instrument(name = "BibliographyElem::show", skip_all)]
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- const COLUMN_GUTTER: Em = Em::new(0.65);
- const INDENT: Em = Em::new(1.5);
-
- let mut seq = vec![];
- if let Some(title) = self.title(styles) {
- let title =
- title.unwrap_or_else(|| {
- TextElem::packed(self.local_name(
- TextElem::lang_in(styles),
- TextElem::region_in(styles),
- ))
- .spanned(self.span())
- });
-
- seq.push(HeadingElem::new(title).with_level(NonZeroUsize::ONE).pack());
- }
-
- Ok(vt.delayed(|vt| {
- let works = Works::new(vt).at(self.span())?;
-
- let row_gutter = BlockElem::below_in(styles).amount();
- if works.references.iter().any(|(prefix, _)| prefix.is_some()) {
- let mut cells = vec![];
- for (prefix, reference) in &works.references {
- cells.push(prefix.clone().unwrap_or_default());
- cells.push(reference.clone());
- }
-
- seq.push(VElem::new(row_gutter).with_weakness(3).pack());
- seq.push(
- GridElem::new(cells)
- .with_columns(TrackSizings(vec![Sizing::Auto; 2]))
- .with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()]))
- .with_row_gutter(TrackSizings(vec![row_gutter.into()]))
- .pack(),
- );
- } else {
- let mut entries = vec![];
- for (_, reference) in &works.references {
- entries.push(VElem::new(row_gutter).with_weakness(3).pack());
- entries.push(reference.clone());
- }
-
- seq.push(
- Content::sequence(entries)
- .styled(ParElem::set_hanging_indent(INDENT.into())),
- );
- }
-
- Ok(Content::sequence(seq))
- }))
- }
-}
-
-impl Finalize for BibliographyElem {
- fn finalize(&self, realized: Content, _: StyleChain) -> Content {
- realized.styled(HeadingElem::set_numbering(None))
- }
-}
-
-impl LocalName for BibliographyElem {
- fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Bibliografi",
- Lang::ARABIC => "المراجع",
- Lang::BOKMÅL => "Bibliografi",
- Lang::CHINESE if option_eq(region, "TW") => "書目",
- Lang::CHINESE => "参考文献",
- Lang::CZECH => "Bibliografie",
- Lang::DANISH => "Bibliografi",
- Lang::DUTCH => "Bibliografie",
- Lang::FILIPINO => "Bibliograpiya",
- Lang::FRENCH => "Bibliographie",
- Lang::GERMAN => "Bibliographie",
- Lang::ITALIAN => "Bibliografia",
- Lang::NYNORSK => "Bibliografi",
- Lang::POLISH => "Bibliografia",
- Lang::PORTUGUESE => "Bibliografia",
- Lang::RUSSIAN => "Библиография",
- Lang::SLOVENIAN => "Literatura",
- Lang::SPANISH => "Bibliografía",
- Lang::SWEDISH => "Bibliografi",
- Lang::TURKISH => "Kaynakça",
- Lang::UKRAINIAN => "Бібліографія",
- Lang::VIETNAMESE => "Tài liệu tham khảo",
- Lang::ENGLISH | _ => "Bibliography",
- }
- }
-}
-
-/// A bibliography style.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
-pub enum BibliographyStyle {
- /// Follows guidance of the American Psychological Association. Based on the
- /// 7th edition of the APA Publication Manual.
- Apa,
- /// The Chicago Author Date style. Based on the 17th edition of the Chicago
- /// Manual of Style, Chapter 15.
- ChicagoAuthorDate,
- /// The Chicago Notes style. Based on the 17th edition of the Chicago
- /// Manual of Style, Chapter 14.
- ChicagoNotes,
- /// The style of the Institute of Electrical and Electronics Engineers.
- /// Based on the 2018 IEEE Reference Guide.
- Ieee,
- /// Follows guidance of the Modern Language Association. Based on the 8th
- /// edition of the MLA Handbook.
- Mla,
-}
-
-impl BibliographyStyle {
- /// The default citation style for this bibliography style.
- pub fn default_citation_style(self) -> CitationStyle {
- match self {
- Self::Apa => CitationStyle::ChicagoAuthorDate,
- Self::ChicagoAuthorDate => CitationStyle::ChicagoAuthorDate,
- Self::ChicagoNotes => CitationStyle::ChicagoNotes,
- Self::Ieee => CitationStyle::Numerical,
- Self::Mla => CitationStyle::ChicagoAuthorDate,
- }
- }
-}
-
-/// Cite a work from the bibliography.
-///
-/// Before you starting citing, you need to add a
-/// [bibliography]($func/bibliography) somewhere in your document.
-///
-/// # Example
-/// ```example
-/// This was already noted by
-/// pirates long ago. @arrgh
-///
-/// Multiple sources say ...
-/// #cite("arrgh", "netwok").
-///
-/// #bibliography("works.bib")
-/// ```
-///
-/// # Syntax
-/// This function indirectly has dedicated syntax. [References]($func/ref)
-/// can be used to cite works from the bibliography. The label then
-/// corresponds to the citation key.
-///
-/// Display: Citation
-/// Category: meta
-#[element(Locatable, Synthesize, Show)]
-pub struct CiteElem {
- /// The citation keys that identify the elements that shall be cited in
- /// the bibliography.
- ///
- /// Reference syntax supports only a single key.
- #[variadic]
- pub keys: Vec<EcoString>,
-
- /// A supplement for the citation such as page or chapter number.
- ///
- /// In reference syntax, the supplement can be added in square brackets:
- ///
- /// ```example
- /// This has been proven over and
- /// over again. @distress[p.~7]
- ///
- /// #bibliography("works.bib")
- /// ```
- #[positional]
- pub supplement: Option<Content>,
-
- /// Whether the citation should include brackets.
- ///
- /// ```example
- /// #set cite(brackets: false)
- ///
- /// @netwok follow these methods
- /// in their work ...
- ///
- /// #bibliography(
- /// "works.bib",
- /// style: "chicago-author-date",
- /// )
- /// ```
- #[default(true)]
- pub brackets: bool,
-
- /// The citation style.
- ///
- /// When set to `{auto}`, automatically picks the preferred citation style
- /// for the bibliography's style.
- ///
- /// ```example
- /// #set cite(style: "alphanumerical")
- /// Alphanumerical references.
- /// @netwok
- ///
- /// #bibliography("works.bib")
- /// ```
- pub style: Smart<CitationStyle>,
-}
-
-impl Synthesize for CiteElem {
- fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
- self.push_supplement(self.supplement(styles));
- self.push_brackets(self.brackets(styles));
- self.push_style(self.style(styles));
- Ok(())
- }
-}
-
-impl Show for CiteElem {
- #[tracing::instrument(name = "CiteElem::show", skip(self, vt))]
- fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(vt.delayed(|vt| {
- let works = Works::new(vt).at(self.span())?;
- let location = self.0.location().unwrap();
- works
- .citations
- .get(&location)
- .cloned()
- .flatten()
- .ok_or("bibliography does not contain this key")
- .at(self.span())
- }))
- }
-}
-
-cast! {
- CiteElem,
- v: Content => v.to::<Self>().cloned().ok_or("expected citation")?,
-}
-
-/// A citation style.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
-pub enum CitationStyle {
- /// IEEE-style numerical reference markers.
- Numerical,
- /// A simple alphanumerical style. For example, the output could be Rass97
- /// or MKG+21.
- Alphanumerical,
- /// Citations that just consist of the entry keys.
- Keys,
- /// The Chicago Author Date style. Based on the 17th edition of the Chicago
- /// Manual of Style, Chapter 15.
- ChicagoAuthorDate,
- /// The Chicago Notes style. Based on the 17th edition of the Chicago
- /// Manual of Style, Chapter 14.
- ChicagoNotes,
- /// A Chicago-like author-title format. Results could look like this:
- /// Prokopov, “It Is Fast or It Is Wrong”.
- ChicagoAuthorTitle,
-}
-
-impl CitationStyle {
- fn is_short(self) -> bool {
- matches!(self, Self::Numerical | Self::Alphanumerical | Self::Keys)
- }
-}
-
-/// Fully formatted citations and references.
-#[derive(Default)]
-struct Works {
- citations: HashMap<Location, Option<Content>>,
- references: Vec<(Option<Content>, Content)>,
-}
-
-impl Works {
- /// Prepare all things need to cite a work or format a bibliography.
- fn new(vt: &Vt) -> StrResult<Arc<Self>> {
- let bibliography = BibliographyElem::find(vt.introspector)?;
- let citations = vt
- .introspector
- .query(&Selector::Or(eco_vec![
- RefElem::func().select(),
- CiteElem::func().select(),
- ]))
- .into_iter()
- .map(|elem| match elem.to::<RefElem>() {
- Some(reference) => reference.citation().unwrap(),
- _ => elem.to::<CiteElem>().unwrap().clone(),
- })
- .collect();
- Ok(create(bibliography, citations))
- }
-}
-
-/// Generate all citations and the whole bibliography.
-#[comemo::memoize]
-fn create(bibliography: BibliographyElem, citations: Vec<CiteElem>) -> Arc<Works> {
- let span = bibliography.span();
- let entries = load(&bibliography.path(), &bibliography.data()).unwrap();
- let style = bibliography.style(StyleChain::default());
- let bib_location = bibliography.0.location().unwrap();
- let ref_location = |target: &Entry| {
- let i = entries
- .iter()
- .position(|entry| entry.key() == target.key())
- .unwrap_or_default();
- bib_location.variant(i)
- };
-
- let mut db = Database::new();
- let mut ids = HashMap::new();
- let mut preliminary = vec![];
-
- for citation in citations {
- let cite_id = citation.0.location().unwrap();
- let entries = citation
- .keys()
- .into_iter()
- .map(|key| {
- let entry = entries.iter().find(|entry| entry.key() == key)?;
- ids.entry(entry.key()).or_insert(cite_id);
- db.push(entry);
- Some(entry)
- })
- .collect::<Option<Vec<_>>>();
- preliminary.push((citation, entries));
- }
-
- let mut current = CitationStyle::Numerical;
- let mut citation_style: Box<dyn style::CitationStyle> =
- Box::new(style::Numerical::new());
-
- let citations = preliminary
- .into_iter()
- .map(|(citation, cited)| {
- let location = citation.0.location().unwrap();
- let Some(cited) = cited else { return (location, None) };
-
- let mut supplement = citation.supplement(StyleChain::default());
- let brackets = citation.brackets(StyleChain::default());
- let style = citation
- .style(StyleChain::default())
- .unwrap_or(style.default_citation_style());
-
- if style != current {
- current = style;
- citation_style = match style {
- CitationStyle::Numerical => Box::new(style::Numerical::new()),
- CitationStyle::Alphanumerical => {
- Box::new(style::Alphanumerical::new())
- }
- CitationStyle::ChicagoAuthorDate => {
- Box::new(style::ChicagoAuthorDate::new())
- }
- CitationStyle::ChicagoNotes => Box::new(style::ChicagoNotes::new()),
- CitationStyle::ChicagoAuthorTitle => {
- Box::new(style::AuthorTitle::new())
- }
- CitationStyle::Keys => Box::new(style::Keys::new()),
- };
- }
-
- let len = cited.len();
- let mut content = Content::empty();
- for (i, entry) in cited.into_iter().enumerate() {
- let supplement = if i + 1 == len { supplement.take() } else { None };
- let mut display = db
- .citation(
- &mut *citation_style,
- &[Citation {
- entry,
- supplement: supplement.is_some().then_some(SUPPLEMENT),
- }],
- )
- .display;
-
- if style.is_short() {
- display.value = display.value.replace(' ', "\u{a0}");
- }
-
- if brackets && len == 1 {
- display = display.with_default_brackets(&*citation_style);
- }
-
- if i > 0 {
- content += TextElem::packed(",\u{a0}");
- }
-
- // Format and link to the reference entry.
- content += format_display_string(&display, supplement, citation.span())
- .linked(Destination::Location(ref_location(entry)));
- }
-
- if brackets && len > 1 {
- content = match citation_style.brackets() {
- Brackets::None => content,
- Brackets::Round => {
- TextElem::packed('(') + content + TextElem::packed(')')
- }
- Brackets::Square => {
- TextElem::packed('[') + content + TextElem::packed(']')
- }
- };
- }
-
- if style == CitationStyle::ChicagoNotes {
- content = FootnoteElem::with_content(content).pack();
- }
-
- (location, Some(content))
- })
- .collect();
-
- let bibliography_style: Box<dyn style::BibliographyStyle> = match style {
- BibliographyStyle::Apa => Box::new(style::Apa::new()),
- BibliographyStyle::ChicagoAuthorDate => Box::new(style::ChicagoAuthorDate::new()),
- BibliographyStyle::ChicagoNotes => Box::new(style::ChicagoNotes::new()),
- BibliographyStyle::Ieee => Box::new(style::Ieee::new()),
- BibliographyStyle::Mla => Box::new(style::Mla::new()),
- };
-
- let references = db
- .bibliography(&*bibliography_style, None)
- .into_iter()
- .map(|reference| {
- let backlink = ref_location(reference.entry);
- let prefix = reference.prefix.map(|prefix| {
- // Format and link to first citation.
- let bracketed = prefix.with_default_brackets(&*citation_style);
- format_display_string(&bracketed, None, span)
- .linked(Destination::Location(ids[reference.entry.key()]))
- .backlinked(backlink)
- });
-
- let mut reference = format_display_string(&reference.display, None, span);
- if prefix.is_none() {
- reference = reference.backlinked(backlink);
- }
-
- (prefix, reference)
- })
- .collect();
-
- Arc::new(Works { citations, references })
-}
-
-/// Load bibliography entries from a path.
-#[comemo::memoize]
-fn load(paths: &BibPaths, data: &[Bytes]) -> StrResult<EcoVec<hayagriva::Entry>> {
- let mut result = EcoVec::new();
-
- // We might have multiple bib/yaml files
- for (path, bytes) in paths.0.iter().zip(data) {
- let src = std::str::from_utf8(bytes).map_err(|_| FileError::InvalidUtf8)?;
- let entries = parse_bib(path, src)?;
- result.extend(entries);
- }
-
- // Biblatex only checks for duplicate keys within files
- // -> We have to do this between files again
- let mut keys = result.iter().map(|r| r.key()).collect::<Vec<_>>();
- keys.sort_unstable();
- // Waiting for `slice_partition_dedup` #54279
- let mut duplicates = Vec::new();
- for pair in keys.windows(2) {
- if pair[0] == pair[1] {
- duplicates.push(pair[0]);
- }
- }
-
- if !duplicates.is_empty() {
- Err(eco_format!("duplicate bibliography keys: {}", duplicates.join(", ")))
- } else {
- Ok(result)
- }
-}
-
-/// Parse a bibliography file (bib/yml/yaml)
-fn parse_bib(path_str: &str, src: &str) -> StrResult<Vec<hayagriva::Entry>> {
- let path = Path::new(path_str);
- let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
- match ext.to_lowercase().as_str() {
- "yml" | "yaml" => {
- hayagriva::io::from_yaml_str(src).map_err(format_hayagriva_error)
- }
- "bib" => hayagriva::io::from_biblatex_str(src).map_err(|err| {
- err.into_iter()
- .next()
- .map(|error| format_biblatex_error(path_str, src, error))
- .unwrap_or_else(|| eco_format!("failed to parse {path_str}"))
- }),
- _ => bail!("unknown bibliography format (must be .yml/.yaml or .bib)"),
- }
-}
-
-/// Format a Hayagriva loading error.
-fn format_hayagriva_error(error: YamlBibliographyError) -> EcoString {
- eco_format!("{error}")
-}
-
-/// Format a BibLaTeX loading error.
-fn format_biblatex_error(path: &str, src: &str, error: BibLaTeXError) -> EcoString {
- let (span, msg) = match error {
- BibLaTeXError::Parse(error) => (error.span, error.kind.to_string()),
- BibLaTeXError::Type(error) => (error.span, error.kind.to_string()),
- };
- let line = src.get(..span.start).unwrap_or_default().lines().count();
- eco_format!("parsing failed at {path}:{line}: {msg}")
-}
-
-/// Hayagriva only supports strings, but we have a content supplement. To deal
-/// with this, we pass this string to hayagriva instead of our content, find it
-/// in the output and replace it with the content.
-const SUPPLEMENT: &str = "cdc579c45cf3d648905c142c7082683f";
-
-/// Format a display string into content.
-fn format_display_string(
- string: &DisplayString,
- mut supplement: Option<Content>,
- span: Span,
-) -> Content {
- let mut stops: Vec<_> = string
- .formatting
- .iter()
- .flat_map(|(range, _)| [range.start, range.end])
- .collect();
-
- if let Some(i) = string.value.find(SUPPLEMENT) {
- stops.push(i);
- stops.push(i + SUPPLEMENT.len());
- }
-
- stops.sort();
- stops.dedup();
- stops.push(string.value.len());
-
- let mut start = 0;
- let mut seq = vec![];
- for stop in stops {
- let segment = string.value.get(start..stop).unwrap_or_default();
- if segment.is_empty() {
- continue;
- }
-
- let mut content = if segment == SUPPLEMENT && supplement.is_some() {
- supplement.take().unwrap_or_default()
- } else {
- TextElem::packed(segment).spanned(span)
- };
-
- for (range, fmt) in &string.formatting {
- if !range.contains(&start) {
- continue;
- }
-
- content = match fmt {
- Formatting::Bold => content.strong(),
- Formatting::Italic => content.emph(),
- Formatting::Link(link) => {
- LinkElem::new(Destination::Url(link.as_str().into()).into(), content)
- .pack()
- }
- };
- }
-
- seq.push(content);
- start = stop;
- }
-
- Content::sequence(seq)
-}
diff --git a/library/src/meta/context.rs b/library/src/meta/context.rs
deleted file mode 100644
index a42c6980..00000000
--- a/library/src/meta/context.rs
+++ /dev/null
@@ -1,220 +0,0 @@
-use crate::prelude::*;
-
-/// Provides access to the location of content.
-///
-/// This is useful in combination with [queries]($func/query),
-/// [counters]($func/counter), [state]($func/state), and [links]($func/link).
-/// See their documentation for more details.
-///
-/// ```example
-/// #locate(loc => [
-/// My location: \
-/// #loc.position()!
-/// ])
-/// ```
-///
-/// ## Methods
-/// ### page()
-/// Returns the page number for this location.
-///
-/// Note that this does not return the value of the [page counter]($func/counter)
-/// at this location, but the true page number (starting from one).
-///
-/// If you want to know the value of the page counter, use
-/// `{counter(page).at(loc)}` instead.
-///
-/// - returns: integer
-///
-/// ### position()
-/// Returns a dictionary with the page number and the x, y position for this
-/// location. The page number starts at one and the coordinates are measured
-/// from the top-left of the page.
-///
-/// If you only need the page number, use `page()` instead as it allows Typst
-/// to skip unnecessary work.
-///
-/// - returns: dictionary
-///
-/// ### page-numbering()
-/// Returns the page numbering pattern of the page at this location. This can be
-/// used when displaying the page counter in order to obtain the local numbering.
-/// This is useful if you are building custom indices or outlines.
-///
-/// If the page numbering is set to `none` at that location, this function returns `none`.
-///
-/// - returns: string or function or none
-///
-/// Display: Locate
-/// Category: meta
-#[func]
-pub fn locate(
- /// A function that receives a `location`. Its return value is displayed
- /// in the document.
- ///
- /// This function is called once for each time the content returned by
- /// `locate` appears in the document. That makes it possible to generate
- /// content that depends on its own location in the document.
- func: Func,
-) -> Content {
- LocateElem::new(func).pack()
-}
-
-/// Executes a `locate` call.
-///
-/// Display: Locate
-/// Category: special
-#[element(Locatable, Show)]
-struct LocateElem {
- /// The function to call with the location.
- #[required]
- func: Func,
-}
-
-impl Show for LocateElem {
- #[tracing::instrument(name = "LocateElem::show", skip(self, vt))]
- fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(vt.delayed(|vt| {
- let location = self.0.location().unwrap();
- Ok(self.func().call_vt(vt, [location])?.display())
- }))
- }
-}
-
-/// Provides access to active styles.
-///
-/// The styles are currently opaque and only useful in combination with the
-/// [`measure`]($func/measure) function. See its documentation for more details.
-/// In the future, the provided styles might also be directly accessed to look
-/// up styles defined by [set rules]($styling/#set-rules).
-///
-/// ```example
-/// #let thing(body) = style(styles => {
-/// let size = measure(body, styles)
-/// [Width of "#body" is #size.width]
-/// })
-///
-/// #thing[Hey] \
-/// #thing[Welcome]
-/// ```
-///
-/// Display: Style
-/// Category: meta
-#[func]
-pub fn style(
- /// A function to call with the styles. Its return value is displayed
- /// in the document.
- ///
- /// This function is called once for each time the content returned by
- /// `style` appears in the document. That makes it possible to generate
- /// content that depends on the style context it appears in.
- func: Func,
-) -> Content {
- StyleElem::new(func).pack()
-}
-
-/// Executes a style access.
-///
-/// Display: Style
-/// Category: special
-#[element(Show)]
-struct StyleElem {
- /// The function to call with the styles.
- #[required]
- func: Func,
-}
-
-impl Show for StyleElem {
- #[tracing::instrument(name = "StyleElem::show", skip_all)]
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- Ok(self.func().call_vt(vt, [styles.to_map()])?.display())
- }
-}
-
-/// Provides access to the current outer container's (or page's, if none) size
-/// (width and height).
-///
-/// The given function must accept a single parameter, `size`, which is a
-/// dictionary with keys `width` and `height`, both of type
-/// [`length`]($type/length).
-///
-
-/// ```example
-/// #let text = lorem(30)
-/// #layout(size => style(styles => [
-/// #let (height,) = measure(
-/// block(width: size.width, text),
-/// styles,
-/// )
-/// This text is #height high with
-/// the current page width: \
-/// #text
-/// ]))
-/// ```
-///
-/// If the `layout` call is placed inside of a box width a width of `{800pt}`
-/// and a height of `{400pt}`, then the specified function will be given the
-/// parameter `{(width: 800pt, height: 400pt)}`. If it placed directly into the
-/// page it receives the page's dimensions minus its margins. This is mostly
-/// useful in combination with [measurement]($func/measure).
-///
-/// You can also use this function to resolve [`ratio`]($type/ratio) to fixed
-/// lengths. This might come in handy if you're building your own layout
-/// abstractions.
-///
-/// ```example
-/// #layout(size => {
-/// let half = 50% * size.width
-/// [Half a page is #half wide.]
-/// })
-/// ```
-///
-/// Note that this function will provide an infinite width or height if one of
-/// the page width or height is `auto`, respectively.
-///
-/// Display: Layout
-/// Category: meta
-#[func]
-pub fn layout(
- /// A function to call with the outer container's size. Its return value is
- /// displayed in the document.
- ///
- /// The container's size is given as a [dictionary]($type/dictionary) with
- /// the keys `width` and `height`.
- ///
- /// This function is called once for each time the content returned by
- /// `layout` appears in the document. That makes it possible to generate
- /// content that depends on the size of the container it is inside of.
- func: Func,
-) -> Content {
- LayoutElem::new(func).pack()
-}
-
-/// Executes a `layout` call.
-///
-/// Display: Layout
-/// Category: special
-#[element(Layout)]
-struct LayoutElem {
- /// The function to call with the outer container's (or page's) size.
- #[required]
- func: Func,
-}
-
-impl Layout for LayoutElem {
- #[tracing::instrument(name = "LayoutElem::layout", skip_all)]
- fn layout(
- &self,
- vt: &mut Vt,
- styles: StyleChain,
- regions: Regions,
- ) -> SourceResult<Fragment> {
- // Gets the current region's base size, which will be the size of the
- // outer container, or of the page if there is no such container.
- let Size { x, y } = regions.base();
- let result = self
- .func()
- .call_vt(vt, [dict! { "width" => x, "height" => y }])?
- .display();
- result.layout(vt, styles, regions)
- }
-}
diff --git a/library/src/meta/counter.rs b/library/src/meta/counter.rs
deleted file mode 100644
index 9a223b32..00000000
--- a/library/src/meta/counter.rs
+++ /dev/null
@@ -1,683 +0,0 @@
-use std::fmt::{self, Debug, Formatter, Write};
-use std::str::FromStr;
-
-use ecow::{eco_vec, EcoVec};
-use smallvec::{smallvec, SmallVec};
-use typst::eval::Tracer;
-use typst::model::DelayedErrors;
-
-use super::{FigureElem, HeadingElem, Numbering, NumberingPattern};
-use crate::layout::PageElem;
-use crate::math::EquationElem;
-use crate::prelude::*;
-
-/// Counts through pages, elements, and more.
-///
-/// With the counter function, you can access and modify counters for pages,
-/// headings, figures, and more. Moreover, you can define custom counters for
-/// other things you want to count.
-///
-/// ## Displaying a counter { #displaying }
-/// To display the current value of the heading counter, you call the `counter`
-/// function with the `key` set to `heading` and then call the `display` method
-/// on the counter. To see any output, you also have to enable heading
-/// [numbering]($func/heading.numbering).
-///
-/// The `display` method optionally takes an argument telling it how to format
-/// the counter. This can be a [numbering pattern or a
-/// function]($func/numbering).
-///
-/// ```example
-/// #set heading(numbering: "1.")
-///
-/// = Introduction
-/// Some text here.
-///
-/// = Background
-/// The current value is:
-/// #counter(heading).display()
-///
-/// Or in roman numerals:
-/// #counter(heading).display("I")
-/// ```
-///
-/// ## Modifying a counter { #modifying }
-/// To modify a counter, you can use the `step` and `update` methods:
-///
-/// - The `step` method increases the value of the counter by one. Because
-/// counters can have multiple levels (in the case of headings for sections,
-/// subsections, and so on), the `step` method optionally takes a `level`
-/// argument. If given, the counter steps at the given depth.
-///
-/// - The `update` method allows you to arbitrarily modify the counter. In its
-/// basic form, you give it an integer (or multiple for multiple levels). For
-/// more flexibility, you can instead also give it a function that gets the
-/// current value and returns a new value.
-///
-/// The heading counter is stepped before the heading is displayed, so
-/// `Analysis` gets the number seven even though the counter is at six after the
-/// second update.
-///
-/// ```example
-/// #set heading(numbering: "1.")
-///
-/// = Introduction
-/// #counter(heading).step()
-///
-/// = Background
-/// #counter(heading).update(3)
-/// #counter(heading).update(n => n * 2)
-///
-/// = Analysis
-/// Let's skip 7.1.
-/// #counter(heading).step(level: 2)
-///
-/// == Analysis
-/// Still at #counter(heading).display().
-/// ```
-///
-/// ## Custom counters { #custom-counters }
-/// To define your own counter, call the `counter` function with a string as a
-/// key. This key identifies the counter globally.
-///
-/// ```example
-/// #let mine = counter("mycounter")
-/// #mine.display() \
-/// #mine.step()
-/// #mine.display() \
-/// #mine.update(c => c * 3)
-/// #mine.display() \
-/// ```
-///
-/// ## How to step { #how-to-step }
-/// When you define and use a custom counter, in general, you should first step
-/// the counter and then display it. This way, the stepping behaviour of a
-/// counter can depend on the element it is stepped for. If you were writing a
-/// counter for, let's say, theorems, your theorem's definition would thus first
-/// include the counter step and only then display the counter and the theorem's
-/// contents.
-///
-/// ```example
-/// #let c = counter("theorem")
-/// #let theorem(it) = block[
-/// #c.step()
-/// *Theorem #c.display():* #it
-/// ]
-///
-/// #theorem[$1 = 1$]
-/// #theorem[$2 < 3$]
-/// ```
-///
-/// The rationale behind this is best explained on the example of the heading
-/// counter: An update to the heading counter depends on the heading's level.
-/// By stepping directly before the heading, we can correctly step from `1` to
-/// `1.1` when encountering a level 2 heading. If we were to step after the
-/// heading, we wouldn't know what to step to.
-///
-/// Because counters should always be stepped before the elements they count,
-/// they always start at zero. This way, they are at one for the first display
-/// (which happens after the first step).
-///
-/// ## Page counter { #page-counter }
-/// The page counter is special. It is automatically stepped at each pagebreak.
-/// But like other counters, you can also step it manually. For example, you
-/// could have Roman page numbers for your preface, then switch to Arabic page
-/// numbers for your main content and reset the page counter to one.
-///
-/// ```example
-/// >>> #set page(
-/// >>> height: 100pt,
-/// >>> margin: (bottom: 24pt, rest: 16pt),
-/// >>> )
-/// #set page(numbering: "(i)")
-///
-/// = Preface
-/// The preface is numbered with
-/// roman numerals.
-///
-/// #set page(numbering: "1 / 1")
-/// #counter(page).update(1)
-///
-/// = Main text
-/// Here, the counter is reset to one.
-/// We also display both the current
-/// page and total number of pages in
-/// Arabic numbers.
-/// ```
-///
-/// ## Time travel { #time-travel }
-/// Counters can travel through time! You can find out the final value of the
-/// counter before it is reached and even determine what the value was at any
-/// particular location in the document.
-///
-/// ```example
-/// #let mine = counter("mycounter")
-///
-/// = Values
-/// #locate(loc => {
-/// let start-val = mine.at(loc)
-/// let elements = query(<intro>, loc)
-/// let intro-val = mine.at(
-/// elements.first().location()
-/// )
-/// let final-val = mine.final(loc)
-/// [Starts as: #start-val \
-/// Value at intro is: #intro-val \
-/// Final value is: #final-val \ ]
-/// })
-///
-/// #mine.update(n => n + 3)
-///
-/// = Introduction <intro>
-/// #lorem(10)
-///
-/// #mine.step()
-/// #mine.step()
-/// ```
-///
-/// Let's dissect what happens in the example above:
-///
-/// - We call [`locate`]($func/locate) to get access to the current location in
-/// the document. We then pass this location to our counter's `at` method to
-/// get its value at the current location. The `at` method always returns an
-/// array because counters can have multiple levels. As the counter starts at
-/// one, the first value is thus `{(1,)}`.
-///
-/// - We now [`query`]($func/query) the document for all elements with the
-/// `{<intro>}` label. The result is an array from which we extract the first
-/// (and only) element's [location]($type/content.location). We then look up
-/// the value of the counter at that location. The first update to the counter
-/// sets it to `{1 + 3 = 4}`. At the introduction heading, the value is thus
-/// `{(4,)}`.
-///
-/// - Last but not least, we call the `final` method on the counter. It tells us
-/// what the counter's value will be at the end of the document. We also need
-/// to give it a location to prove that we are inside of a `locate` call, but
-/// which one doesn't matter. After the heading follow two calls to `step()`,
-/// so the final value is `{(6,)}`.
-///
-/// ## Other kinds of state { #other-state }
-/// The `counter` function is closely related to [state]($func/state) function.
-/// Read its documentation for more details on state management in Typst and
-/// why it doesn't just use normal variables for counters.
-///
-/// ## Methods
-/// ### display()
-/// Displays the value of the counter.
-///
-/// - numbering: string or function (positional)
-/// A [numbering pattern or a function]($func/numbering), which specifies how
-/// to display the counter. If given a function, that function receives each
-/// number of the counter as a separate argument. If the amount of numbers
-/// varies, e.g. for the heading argument, you can use an
-/// [argument sink]($type/arguments).
-///
-/// If this is omitted, displays the counter with the numbering style for the
-/// counted element or with the pattern `{"1.1"}` if no such style exists.
-///
-/// - both: boolean (named)
-/// If enabled, displays the current and final top-level count together. Both
-/// can be styled through a single numbering pattern. This is used by the page
-/// numbering property to display the current and total number of pages when a
-/// pattern like `{"1 / 1"}` is given.
-///
-/// - returns: content
-///
-/// ### step()
-/// Increases the value of the counter by one.
-///
-/// The update will be in effect at the position where the returned content is
-/// inserted into the document. If you don't put the output into the document,
-/// nothing happens! This would be the case, for example, if you write
-/// `{let _ = counter(page).step()}`. Counter updates are always applied in
-/// layout order and in that case, Typst wouldn't know when to step the counter.
-///
-/// - level: integer (named)
-/// The depth at which to step the counter. Defaults to `{1}`.
-///
-/// - returns: content
-///
-/// ### update()
-/// Updates the value of the counter.
-///
-/// Just like with `step`, the update only occurs if you put the resulting
-/// content into the document.
-///
-/// - value: integer or array or function (positional, required)
-/// If given an integer or array of integers, sets the counter to that value.
-/// If given a function, that function receives the previous counter value
-/// (with each number as a separate argument) and has to return the new
-/// value (integer or array).
-///
-/// - returns: content
-///
-/// ### at()
-/// Gets the value of the counter at the given location. Always returns an
-/// array of integers, even if the counter has just one number.
-///
-/// - location: location (positional, required)
-/// The location at which the counter value should be retrieved. A suitable
-/// location can be retrieved from [`locate`]($func/locate) or
-/// [`query`]($func/query).
-///
-/// - returns: array
-///
-/// ### final()
-/// Gets the value of the counter at the end of the document. Always returns an
-/// array of integers, even if the counter has just one number.
-///
-/// - location: location (positional, required)
-/// Can be any location. Why is it required then? Typst has to evaluate parts
-/// of your code multiple times to determine all counter values. By only
-/// allowing this method within [`locate`]($func/locate) calls, the amount of
-/// code that can depend on the method's result is reduced. If you could call
-/// `final` directly at the top level of a module, the evaluation of the whole
-/// module and its exports could depend on the counter's value.
-///
-/// - returns: array
-///
-/// Display: Counter
-/// Category: meta
-#[func]
-pub fn counter(
- /// The key that identifies this counter.
- ///
- /// - If it is a string, creates a custom counter that is only affected by
- /// manual updates,
- /// - If this is a `{<label>}`, counts through all elements with that label,
- /// - If this is an element function or selector, counts through its elements,
- /// - If this is the [`page`]($func/page) function, counts through pages.
- key: CounterKey,
-) -> Counter {
- 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 element.
- pub fn of(func: ElemFunc) -> Self {
- Self::new(CounterKey::Selector(Selector::Elem(func, None)))
- }
-
- /// Call a method on counter.
- #[tracing::instrument(skip(vm))]
- 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()?, args.named("both")?.unwrap_or(false))
- .into_value(),
- "step" => self
- .update(CounterUpdate::Step(
- args.named("level")?.unwrap_or(NonZeroUsize::ONE),
- ))
- .into_value(),
- "update" => self.update(args.expect("value or function")?).into_value(),
- "at" => self.at(&mut vm.vt, args.expect("location")?)?.into_value(),
- "final" => self.final_(&mut vm.vt, args.expect("location")?)?.into_value(),
- _ => bail!(span, "type counter has no method `{}`", method),
- };
- args.finish()?;
- Ok(value)
- }
-
- /// Display the current value of the counter.
- pub fn display(self, numbering: Option<Numbering>, both: bool) -> Content {
- DisplayElem::new(self, numbering, both).pack()
- }
-
- /// Get the value of the state at the given location.
- pub fn at(&self, vt: &mut Vt, location: Location) -> SourceResult<CounterState> {
- let sequence = self.sequence(vt)?;
- let offset = vt.introspector.query(&self.selector().before(location, true)).len();
- let (mut state, page) = sequence[offset].clone();
- if self.is_page() {
- let delta = vt.introspector.page(location).get().saturating_sub(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, _: Location) -> 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().saturating_sub(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, location: Location) -> SourceResult<CounterState> {
- let sequence = self.sequence(vt)?;
- let offset = vt
- .introspector
- .query(&Selector::before(self.selector(), location, true))
- .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(location).get().saturating_sub(at_page.get());
- at_state.step(NonZeroUsize::ONE, at_delta);
- let final_delta =
- vt.introspector.pages().get().saturating_sub(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 {
- UpdateElem::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,
- vt.introspector,
- vt.locator.track(),
- TrackedMut::reborrow_mut(&mut vt.delayed),
- TrackedMut::reborrow_mut(&mut vt.tracer),
- )
- }
-
- /// Memoized implementation of `sequence`.
- #[comemo::memoize]
- fn sequence_impl(
- &self,
- world: Tracked<dyn World + '_>,
- introspector: Tracked<Introspector>,
- locator: Tracked<Locator>,
- delayed: TrackedMut<DelayedErrors>,
- tracer: TrackedMut<Tracer>,
- ) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {
- let mut locator = Locator::chained(locator);
- let mut vt = Vt {
- world,
- introspector,
- locator: &mut locator,
- delayed,
- tracer,
- };
- let mut state = CounterState(match &self.0 {
- // special case, because pages always start at one.
- CounterKey::Page => smallvec![1],
- _ => smallvec![0],
- });
- let mut page = NonZeroUsize::ONE;
- let mut stops = eco_vec![(state.clone(), page)];
-
- for elem in introspector.query(&self.selector()) {
- if self.is_page() {
- let prev = page;
- page = introspector.page(elem.location().unwrap());
-
- let delta = page.get() - prev.get();
- if delta > 0 {
- state.step(NonZeroUsize::ONE, delta);
- }
- }
-
- if let Some(update) = match elem.to::<UpdateElem>() {
- Some(elem) => Some(elem.update()),
- None => match elem.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::Elem(UpdateElem::func(), Some(dict! { "counter" => self.clone() }));
-
- if let CounterKey::Selector(key) = &self.0 {
- selector = Selector::Or(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! {
- type Counter: "counter",
-}
-
-/// Identifies a counter.
-#[derive(Clone, PartialEq, Hash)]
-pub enum CounterKey {
- /// The page counter.
- Page,
- /// Counts elements matching the given selectors. Only works for locatable
- /// elements or labels.
- Selector(Selector),
- /// Counts through manual counters with the same key.
- Str(Str),
-}
-
-cast! {
- CounterKey,
- v: Str => Self::Str(v),
- label: Label => Self::Selector(Selector::Label(label)),
- v: ElemFunc => {
- if v == PageElem::func() {
- Self::Page
- } else {
- Self::Selector(LocatableSelector::from_value(v.into_value())?.0)
- }
- },
- selector: LocatableSelector => Self::Selector(selector.0),
-}
-
-impl Debug for CounterKey {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Page => f.pad("page"),
- Self::Selector(selector) => selector.fmt(f),
- Self::Str(str) => str.fmt(f),
- }
- }
-}
-
-/// An update to perform on a counter.
-#[derive(Clone, PartialEq, Hash)]
-pub enum CounterUpdate {
- /// Set the counter to the specified state.
- Set(CounterState),
- /// Increase the number for the given level by one.
- Step(NonZeroUsize),
- /// Apply the given function to the counter's state.
- Func(Func),
-}
-
-impl Debug for CounterUpdate {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad("..")
- }
-}
-
-cast! {
- type CounterUpdate: "counter update",
- v: CounterState => Self::Set(v),
- v: Func => Self::Func(v),
-}
-
-/// Elements that have special counting behaviour.
-pub trait Count {
- /// Get the counter update for this element.
- fn update(&self) -> Option<CounterUpdate>;
-}
-
-/// Counts through elements with different levels.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub struct CounterState(pub SmallVec<[usize; 3]>);
-
-impl CounterState {
- /// Advance the counter and return the numbers for the given heading.
- pub fn update(&mut self, vt: &mut Vt, update: CounterUpdate) -> SourceResult<()> {
- match update {
- CounterUpdate::Set(state) => *self = state,
- CounterUpdate::Step(level) => self.step(level, 1),
- CounterUpdate::Func(func) => {
- *self = func.call_vt(vt, self.0.iter().copied())?.cast().at(func.span())?
- }
- }
- Ok(())
- }
-
- /// Advance the number of the given level by the specified amount.
- pub fn step(&mut self, level: NonZeroUsize, by: usize) {
- let level = level.get();
-
- if self.0.len() >= level {
- self.0[level - 1] = self.0[level - 1].saturating_add(by);
- self.0.truncate(level);
- }
-
- while self.0.len() < level {
- self.0.push(1);
- }
- }
-
- /// Get the first number of the state.
- pub fn first(&self) -> usize {
- self.0.first().copied().unwrap_or(1)
- }
-
- /// 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! {
- CounterState,
- self => Value::Array(self.0.into_iter().map(IntoValue::into_value).collect()),
- num: usize => Self(smallvec![num]),
- array: Array => Self(array
- .into_iter()
- .map(Value::cast)
- .collect::<StrResult<_>>()?),
-}
-
-/// Executes a display of a state.
-///
-/// Display: State
-/// Category: special
-#[element(Locatable, Show)]
-struct DisplayElem {
- /// The counter.
- #[required]
- counter: Counter,
-
- /// The numbering to display the counter with.
- #[required]
- numbering: Option<Numbering>,
-
- /// Whether to display both the current and final value.
- #[required]
- both: bool,
-}
-
-impl Show for DisplayElem {
- #[tracing::instrument(name = "DisplayElem::show", skip_all)]
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- Ok(vt.delayed(|vt| {
- let location = self.0.location().unwrap();
- let counter = self.counter();
- let numbering = self
- .numbering()
- .or_else(|| {
- let CounterKey::Selector(Selector::Elem(func, _)) = counter.0 else {
- return None;
- };
-
- if func == HeadingElem::func() {
- HeadingElem::numbering_in(styles)
- } else if func == FigureElem::func() {
- FigureElem::numbering_in(styles)
- } else if func == EquationElem::func() {
- EquationElem::numbering_in(styles)
- } else {
- None
- }
- })
- .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into());
-
- let state = if self.both() {
- counter.both(vt, location)?
- } else {
- counter.at(vt, location)?
- };
-
- state.display(vt, &numbering)
- }))
- }
-}
-
-/// Executes a display of a state.
-///
-/// Display: State
-/// Category: special
-#[element(Locatable, Show)]
-struct UpdateElem {
- /// The counter.
- #[required]
- counter: Counter,
-
- /// The update to perform on the counter.
- #[required]
- update: CounterUpdate,
-}
-
-impl Show for UpdateElem {
- #[tracing::instrument(name = "UpdateElem::show", skip(self))]
- fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(Content::empty())
- }
-}
diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs
deleted file mode 100644
index db036e0a..00000000
--- a/library/src/meta/document.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-use crate::layout::{LayoutRoot, PageElem};
-use crate::prelude::*;
-
-/// The root element of a document and its metadata.
-///
-/// All documents are automatically wrapped in a `document` element. You cannot
-/// create a document element yourself. This function is only used with
-/// [set rules]($styling/#set-rules) to specify document metadata. Such a set
-/// rule must appear before any of the document's contents.
-///
-/// ```example
-/// #set document(title: "Hello")
-///
-/// This has no visible output, but
-/// embeds metadata into the PDF!
-/// ```
-///
-/// Note that metadata set with this function is not rendered within the
-/// document. Instead, it is embedded in the compiled PDF file.
-///
-/// Display: Document
-/// Category: meta
-#[element(Construct, LayoutRoot)]
-pub struct DocumentElem {
- /// The document's title. This is often rendered as the title of the
- /// PDF viewer window.
- pub title: Option<EcoString>,
-
- /// The document's authors.
- pub author: Author,
-
- /// The page runs.
- #[internal]
- #[variadic]
- pub children: Vec<Content>,
-}
-
-impl Construct for DocumentElem {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- bail!(args.span, "can only be used in set rules")
- }
-}
-
-impl LayoutRoot for DocumentElem {
- /// Layout the document into a sequence of frames, one per page.
- #[tracing::instrument(name = "DocumentElem::layout_root", skip_all)]
- fn layout_root(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Document> {
- tracing::info!("Document layout");
-
- let mut pages = vec![];
-
- for mut child in &self.children() {
- let outer = styles;
- let mut styles = styles;
- if let Some((elem, local)) = child.to_styled() {
- styles = outer.chain(local);
- child = elem;
- }
-
- if let Some(page) = child.to::<PageElem>() {
- let number = NonZeroUsize::ONE.saturating_add(pages.len());
- let fragment = page.layout(vt, styles, number)?;
- pages.extend(fragment);
- } else {
- bail!(child.span(), "unexpected document child");
- }
- }
-
- Ok(Document {
- pages,
- title: self.title(styles),
- author: self.author(styles).0,
- })
- }
-}
-
-/// A list of authors.
-#[derive(Debug, Default, Clone, Hash)]
-pub struct Author(Vec<EcoString>);
-
-cast! {
- Author,
- self => self.0.into_value(),
- v: EcoString => Self(vec![v]),
- v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
-}
diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs
deleted file mode 100644
index 0d218770..00000000
--- a/library/src/meta/figure.rs
+++ /dev/null
@@ -1,351 +0,0 @@
-use std::str::FromStr;
-
-use super::{
- Count, Counter, CounterKey, CounterUpdate, LocalName, Numbering, NumberingPattern,
-};
-use crate::layout::{BlockElem, VElem};
-use crate::meta::{Outlinable, Refable, Supplement};
-use crate::prelude::*;
-use crate::text::TextElem;
-use crate::visualize::ImageElem;
-
-/// A figure with an optional caption.
-///
-/// Automatically detects its contents to select the correct counting track.
-/// For example, figures containing images will be numbered separately from
-/// figures containing tables.
-///
-/// ## Examples { #examples }
-/// The example below shows a basic figure with an image:
-/// ```example
-/// @glacier shows a glacier. Glaciers
-/// are complex systems.
-///
-/// #figure(
-/// image("glacier.jpg", width: 80%),
-/// caption: [A curious figure.],
-/// ) <glacier>
-/// ```
-///
-/// You can also insert [tables]($func/table) into figures to give them a
-/// caption. The figure will detect this and automatically use a separate
-/// counter.
-///
-/// ```example
-/// #figure(
-/// table(
-/// columns: 4,
-/// [t], [1], [2], [3],
-/// [y], [0.3s], [0.4s], [0.8s],
-/// ),
-/// caption: [Timing results],
-/// )
-/// ```
-///
-/// This behaviour can be overridden by explicitly specifying the figure's
-/// `kind`. All figures of the same kind share a common counter.
-///
-/// ## Modifying the appearance { #modifying-appearance }
-/// You can completely customize the look of your figures with a [show
-/// rule]($styling/#show-rules). In the example below, we show the figure's
-/// caption above its body and display its supplement and counter after the
-/// caption.
-///
-/// ```example
-/// #show figure: it => align(center)[
-/// #it.caption |
-/// #emph[
-/// #it.supplement
-/// #it.counter.display(it.numbering)
-/// ]
-/// #v(10pt, weak: true)
-/// #it.body
-/// ]
-///
-/// #figure(
-/// image("molecular.jpg", width: 80%),
-/// caption: [
-/// The molecular testing pipeline.
-/// ],
-/// )
-/// ```
-///
-/// If your figure is too large and its contents are breakable across pages
-/// (e.g. if it contains a large table), then you can make the figure breakable
-/// across pages as well by using `#show figure: set block(breakable: true)`
-/// (see the [block]($func/block) documentation for more information).
-///
-/// Display: Figure
-/// Category: meta
-#[element(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)]
-pub struct FigureElem {
- /// The content of the figure. Often, an [image]($func/image).
- #[required]
- pub body: Content,
-
- /// The figure's caption.
- pub caption: Option<Content>,
-
- /// The kind of the figure this is.
- ///
- /// If set to `{auto}`, the figure will try to automatically determine its
- /// kind. All figures of the same kind share a common counter.
- ///
- /// Setting this to something other than `{auto}` will override the
- /// automatic detection. This can be useful if
- /// - you wish to create a custom figure type that is not an
- /// [image]($func/image), a [table]($func/table) or [code]($func/raw),
- /// - you want to force the figure to use a specific counter regardless of
- /// its content.
- ///
- /// You can set the kind to be an element function or a string. If you set
- /// it to an element function that is not supported by the figure, you will
- /// need to manually specify the figure's supplement.
- ///
- /// ```example
- /// #figure(
- /// circle(radius: 10pt),
- /// caption: [A curious atom.],
- /// kind: "atom",
- /// supplement: [Atom],
- /// )
- /// ```
- #[default(Smart::Auto)]
- pub kind: Smart<FigureKind>,
-
- /// The figure's supplement.
- ///
- /// If set to `{auto}`, the figure will try to automatically determine the
- /// correct supplement based on the `kind` and the active [text
- /// language]($func/text.lang). If you are using a custom figure type, you
- /// will need to manually specify the supplement.
- ///
- /// If a function is specified, it is passed the first descendant of the
- /// specified `kind` (typically, the figure's body) and should return
- /// content.
- ///
- /// ```example
- /// #figure(
- /// [The contents of my figure!],
- /// caption: [My custom figure],
- /// supplement: [Bar],
- /// kind: "foo",
- /// )
- /// ```
- pub supplement: Smart<Option<Supplement>>,
-
- /// How to number the figure. Accepts a
- /// [numbering pattern or function]($func/numbering).
- #[default(Some(NumberingPattern::from_str("1").unwrap().into()))]
- pub numbering: Option<Numbering>,
-
- /// The vertical gap between the body and caption.
- #[default(Em::new(0.65).into())]
- pub gap: Length,
-
- /// Whether the figure should appear in an [`outline`]($func/outline)
- /// of figures.
- #[default(true)]
- pub outlined: bool,
-
- /// Convenience field to get access to the counter for this figure.
- ///
- /// The counter only depends on the `kind`:
- /// - For (tables)[$func/table]: `{counter(figure.where(kind: table))}`
- /// - For (images)[$func/image]: `{counter(figure.where(kind: image))}`
- /// - For a custom kind: `{counter(figure.where(kind: kind))}`
- ///
- /// These are the counters you'll need to modify if you want to skip a
- /// number or reset the counter.
- #[synthesized]
- pub counter: Option<Counter>,
-}
-
-impl Synthesize for FigureElem {
- fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
- let numbering = self.numbering(styles);
-
- // Determine the figure's kind.
- let kind = self.kind(styles).unwrap_or_else(|| {
- self.body()
- .query_first(Selector::can::<dyn Figurable>())
- .cloned()
- .map(|elem| FigureKind::Elem(elem.func()))
- .unwrap_or_else(|| FigureKind::Elem(ImageElem::func()))
- });
-
- // Resolve the supplement.
- let supplement = match self.supplement(styles) {
- Smart::Auto => {
- // Default to the local name for the kind, if available.
- let name = match &kind {
- FigureKind::Elem(func) => {
- let empty = Content::new(*func);
- empty.with::<dyn LocalName>().map(|c| {
- TextElem::packed(c.local_name(
- TextElem::lang_in(styles),
- TextElem::region_in(styles),
- ))
- })
- }
- FigureKind::Name(_) => None,
- };
-
- if numbering.is_some() && name.is_none() {
- bail!(self.span(), "please specify the figure's supplement")
- }
-
- name.unwrap_or_default()
- }
- Smart::Custom(None) => Content::empty(),
- Smart::Custom(Some(supplement)) => {
- // Resolve the supplement with the first descendant of the kind or
- // just the body, if none was found.
- let descendant = match kind {
- FigureKind::Elem(func) => {
- self.body().query_first(Selector::Elem(func, None)).cloned()
- }
- FigureKind::Name(_) => None,
- };
-
- let target = descendant.unwrap_or_else(|| self.body());
- supplement.resolve(vt, [target])?
- }
- };
-
- // Construct the figure's counter.
- let counter = Counter::new(CounterKey::Selector(Selector::Elem(
- Self::func(),
- Some(dict! {
- "kind" => kind.clone(),
- }),
- )));
-
- self.push_caption(self.caption(styles));
- self.push_kind(Smart::Custom(kind));
- self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
- self.push_numbering(numbering);
- self.push_outlined(self.outlined(styles));
- self.push_counter(Some(counter));
-
- Ok(())
- }
-}
-
-impl Show for FigureElem {
- #[tracing::instrument(name = "FigureElem::show", skip_all)]
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = self.body();
-
- // Build the caption, if any.
- if let Some(caption) = self.full_caption(vt)? {
- realized += VElem::weak(self.gap(styles).into()).pack();
- realized += caption;
- }
-
- // Wrap the contents in a block.
- Ok(BlockElem::new()
- .with_body(Some(realized))
- .pack()
- .aligned(Axes::with_x(Some(Align::Center.into()))))
- }
-}
-
-impl Finalize for FigureElem {
- fn finalize(&self, realized: Content, _: StyleChain) -> Content {
- // Allow breakable figures with `show figure: set block(breakable: true)`.
- realized.styled(BlockElem::set_breakable(false))
- }
-}
-
-impl Count for FigureElem {
- fn update(&self) -> Option<CounterUpdate> {
- // If the figure is numbered, step the counter by one.
- // This steps the `counter(figure)` which is global to all numbered figures.
- self.numbering(StyleChain::default())
- .is_some()
- .then(|| CounterUpdate::Step(NonZeroUsize::ONE))
- }
-}
-
-impl Refable for FigureElem {
- fn supplement(&self) -> Content {
- // After synthesis, this should always be custom content.
- match self.supplement(StyleChain::default()) {
- Smart::Custom(Some(Supplement::Content(content))) => content,
- _ => Content::empty(),
- }
- }
-
- fn counter(&self) -> Counter {
- self.counter().unwrap_or_else(|| Counter::of(Self::func()))
- }
-
- fn numbering(&self) -> Option<Numbering> {
- self.numbering(StyleChain::default())
- }
-}
-
-impl Outlinable for FigureElem {
- fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
- if !self.outlined(StyleChain::default()) {
- return Ok(None);
- }
-
- self.full_caption(vt)
- }
-}
-
-impl FigureElem {
- /// Builds the full caption for the figure (with supplement and numbering).
- pub fn full_caption(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
- let Some(mut caption) = self.caption(StyleChain::default()) else {
- return Ok(None);
- };
-
- if let (
- Smart::Custom(Some(Supplement::Content(mut supplement))),
- Some(counter),
- Some(numbering),
- ) = (
- self.supplement(StyleChain::default()),
- self.counter(),
- self.numbering(StyleChain::default()),
- ) {
- let loc = self.0.location().unwrap();
- let numbers = counter.at(vt, loc)?.display(vt, &numbering)?;
-
- if !supplement.is_empty() {
- supplement += TextElem::packed("\u{a0}");
- }
-
- caption = supplement + numbers + TextElem::packed(": ") + caption;
- }
-
- Ok(Some(caption))
- }
-}
-
-/// The `kind` parameter of a [`FigureElem`].
-#[derive(Debug, Clone)]
-pub enum FigureKind {
- /// The kind is an element function.
- Elem(ElemFunc),
- /// The kind is a name.
- Name(EcoString),
-}
-
-cast! {
- FigureKind,
- self => match self {
- Self::Elem(v) => v.into_value(),
- Self::Name(v) => v.into_value(),
- },
- v: ElemFunc => Self::Elem(v),
- v: EcoString => Self::Name(v),
-}
-
-/// An element that can be auto-detected in a figure.
-///
-/// This trait is used to determine the type of a figure.
-pub trait Figurable: LocalName {}
diff --git a/library/src/meta/footnote.rs b/library/src/meta/footnote.rs
deleted file mode 100644
index 31ec9fe9..00000000
--- a/library/src/meta/footnote.rs
+++ /dev/null
@@ -1,299 +0,0 @@
-use comemo::Prehashed;
-use std::str::FromStr;
-
-use super::{Counter, Numbering, NumberingPattern};
-use crate::layout::{HElem, ParElem};
-use crate::meta::{Count, CounterUpdate};
-use crate::prelude::*;
-use crate::text::{SuperElem, TextElem, TextSize};
-use crate::visualize::LineElem;
-
-/// The body of a footnote can be either some content or a label referencing
-/// another footnote.
-#[derive(Debug)]
-pub enum FootnoteBody {
- Content(Content),
- Reference(Label),
-}
-
-cast! {
- FootnoteBody,
- self => match self {
- Self::Content(v) => v.into_value(),
- Self::Reference(v) => v.into_value(),
- },
- v: Content => Self::Content(v),
- v: Label => Self::Reference(v),
-}
-
-/// A footnote.
-///
-/// Includes additional remarks and references on the same page with footnotes.
-/// A footnote will insert a superscript number that links to the note at the
-/// bottom of the page. Notes are numbered sequentially throughout your document
-/// and can break across multiple pages.
-///
-/// To customize the appearance of the entry in the footnote listing, see
-/// [`footnote.entry`]($func/footnote.entry). The footnote itself is realized as
-/// a normal superscript, so you can use a set rule on the
-/// [`super`]($func/super) function to customize it.
-///
-/// ## Example { #example }
-/// ```example
-/// Check the docs for more details.
-/// #footnote[https://typst.app/docs]
-/// ```
-///
-/// The footnote automatically attaches itself to the preceding word, even if
-/// there is a space before it in the markup. To force space, you can use the
-/// string `[#" "]` or explicit [horizontal spacing]($func/h).
-///
-/// By giving a label to a footnote, you can have multiple references to it.
-///
-/// ```example
-/// You can edit Typst documents online.
-/// #footnote[https://typst.app/app] <fn>
-/// Checkout Typst's website. @fn
-/// And the online app. #footnote(<fn>)
-/// ```
-///
-/// _Note:_ Set and show rules in the scope where `footnote` is called may not
-/// apply to the footnote's content. See [here][issue] more information.
-///
-/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
-///
-/// Display: Footnote
-/// Category: meta
-#[element(Locatable, Synthesize, Show, Count)]
-#[scope(
- scope.define("entry", FootnoteEntry::func());
- scope
-)]
-pub struct FootnoteElem {
- /// How to number footnotes.
- ///
- /// By default, the footnote numbering continues throughout your document.
- /// If you prefer per-page footnote numbering, you can reset the footnote
- /// [counter]($func/counter) in the page [header]($func/page.header). In the
- /// future, there might be a simpler way to achieve this.
- ///
- /// ```example
- /// #set footnote(numbering: "*")
- ///
- /// Footnotes:
- /// #footnote[Star],
- /// #footnote[Dagger]
- /// ```
- #[default(Numbering::Pattern(NumberingPattern::from_str("1").unwrap()))]
- pub numbering: Numbering,
-
- /// The content to put into the footnote. Can also be the label of another
- /// footnote this one should point to.
- #[required]
- pub body: FootnoteBody,
-}
-
-impl FootnoteElem {
- /// Creates a new footnote that the passed content as its body.
- pub fn with_content(content: Content) -> Self {
- Self::new(FootnoteBody::Content(content))
- }
-
- /// Creates a new footnote referencing the footnote with the specified label.
- pub fn with_label(label: Label) -> Self {
- Self::new(FootnoteBody::Reference(label))
- }
-
- /// Tests if this footnote is a reference to another footnote.
- pub fn is_ref(&self) -> bool {
- matches!(self.body(), FootnoteBody::Reference(_))
- }
-
- /// Returns the content of the body of this footnote if it is not a ref.
- pub fn body_content(&self) -> Option<Content> {
- match self.body() {
- FootnoteBody::Content(content) => Some(content),
- _ => None,
- }
- }
-
- /// Returns the location of the definition of this footnote.
- pub fn declaration_location(&self, vt: &Vt) -> StrResult<Location> {
- match self.body() {
- FootnoteBody::Reference(label) => {
- let element: Prehashed<Content> = vt.introspector.query_label(&label)?;
- let footnote = element
- .to::<FootnoteElem>()
- .ok_or("referenced element should be a footnote")?;
- footnote.declaration_location(vt)
- }
- _ => Ok(self.0.location().unwrap()),
- }
- }
-}
-
-impl Synthesize for FootnoteElem {
- fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
- self.push_numbering(self.numbering(styles));
- Ok(())
- }
-}
-
-impl Show for FootnoteElem {
- #[tracing::instrument(name = "FootnoteElem::show", skip_all)]
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- Ok(vt.delayed(|vt| {
- let loc = self.declaration_location(vt).at(self.span())?;
- let numbering = self.numbering(styles);
- let counter = Counter::of(Self::func());
- let num = counter.at(vt, loc)?.display(vt, &numbering)?;
- let sup = SuperElem::new(num).pack();
- let hole = HElem::new(Abs::zero().into()).with_weak(true).pack();
- let loc = loc.variant(1);
- Ok(hole + sup.linked(Destination::Location(loc)))
- }))
- }
-}
-
-impl Count for FootnoteElem {
- fn update(&self) -> Option<CounterUpdate> {
- (!self.is_ref()).then(|| CounterUpdate::Step(NonZeroUsize::ONE))
- }
-}
-
-/// An entry in a footnote list.
-///
-/// This function is not intended to be called directly. Instead, it is used
-/// in set and show rules to customize footnote listings.
-///
-/// ## Example { #example }
-/// ```example
-/// #show footnote.entry: set text(red)
-///
-/// My footnote listing
-/// #footnote[It's down here]
-/// has red text!
-/// ```
-///
-/// Display: Footnote Entry
-/// Category: meta
-#[element(Show, Finalize)]
-pub struct FootnoteEntry {
- /// The footnote for this entry. It's location can be used to determine
- /// the footnote counter state.
- ///
- /// ```example
- /// #show footnote.entry: it => {
- /// let loc = it.note.location()
- /// numbering(
- /// "1: ",
- /// ..counter(footnote).at(loc),
- /// )
- /// it.note.body
- /// }
- ///
- /// Customized #footnote[Hello]
- /// listing #footnote[World! 🌏]
- /// ```
- #[required]
- pub note: FootnoteElem,
-
- /// The separator between the document body and the footnote listing.
- ///
- /// ```example
- /// #set footnote.entry(
- /// separator: repeat[.]
- /// )
- ///
- /// Testing a different separator.
- /// #footnote[
- /// Unconventional, but maybe
- /// not that bad?
- /// ]
- /// ```
- #[default(
- LineElem::new()
- .with_length(Ratio::new(0.3).into())
- .with_stroke(PartialStroke {
- thickness: Smart::Custom(Abs::pt(0.5).into()),
- ..Default::default()
- })
- .pack()
- )]
- pub separator: Content,
-
- /// The amount of clearance between the document body and the separator.
- ///
- /// ```example
- /// #set footnote.entry(clearance: 3em)
- ///
- /// Footnotes also need ...
- /// #footnote[
- /// ... some space to breathe.
- /// ]
- /// ```
- #[default(Em::new(1.0).into())]
- #[resolve]
- pub clearance: Length,
-
- /// The gap between footnote entries.
- ///
- /// ```example
- /// #set footnote.entry(gap: 0.8em)
- ///
- /// Footnotes:
- /// #footnote[Spaced],
- /// #footnote[Apart]
- /// ```
- #[default(Em::new(0.5).into())]
- #[resolve]
- pub gap: Length,
-
- /// The indent of each footnote entry.
- ///
- /// ```example
- /// #set footnote.entry(indent: 0em)
- ///
- /// Footnotes:
- /// #footnote[No],
- /// #footnote[Indent]
- /// ```
- #[default(Em::new(1.0).into())]
- pub indent: Length,
-}
-
-impl Show for FootnoteEntry {
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- let note = self.note();
- let number_gap = Em::new(0.05);
- let numbering = note.numbering(StyleChain::default());
- let counter = Counter::of(FootnoteElem::func());
- let loc = note.0.location().unwrap();
- let num = counter.at(vt, loc)?.display(vt, &numbering)?;
- let sup = SuperElem::new(num)
- .pack()
- .linked(Destination::Location(loc))
- .backlinked(loc.variant(1));
- Ok(Content::sequence([
- HElem::new(self.indent(styles).into()).pack(),
- sup,
- HElem::new(number_gap.into()).with_weak(true).pack(),
- note.body_content().unwrap(),
- ]))
- }
-}
-
-impl Finalize for FootnoteEntry {
- fn finalize(&self, realized: Content, _: StyleChain) -> Content {
- let text_size = Em::new(0.85);
- let leading = Em::new(0.5);
- realized
- .styled(ParElem::set_leading(leading.into()))
- .styled(TextElem::set_size(TextSize(text_size.into())))
- }
-}
-
-cast! {
- FootnoteElem,
- v: Content => v.to::<Self>().cloned().unwrap_or_else(|| Self::with_content(v.clone())),
-}
diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs
deleted file mode 100644
index d70bc0a9..00000000
--- a/library/src/meta/heading.rs
+++ /dev/null
@@ -1,239 +0,0 @@
-use typst::font::FontWeight;
-use typst::util::option_eq;
-
-use super::{Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable};
-use crate::layout::{BlockElem, HElem, VElem};
-use crate::meta::{Count, Supplement};
-use crate::prelude::*;
-use crate::text::{SpaceElem, TextElem, TextSize};
-
-/// A section heading.
-///
-/// With headings, you can structure your document into sections. Each heading
-/// has a _level,_ which starts at one and is unbounded upwards. This level
-/// indicates the logical role of the following content (section, subsection,
-/// etc.) A top-level heading indicates a top-level section of the document
-/// (not the document's title).
-///
-/// Typst can automatically number your headings for you. To enable numbering,
-/// specify how you want your headings to be numbered with a
-/// [numbering pattern or function]($func/numbering).
-///
-/// Independently from the numbering, Typst can also automatically generate an
-/// [outline]($func/outline) of all headings for you. To exclude one or more
-/// headings from this outline, you can set the `outlined` parameter to
-/// `{false}`.
-///
-/// ## Example { #example }
-/// ```example
-/// #set heading(numbering: "1.a)")
-///
-/// = Introduction
-/// In recent years, ...
-///
-/// == Preliminaries
-/// To start, ...
-/// ```
-///
-/// ## Syntax { #syntax }
-/// Headings have dedicated syntax: They can be created by starting a line with
-/// one or multiple equals signs, followed by a space. The number of equals
-/// signs determines the heading's logical nesting depth.
-///
-/// Display: Heading
-/// Category: meta
-#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable, Outlinable)]
-pub struct HeadingElem {
- /// The logical nesting depth of the heading, starting from one.
- #[default(NonZeroUsize::ONE)]
- pub level: NonZeroUsize,
-
- /// How to number the heading. Accepts a
- /// [numbering pattern or function]($func/numbering).
- ///
- /// ```example
- /// #set heading(numbering: "1.a.")
- ///
- /// = A section
- /// == A subsection
- /// === A sub-subsection
- /// ```
- pub numbering: Option<Numbering>,
-
- /// A supplement for the heading.
- ///
- /// For references to headings, this is added before the referenced number.
- ///
- /// If a function is specified, it is passed the referenced heading and
- /// should return content.
- ///
- /// ```example
- /// #set heading(numbering: "1.", supplement: [Chapter])
- ///
- /// = Introduction <intro>
- /// In @intro, we see how to turn
- /// Sections into Chapters. And
- /// in @intro[Part], it is done
- /// manually.
- /// ```
- pub supplement: Smart<Option<Supplement>>,
-
- /// Whether the heading should appear in the outline.
- ///
- /// ```example
- /// #outline()
- ///
- /// #heading[Normal]
- /// This is a normal heading.
- ///
- /// #heading(outlined: false)[Hidden]
- /// This heading does not appear
- /// in the outline.
- /// ```
- #[default(true)]
- pub outlined: bool,
-
- /// The heading's title.
- #[required]
- pub body: Content,
-}
-
-impl Synthesize for HeadingElem {
- fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
- // Resolve the supplement.
- let supplement = match self.supplement(styles) {
- Smart::Auto => TextElem::packed(self.local_name_in(styles)),
- Smart::Custom(None) => Content::empty(),
- Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?,
- };
-
- self.push_level(self.level(styles));
- self.push_numbering(self.numbering(styles));
- self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
- self.push_outlined(self.outlined(styles));
-
- Ok(())
- }
-}
-
-impl Show for HeadingElem {
- #[tracing::instrument(name = "HeadingElem::show", skip_all)]
- fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- let mut realized = self.body();
- if let Some(numbering) = self.numbering(styles) {
- realized = Counter::of(Self::func())
- .display(Some(numbering), false)
- .spanned(self.span())
- + HElem::new(Em::new(0.3).into()).with_weak(true).pack()
- + realized;
- }
- Ok(BlockElem::new().with_body(Some(realized)).pack())
- }
-}
-
-impl Finalize for HeadingElem {
- fn finalize(&self, realized: Content, styles: StyleChain) -> Content {
- let level = self.level(styles).get();
- let scale = match level {
- 1 => 1.4,
- 2 => 1.2,
- _ => 1.0,
- };
-
- let size = Em::new(scale);
- let above = Em::new(if level == 1 { 1.8 } else { 1.44 }) / scale;
- let below = Em::new(0.75) / scale;
-
- let mut styles = Styles::new();
- styles.set(TextElem::set_size(TextSize(size.into())));
- styles.set(TextElem::set_weight(FontWeight::BOLD));
- styles.set(BlockElem::set_above(VElem::block_around(above.into())));
- styles.set(BlockElem::set_below(VElem::block_around(below.into())));
- styles.set(BlockElem::set_sticky(true));
- realized.styled_with_map(styles)
- }
-}
-
-impl Count for HeadingElem {
- fn update(&self) -> Option<CounterUpdate> {
- self.numbering(StyleChain::default())
- .is_some()
- .then(|| CounterUpdate::Step(self.level(StyleChain::default())))
- }
-}
-
-cast! {
- HeadingElem,
- v: Content => v.to::<Self>().ok_or("expected heading")?.clone(),
-}
-
-impl Refable for HeadingElem {
- fn supplement(&self) -> Content {
- // After synthesis, this should always be custom content.
- match self.supplement(StyleChain::default()) {
- Smart::Custom(Some(Supplement::Content(content))) => content,
- _ => Content::empty(),
- }
- }
-
- fn counter(&self) -> Counter {
- Counter::of(Self::func())
- }
-
- fn numbering(&self) -> Option<Numbering> {
- self.numbering(StyleChain::default())
- }
-}
-
-impl Outlinable for HeadingElem {
- fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
- if !self.outlined(StyleChain::default()) {
- return Ok(None);
- }
-
- let mut content = self.body();
- if let Some(numbering) = self.numbering(StyleChain::default()) {
- let numbers = Counter::of(Self::func())
- .at(vt, self.0.location().unwrap())?
- .display(vt, &numbering)?;
- content = numbers + SpaceElem::new().pack() + content;
- };
-
- Ok(Some(content))
- }
-
- fn level(&self) -> NonZeroUsize {
- self.level(StyleChain::default())
- }
-}
-
-impl LocalName for HeadingElem {
- fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Kapitull",
- Lang::ARABIC => "الفصل",
- Lang::BOKMÅL => "Kapittel",
- Lang::CHINESE if option_eq(region, "TW") => "小節",
- Lang::CHINESE => "小节",
- Lang::CZECH => "Kapitola",
- Lang::DANISH => "Afsnit",
- Lang::DUTCH => "Hoofdstuk",
- Lang::FILIPINO => "Seksyon",
- Lang::FRENCH => "Chapitre",
- Lang::GERMAN => "Abschnitt",
- Lang::ITALIAN => "Sezione",
- Lang::NYNORSK => "Kapittel",
- Lang::POLISH => "Sekcja",
- Lang::PORTUGUESE if option_eq(region, "PT") => "Secção",
- Lang::PORTUGUESE => "Seção",
- Lang::RUSSIAN => "Раздел",
- Lang::SLOVENIAN => "Poglavje",
- Lang::SPANISH => "Sección",
- Lang::SWEDISH => "Kapitel",
- Lang::TURKISH => "Bölüm",
- Lang::UKRAINIAN => "Розділ",
- Lang::VIETNAMESE => "Phần", // TODO: This may be wrong.
- Lang::ENGLISH | _ => "Section",
- }
- }
-}
diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs
deleted file mode 100644
index 2a53b84f..00000000
--- a/library/src/meta/link.rs
+++ /dev/null
@@ -1,137 +0,0 @@
-use crate::prelude::*;
-use crate::text::{Hyphenate, TextElem};
-
-/// Links to a URL or a location in the document.
-///
-/// By default, links are not styled any different from normal text. However,
-/// you can easily apply a style of your choice with a show rule.
-///
-/// ## Example { #example }
-/// ```example
-/// #show link: underline
-///
-/// https://example.com \
-///
-/// #link("https://example.com") \
-/// #link("https://example.com")[
-/// See example.com
-/// ]
-/// ```
-///
-/// ## Syntax { #syntax }
-/// This function also has dedicated syntax: Text that starts with `http://` or
-/// `https://` is automatically turned into a link.
-///
-/// Display: Link
-/// Category: meta
-#[element(Show)]
-pub struct LinkElem {
- /// The destination the link points to.
- ///
- /// - To link to web pages, `dest` should be a valid URL string. If the URL
- /// is in the `mailto:` or `tel:` scheme and the `body` parameter is
- /// omitted, the email address or phone number will be the link's body,
- /// without the scheme.
- ///
- /// - To link to another part of the document, `dest` can take one of three
- /// forms:
- /// - A [label]($func/label) attached to an element. If you also want
- /// automatic text for the link based on the element, consider using
- /// a [reference]($func/ref) instead.
- ///
- /// - A [location]($func/locate) resulting from a [`locate`]($func/locate)
- /// call or [`query`]($func/query).
- ///
- /// - A dictionary with a `page` key of type [integer]($type/integer) and
- /// `x` and `y` coordinates of type [length]($type/length). Pages are
- /// counted from one, and the coordinates are relative to the page's top
- /// left corner.
- ///
- /// ```example
- /// = Introduction <intro>
- /// #link("mailto:hello@typst.app") \
- /// #link(<intro>)[Go to intro] \
- /// #link((page: 1, x: 0pt, y: 0pt))[
- /// Go to top
- /// ]
- /// ```
- #[required]
- #[parse(
- let dest = args.expect::<LinkTarget>("destination")?;
- dest.clone()
- )]
- pub dest: LinkTarget,
-
- /// The content that should become a link.
- ///
- /// If `dest` is an URL string, the parameter can be omitted. In this case,
- /// the URL will be shown as the link.
- #[required]
- #[parse(match &dest {
- LinkTarget::Dest(Destination::Url(url)) => match args.eat()? {
- Some(body) => body,
- None => body_from_url(url),
- },
- _ => args.expect("body")?,
- })]
- pub body: Content,
-}
-
-impl LinkElem {
- /// Create a link element from a URL with its bare text.
- pub fn from_url(url: EcoString) -> Self {
- let body = body_from_url(&url);
- Self::new(LinkTarget::Dest(Destination::Url(url)), body)
- }
-}
-
-impl Show for LinkElem {
- #[tracing::instrument(name = "LinkElem::show", skip(self, vt))]
- fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- let body = self.body();
- let linked = match self.dest() {
- LinkTarget::Dest(dest) => body.linked(dest),
- LinkTarget::Label(label) => vt
- .delayed(|vt| {
- let elem = vt.introspector.query_label(&label).at(self.span())?;
- let dest = Destination::Location(elem.location().unwrap());
- Ok(Some(body.clone().linked(dest)))
- })
- .unwrap_or(body),
- };
-
- Ok(linked.styled(TextElem::set_hyphenate(Hyphenate(Smart::Custom(false)))))
- }
-}
-
-fn body_from_url(url: &EcoString) -> Content {
- let mut text = url.as_str();
- for prefix in ["mailto:", "tel:"] {
- text = text.trim_start_matches(prefix);
- }
- let shorter = text.len() < url.len();
- TextElem::packed(if shorter { text.into() } else { url.clone() })
-}
-
-/// A target where a link can go.
-#[derive(Debug, Clone)]
-pub enum LinkTarget {
- Dest(Destination),
- Label(Label),
-}
-
-cast! {
- LinkTarget,
- self => match self {
- Self::Dest(v) => v.into_value(),
- Self::Label(v) => v.into_value(),
- },
- v: Destination => Self::Dest(v),
- v: Label => Self::Label(v),
-}
-
-impl From<Destination> for LinkTarget {
- fn from(dest: Destination) -> Self {
- Self::Dest(dest)
- }
-}
diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs
deleted file mode 100644
index dcac6379..00000000
--- a/library/src/meta/mod.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-//! Interaction between document parts.
-
-mod bibliography;
-mod context;
-mod counter;
-mod document;
-mod figure;
-mod footnote;
-mod heading;
-mod link;
-mod numbering;
-mod outline;
-mod query;
-mod reference;
-mod state;
-
-pub use self::bibliography::*;
-pub use self::context::*;
-pub use self::counter::*;
-pub use self::document::*;
-pub use self::figure::*;
-pub use self::footnote::*;
-pub use self::heading::*;
-pub use self::link::*;
-pub use self::numbering::*;
-pub use self::outline::*;
-pub use self::query::*;
-pub use self::reference::*;
-pub use self::state::*;
-
-use crate::prelude::*;
-use crate::text::TextElem;
-
-/// Hook up all meta definitions.
-pub(super) fn define(global: &mut Scope) {
- global.define("document", DocumentElem::func());
- global.define("ref", RefElem::func());
- global.define("link", LinkElem::func());
- global.define("outline", OutlineElem::func());
- global.define("heading", HeadingElem::func());
- global.define("figure", FigureElem::func());
- global.define("footnote", FootnoteElem::func());
- global.define("cite", CiteElem::func());
- global.define("bibliography", BibliographyElem::func());
- global.define("locate", locate_func());
- global.define("style", style_func());
- global.define("layout", layout_func());
- global.define("counter", counter_func());
- global.define("numbering", numbering_func());
- global.define("state", state_func());
- global.define("query", query_func());
- global.define("selector", selector_func());
-}
-
-/// The named with which an element is referenced.
-pub trait LocalName {
- /// Get the name in the given language and (optionally) region.
- fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str;
-
- /// Resolve the local name with a style chain.
- fn local_name_in(&self, styles: StyleChain) -> &'static str {
- self.local_name(TextElem::lang_in(styles), TextElem::region_in(styles))
- }
-}
diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs
deleted file mode 100644
index 8698f7b9..00000000
--- a/library/src/meta/numbering.rs
+++ /dev/null
@@ -1,525 +0,0 @@
-use std::str::FromStr;
-
-use chinese_number::{ChineseCase, ChineseCountMethod, ChineseVariant, NumberToChinese};
-use ecow::EcoVec;
-
-use crate::prelude::*;
-use crate::text::Case;
-
-/// Applies a numbering to a sequence of numbers.
-///
-/// A numbering defines how a sequence of numbers should be displayed as
-/// content. It is defined either through a pattern string or an arbitrary
-/// function.
-///
-/// A numbering pattern consists of counting symbols, for which the actual
-/// number is substituted, their prefixes, and one suffix. The prefixes and the
-/// suffix are repeated as-is.
-///
-/// ## Example { #example }
-/// ```example
-/// #numbering("1.1)", 1, 2, 3) \
-/// #numbering("1.a.i", 1, 2) \
-/// #numbering("I – 1", 12, 2) \
-/// #numbering(
-/// (..nums) => nums
-/// .pos()
-/// .map(str)
-/// .join(".") + ")",
-/// 1, 2, 3,
-/// )
-/// ```
-///
-/// Display: Numbering
-/// Category: meta
-#[func]
-pub fn numbering(
- /// Defines how the numbering works.
- ///
- /// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `い`, `イ`, `א`, `가`,
- /// `ㄱ`, and `*`. They are replaced by the number in the sequence, in the
- /// given case.
- ///
- /// The `*` character means that symbols should be used to count, in the
- /// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six
- /// items, the number is represented using multiple symbols.
- ///
- /// **Suffixes** are all characters after the last counting symbol. They are
- /// repeated as-is at the end of any rendered number.
- ///
- /// **Prefixes** are all characters that are neither counting symbols nor
- /// suffixes. They are repeated as-is at in front of their rendered
- /// equivalent of their counting symbol.
- ///
- /// This parameter can also be an arbitrary function that gets each number
- /// as an individual argument. When given a function, the `numbering`
- /// function just forwards the arguments to that function. While this is not
- /// particularly useful in itself, it means that you can just give arbitrary
- /// numberings to the `numbering` function without caring whether they are
- /// defined as a pattern or function.
- numbering: Numbering,
- /// The numbers to apply the numbering to. Must be positive.
- ///
- /// If `numbering` is a pattern and more numbers than counting symbols are
- /// given, the last counting symbol with its prefix is repeated.
- #[variadic]
- numbers: Vec<usize>,
- /// The virtual machine.
- vm: &mut Vm,
-) -> SourceResult<Value> {
- numbering.apply_vm(vm, &numbers)
-}
-
-/// How to number a sequence of things.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Numbering {
- /// A pattern with prefix, numbering, lower / upper case and suffix.
- Pattern(NumberingPattern),
- /// A closure mapping from an item's number to content.
- Func(Func),
-}
-
-impl Numbering {
- /// Apply the pattern to the given numbers.
- pub fn apply_vm(&self, vm: &mut Vm, numbers: &[usize]) -> SourceResult<Value> {
- Ok(match self {
- Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()),
- Self::Func(func) => {
- let args = Args::new(func.span(), numbers.iter().copied());
- func.call_vm(vm, args)?
- }
- })
- }
-
- /// Apply the pattern to the given numbers.
- pub fn apply_vt(&self, vt: &mut Vt, numbers: &[usize]) -> SourceResult<Value> {
- Ok(match self {
- Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()),
- Self::Func(func) => func.call_vt(vt, numbers.iter().copied())?,
- })
- }
-
- /// Trim the prefix suffix if this is a pattern.
- pub fn trimmed(mut self) -> Self {
- if let Self::Pattern(pattern) = &mut self {
- pattern.trimmed = true;
- }
- self
- }
-}
-
-impl From<NumberingPattern> for Numbering {
- fn from(pattern: NumberingPattern) -> Self {
- Self::Pattern(pattern)
- }
-}
-
-cast! {
- Numbering,
- self => match self {
- Self::Pattern(pattern) => pattern.into_value(),
- Self::Func(func) => func.into_value(),
- },
- v: NumberingPattern => Self::Pattern(v),
- v: Func => Self::Func(v),
-}
-
-/// How to turn a number into text.
-///
-/// A pattern consists of a prefix, followed by one of `1`, `a`, `A`, `i`,
-/// `I`, `い`, `イ`, `א`, `가`, `ㄱ`, or `*`, and then a suffix.
-///
-/// Examples of valid patterns:
-/// - `1)`
-/// - `a.`
-/// - `(I)`
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct NumberingPattern {
- pieces: EcoVec<(EcoString, NumberingKind, Case)>,
- suffix: EcoString,
- trimmed: bool,
-}
-
-impl NumberingPattern {
- /// Apply the pattern to the given number.
- pub fn apply(&self, numbers: &[usize]) -> EcoString {
- let mut fmt = EcoString::new();
- let mut numbers = numbers.iter();
-
- for (i, ((prefix, kind, case), &n)) in
- self.pieces.iter().zip(&mut numbers).enumerate()
- {
- if i > 0 || !self.trimmed {
- fmt.push_str(prefix);
- }
- fmt.push_str(&kind.apply(n, *case));
- }
-
- for ((prefix, kind, case), &n) in
- self.pieces.last().into_iter().cycle().zip(numbers)
- {
- if prefix.is_empty() {
- fmt.push_str(&self.suffix);
- } else {
- fmt.push_str(prefix);
- }
- fmt.push_str(&kind.apply(n, *case));
- }
-
- if !self.trimmed {
- fmt.push_str(&self.suffix);
- }
-
- fmt
- }
-
- /// Apply only the k-th segment of the pattern to a number.
- pub fn apply_kth(&self, k: usize, number: usize) -> EcoString {
- let mut fmt = EcoString::new();
- if let Some((prefix, _, _)) = self.pieces.first() {
- fmt.push_str(prefix);
- }
- if let Some((_, kind, case)) = self
- .pieces
- .iter()
- .chain(self.pieces.last().into_iter().cycle())
- .nth(k)
- {
- fmt.push_str(&kind.apply(number, *case));
- }
- fmt.push_str(&self.suffix);
- fmt
- }
-
- /// How many counting symbols this pattern has.
- pub fn pieces(&self) -> usize {
- self.pieces.len()
- }
-}
-
-impl FromStr for NumberingPattern {
- type Err = &'static str;
-
- fn from_str(pattern: &str) -> Result<Self, Self::Err> {
- let mut pieces = EcoVec::new();
- let mut handled = 0;
-
- for (i, c) in pattern.char_indices() {
- let Some(kind) = NumberingKind::from_char(c.to_ascii_lowercase()) else {
- continue;
- };
-
- let prefix = pattern[handled..i].into();
- let case =
- if c.is_uppercase() || c == '壹' { Case::Upper } else { Case::Lower };
- pieces.push((prefix, kind, case));
- handled = c.len_utf8() + i;
- }
-
- let suffix = pattern[handled..].into();
- if pieces.is_empty() {
- return Err("invalid numbering pattern");
- }
-
- Ok(Self { pieces, suffix, trimmed: false })
- }
-}
-
-cast! {
- NumberingPattern,
- self => {
- let mut pat = EcoString::new();
- for (prefix, kind, case) in &self.pieces {
- pat.push_str(prefix);
- let mut c = kind.to_char();
- if *case == Case::Upper {
- c = c.to_ascii_uppercase();
- }
- pat.push(c);
- }
- pat.push_str(&self.suffix);
- pat.into_value()
- },
- v: Str => v.parse()?,
-}
-
-/// Different kinds of numberings.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-enum NumberingKind {
- Arabic,
- Letter,
- Roman,
- Symbol,
- Hebrew,
- SimplifiedChinese,
- // TODO: Pick the numbering pattern based on languages choice.
- // As the `1st` numbering character of Chinese (Simplified) and
- // Chinese (Traditional) is same, we are unable to determine
- // if the context is Simplified or Traditional by only this
- // character.
- #[allow(unused)]
- TraditionalChinese,
- HiraganaIroha,
- KatakanaIroha,
- KoreanJamo,
- KoreanSyllable,
-}
-
-impl NumberingKind {
- /// Create a numbering kind from a lowercase character.
- pub fn from_char(c: char) -> Option<Self> {
- Some(match c {
- '1' => NumberingKind::Arabic,
- 'a' => NumberingKind::Letter,
- 'i' => NumberingKind::Roman,
- '*' => NumberingKind::Symbol,
- 'א' => NumberingKind::Hebrew,
- '一' | '壹' => NumberingKind::SimplifiedChinese,
- 'い' => NumberingKind::HiraganaIroha,
- 'イ' => NumberingKind::KatakanaIroha,
- 'ㄱ' => NumberingKind::KoreanJamo,
- '가' => NumberingKind::KoreanSyllable,
- _ => return None,
- })
- }
-
- /// The lowercase character for this numbering kind.
- pub fn to_char(self) -> char {
- match self {
- Self::Arabic => '1',
- Self::Letter => 'a',
- Self::Roman => 'i',
- Self::Symbol => '*',
- Self::Hebrew => 'א',
- Self::SimplifiedChinese => '一',
- Self::TraditionalChinese => '一',
- Self::HiraganaIroha => 'い',
- Self::KatakanaIroha => 'イ',
- Self::KoreanJamo => 'ㄱ',
- Self::KoreanSyllable => '가',
- }
- }
-
- /// Apply the numbering to the given number.
- pub fn apply(self, mut n: usize, case: Case) -> EcoString {
- match self {
- Self::Arabic => {
- eco_format!("{n}")
- }
- Self::Letter => zeroless::<26>(
- |x| match case {
- Case::Lower => char::from(b'a' + x as u8),
- Case::Upper => char::from(b'A' + x as u8),
- },
- n,
- ),
- Self::HiraganaIroha => zeroless::<47>(
- |x| {
- [
- 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る',
- 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら',
- 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ',
- 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ',
- 'も', 'せ', 'す',
- ][x]
- },
- n,
- ),
- Self::KatakanaIroha => zeroless::<47>(
- |x| {
- [
- 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル',
- 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ',
- 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ',
- 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ',
- 'モ', 'セ', 'ス',
- ][x]
- },
- n,
- ),
- Self::Roman => {
- if n == 0 {
- return 'N'.into();
- }
-
- // Adapted from Yann Villessuzanne's roman.rs under the
- // Unlicense, at https://github.com/linfir/roman.rs/
- let mut fmt = EcoString::new();
- for &(name, value) in &[
- ("M̅", 1000000),
- ("D̅", 500000),
- ("C̅", 100000),
- ("L̅", 50000),
- ("X̅", 10000),
- ("V̅", 5000),
- ("I̅V̅", 4000),
- ("M", 1000),
- ("CM", 900),
- ("D", 500),
- ("CD", 400),
- ("C", 100),
- ("XC", 90),
- ("L", 50),
- ("XL", 40),
- ("X", 10),
- ("IX", 9),
- ("V", 5),
- ("IV", 4),
- ("I", 1),
- ] {
- while n >= value {
- n -= value;
- for c in name.chars() {
- match case {
- Case::Lower => fmt.extend(c.to_lowercase()),
- Case::Upper => fmt.push(c),
- }
- }
- }
- }
-
- fmt
- }
- Self::Symbol => {
- if n == 0 {
- return '-'.into();
- }
-
- const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖'];
- let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()];
- let amount = ((n - 1) / SYMBOLS.len()) + 1;
- std::iter::repeat(symbol).take(amount).collect()
- }
- Self::Hebrew => {
- if n == 0 {
- return '-'.into();
- }
-
- let mut fmt = EcoString::new();
- 'outer: for &(name, value) in &[
- ('ת', 400),
- ('ש', 300),
- ('ר', 200),
- ('ק', 100),
- ('צ', 90),
- ('פ', 80),
- ('ע', 70),
- ('ס', 60),
- ('נ', 50),
- ('מ', 40),
- ('ל', 30),
- ('כ', 20),
- ('י', 10),
- ('ט', 9),
- ('ח', 8),
- ('ז', 7),
- ('ו', 6),
- ('ה', 5),
- ('ד', 4),
- ('ג', 3),
- ('ב', 2),
- ('א', 1),
- ] {
- while n >= value {
- match n {
- 15 => fmt.push_str("ט״ו"),
- 16 => fmt.push_str("ט״ז"),
- _ => {
- let append_geresh = n == value && fmt.is_empty();
- if n == value && !fmt.is_empty() {
- fmt.push('״');
- }
- fmt.push(name);
- if append_geresh {
- fmt.push('׳');
- }
-
- n -= value;
- continue;
- }
- }
- break 'outer;
- }
- }
- fmt
- }
- l @ (Self::SimplifiedChinese | Self::TraditionalChinese) => {
- let chinese_case = match case {
- Case::Lower => ChineseCase::Lower,
- Case::Upper => ChineseCase::Upper,
- };
-
- match (n as u8).to_chinese(
- match l {
- Self::SimplifiedChinese => ChineseVariant::Simple,
- Self::TraditionalChinese => ChineseVariant::Traditional,
- _ => unreachable!(),
- },
- chinese_case,
- ChineseCountMethod::TenThousand,
- ) {
- Ok(num_str) => EcoString::from(num_str),
- Err(_) => '-'.into(),
- }
- }
- Self::KoreanJamo => zeroless::<14>(
- |x| {
- [
- 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ',
- 'ㅌ', 'ㅍ', 'ㅎ',
- ][x]
- },
- n,
- ),
- Self::KoreanSyllable => zeroless::<14>(
- |x| {
- [
- '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카',
- '타', '파', '하',
- ][x]
- },
- n,
- ),
- }
- }
-}
-
-/// Stringify a number using a base-N counting system with no zero digit.
-///
-/// This is best explained by example. Suppose our digits are 'A', 'B', and 'C'.
-/// we would get the following:
-///
-/// ```text
-/// 1 => "A"
-/// 2 => "B"
-/// 3 => "C"
-/// 4 => "AA"
-/// 5 => "AB"
-/// 6 => "AC"
-/// 7 => "BA"
-/// 8 => "BB"
-/// 9 => "BC"
-/// 10 => "CA"
-/// 11 => "CB"
-/// 12 => "CC"
-/// 13 => "AAA"
-/// etc.
-/// ```
-///
-/// You might be familiar with this scheme from the way spreadsheet software
-/// tends to label its columns.
-fn zeroless<const N_DIGITS: usize>(
- mk_digit: impl Fn(usize) -> char,
- mut n: usize,
-) -> EcoString {
- if n == 0 {
- return '-'.into();
- }
- let mut cs = vec![];
- while n > 0 {
- n -= 1;
- cs.push(mk_digit(n % N_DIGITS));
- n /= N_DIGITS;
- }
- cs.into_iter().rev().collect()
-}
diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs
deleted file mode 100644
index ba858a69..00000000
--- a/library/src/meta/outline.rs
+++ /dev/null
@@ -1,528 +0,0 @@
-use std::str::FromStr;
-
-use typst::util::option_eq;
-
-use super::{
- Counter, CounterKey, HeadingElem, LocalName, Numbering, NumberingPattern, Refable,
-};
-use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem, Spacing};
-use crate::prelude::*;
-use crate::text::{LinebreakElem, SpaceElem, TextElem};
-
-/// A table of contents, figures, or other elements.
-///
-/// This function generates a list of all occurrences of an element in the
-/// document, up to a given depth. The element's numbering and page number will
-/// be displayed in the outline alongside its title or caption. By default this
-/// generates a table of contents.
-///
-/// ## Example { #example }
-/// ```example
-/// #outline()
-///
-/// = Introduction
-/// #lorem(5)
-///
-/// = Prior work
-/// #lorem(10)
-/// ```
-///
-/// ## Alternative outlines { #alternative-outlines }
-/// By setting the `target` parameter, the outline can be used to generate a
-/// list of other kinds of elements than headings. In the example below, we list
-/// all figures containing images by setting `target` to `{figure.where(kind:
-/// image)}`. We could have also set it to just `figure`, but then the list
-/// would also include figures containing tables or other material. For more
-/// details on the `where` selector, [see here]($type/content.where).
-///
-/// ```example
-/// #outline(
-/// title: [List of Figures],
-/// target: figure.where(kind: image),
-/// )
-///
-/// #figure(
-/// image("tiger.jpg"),
-/// caption: [A nice figure!],
-/// )
-/// ```
-///
-/// ## Styling the outline { #styling-the-outline }
-/// The outline element has several options for customization, such as its
-/// `title` and `indent` parameters. If desired, however, it is possible to
-/// have more control over the outline's look and style through the
-/// [`outline.entry`]($func/outline.entry) element.
-///
-/// Display: Outline
-/// Category: meta
-/// Keywords: Table of Contents
-#[element(Show, Finalize, LocalName)]
-#[scope(
- scope.define("entry", OutlineEntry::func());
- scope
-)]
-pub struct OutlineElem {
- /// The title of the outline.
- ///
- /// - When set to `{auto}`, an appropriate title for the
- /// [text language]($func/text.lang) will be used. This is the default.
- /// - When set to `{none}`, the outline will not have a title.
- /// - A custom title can be set by passing content.
- ///
- /// The outline's heading will not be numbered by default, but you can
- /// force it to be with a show-set rule:
- /// `{show outline: set heading(numbering: "1.")}`
- /// ```
- #[default(Some(Smart::Auto))]
- pub title: Option<Smart<Content>>,
-
- /// The type of element to include in the outline.
- ///
- /// To list figures containing a specific kind of element, like a table, you
- /// can write `{figure.where(kind: table)}`.
- ///
- /// ```example
- /// #outline(
- /// title: [List of Tables],
- /// target: figure.where(kind: table),
- /// )
- ///
- /// #figure(
- /// table(
- /// columns: 4,
- /// [t], [1], [2], [3],
- /// [y], [0.3], [0.7], [0.5],
- /// ),
- /// caption: [Experiment results],
- /// )
- /// ```
- #[default(LocatableSelector(Selector::Elem(
- HeadingElem::func(),
- Some(dict! { "outlined" => true })
- )))]
- pub target: LocatableSelector,
-
- /// The maximum level up to which elements are included in the outline. When
- /// this argument is `{none}`, all elements are included.
- ///
- /// ```example
- /// #set heading(numbering: "1.")
- /// #outline(depth: 2)
- ///
- /// = Yes
- /// Top-level section.
- ///
- /// == Still
- /// Subsection.
- ///
- /// === Nope
- /// Not included.
- /// ```
- pub depth: Option<NonZeroUsize>,
-
- /// How to indent the outline's entries.
- ///
- /// - `{none}`: No indent
- /// - `{auto}`: Indents the numbering of the nested entry with the title of
- /// its parent entry. This only has an effect if the entries are numbered
- /// (e.g., via [heading numbering]($func/heading.numbering)).
- /// - [Relative length]($type/relative-length): Indents the item by this length
- /// multiplied by its nesting level. Specifying `{2em}`, for instance,
- /// would indent top-level headings (not nested) by `{0em}`, second level
- /// headings by `{2em}` (nested once), third-level headings by `{4em}`
- /// (nested twice) and so on.
- /// - [Function]($type/function): You can completely customize this setting
- /// with a function. That function receives the nesting level as a
- /// parameter (starting at 0 for top-level headings/elements) and can
- /// return a relative length or content making up the indent. For example,
- /// `{n => n * 2em}` would be equivalent to just specifying `{2em}`,
- /// while `{n => [→ ] * n}` would indent with one arrow per nesting
- /// level.
- ///
- /// *Migration hints:* Specifying `{true}` (equivalent to `{auto}`) or
- /// `{false}` (equivalent to `{none}`) for this option is deprecated and
- /// will be removed in a future release.
- ///
- /// ```example
- /// #set heading(numbering: "1.a.")
- ///
- /// #outline(
- /// title: [Contents (Automatic)],
- /// indent: auto,
- /// )
- ///
- /// #outline(
- /// title: [Contents (Length)],
- /// indent: 2em,
- /// )
- ///
- /// #outline(
- /// title: [Contents (Function)],
- /// indent: n => [→ ] * n,
- /// )
- ///
- /// = About ACME Corp.
- /// == History
- /// === Origins
- /// #lorem(10)
- ///
- /// == Products
- /// #lorem(10)
- /// ```
- #[default(None)]
- pub indent: Option<Smart<OutlineIndent>>,
-
- /// Content to fill the space between the title and the page number. Can be
- /// set to `none` to disable filling.
- ///
- /// ```example
- /// #outline(fill: line(length: 100%))
- ///
- /// = A New Beginning
- /// ```
- #[default(Some(RepeatElem::new(TextElem::packed(".")).pack()))]
- pub fill: Option<Content>,
-}
-
-impl Show for OutlineElem {
- #[tracing::instrument(name = "OutlineElem::show", skip_all)]
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- let mut seq = vec![ParbreakElem::new().pack()];
- // Build the outline title.
- if let Some(title) = self.title(styles) {
- let title =
- title.unwrap_or_else(|| {
- TextElem::packed(self.local_name(
- TextElem::lang_in(styles),
- TextElem::region_in(styles),
- ))
- .spanned(self.span())
- });
-
- seq.push(HeadingElem::new(title).with_level(NonZeroUsize::ONE).pack());
- }
-
- let indent = self.indent(styles);
- let depth = self.depth(styles).unwrap_or(NonZeroUsize::new(usize::MAX).unwrap());
-
- let mut ancestors: Vec<&Content> = vec![];
- let elems = vt.introspector.query(&self.target(styles).0);
-
- for elem in &elems {
- let Some(entry) = OutlineEntry::from_outlinable(
- vt,
- self.span(),
- elem.clone().into_inner(),
- self.fill(styles),
- )? else {
- continue;
- };
-
- let level = entry.level();
- if depth < level {
- continue;
- }
-
- // Deals with the ancestors of the current element.
- // This is only applicable for elements with a hierarchy/level.
- while ancestors
- .last()
- .and_then(|ancestor| ancestor.with::<dyn Outlinable>())
- .map_or(false, |last| last.level() >= level)
- {
- ancestors.pop();
- }
-
- OutlineIndent::apply(&indent, vt, &ancestors, &mut seq, self.span())?;
-
- // Add the overridable outline entry, followed by a line break.
- seq.push(entry.pack());
- seq.push(LinebreakElem::new().pack());
-
- ancestors.push(elem);
- }
-
- seq.push(ParbreakElem::new().pack());
-
- Ok(Content::sequence(seq))
- }
-}
-
-impl Finalize for OutlineElem {
- fn finalize(&self, realized: Content, _: StyleChain) -> Content {
- realized
- .styled(HeadingElem::set_outlined(false))
- .styled(HeadingElem::set_numbering(None))
- }
-}
-
-impl LocalName for OutlineElem {
- fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
- match lang {
- Lang::ALBANIAN => "Përmbajtja",
- Lang::ARABIC => "المحتويات",
- Lang::BOKMÅL => "Innhold",
- Lang::CHINESE if option_eq(region, "TW") => "目錄",
- Lang::CHINESE => "目录",
- Lang::CZECH => "Obsah",
- Lang::DANISH => "Indhold",
- Lang::DUTCH => "Inhoudsopgave",
- Lang::FILIPINO => "Talaan ng mga Nilalaman",
- Lang::FRENCH => "Table des matières",
- Lang::GERMAN => "Inhaltsverzeichnis",
- Lang::ITALIAN => "Indice",
- Lang::NYNORSK => "Innhald",
- Lang::POLISH => "Spis treści",
- Lang::PORTUGUESE if option_eq(region, "PT") => "Índice",
- Lang::PORTUGUESE => "Sumário",
- Lang::RUSSIAN => "Содержание",
- Lang::SLOVENIAN => "Kazalo",
- Lang::SPANISH => "Índice",
- Lang::SWEDISH => "Innehåll",
- Lang::TURKISH => "İçindekiler",
- Lang::UKRAINIAN => "Зміст",
- Lang::VIETNAMESE => "Mục lục",
- Lang::ENGLISH | _ => "Contents",
- }
- }
-}
-
-/// Marks an element as being able to be outlined. This is used to implement the
-/// `#outline()` element.
-pub trait Outlinable: Refable {
- /// Produce an outline item for this element.
- fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>>;
-
- /// Returns the nesting level of this element.
- fn level(&self) -> NonZeroUsize {
- NonZeroUsize::ONE
- }
-}
-
-#[derive(Debug, Clone)]
-pub enum OutlineIndent {
- Bool(bool),
- Rel(Rel<Length>),
- Func(Func),
-}
-
-impl OutlineIndent {
- fn apply(
- indent: &Option<Smart<Self>>,
- vt: &mut Vt,
- ancestors: &Vec<&Content>,
- seq: &mut Vec<Content>,
- span: Span,
- ) -> SourceResult<()> {
- match indent {
- // 'none' | 'false' => no indenting
- None | Some(Smart::Custom(OutlineIndent::Bool(false))) => {}
-
- // 'auto' | 'true' => use numbering alignment for indenting
- Some(Smart::Auto | Smart::Custom(OutlineIndent::Bool(true))) => {
- // Add hidden ancestors numberings to realize the indent.
- let mut hidden = Content::empty();
- for ancestor in ancestors {
- let ancestor_outlinable = ancestor.with::<dyn Outlinable>().unwrap();
-
- if let Some(numbering) = ancestor_outlinable.numbering() {
- let numbers = ancestor_outlinable
- .counter()
- .at(vt, ancestor.location().unwrap())?
- .display(vt, &numbering)?;
-
- hidden += numbers + SpaceElem::new().pack();
- };
- }
-
- if !ancestors.is_empty() {
- seq.push(HideElem::new(hidden).pack());
- seq.push(SpaceElem::new().pack());
- }
- }
-
- // Length => indent with some fixed spacing per level
- Some(Smart::Custom(OutlineIndent::Rel(length))) => {
- seq.push(
- HElem::new(Spacing::Rel(*length)).pack().repeat(ancestors.len()),
- );
- }
-
- // Function => call function with the current depth and take
- // the returned content
- Some(Smart::Custom(OutlineIndent::Func(func))) => {
- let depth = ancestors.len();
- let LengthOrContent(content) =
- func.call_vt(vt, [depth])?.cast().at(span)?;
- if !content.is_empty() {
- seq.push(content);
- }
- }
- };
-
- Ok(())
- }
-}
-
-cast! {
- OutlineIndent,
- self => match self {
- Self::Bool(v) => v.into_value(),
- Self::Rel(v) => v.into_value(),
- Self::Func(v) => v.into_value()
- },
- v: bool => OutlineIndent::Bool(v),
- v: Rel<Length> => OutlineIndent::Rel(v),
- v: Func => OutlineIndent::Func(v),
-}
-
-struct LengthOrContent(Content);
-
-cast! {
- LengthOrContent,
- v: Rel<Length> => Self(HElem::new(Spacing::Rel(v)).pack()),
- v: Content => Self(v),
-}
-
-/// Represents each entry line in an outline, including the reference to the
-/// outlined element, its page number, and the filler content between both.
-///
-/// This element is intended for use with show rules to control the appearance
-/// of outlines.
-///
-/// ## Example { #example }
-/// The example below shows how to style entries for top-level sections to make
-/// them stand out.
-///
-/// ```example
-/// #set heading(numbering: "1.")
-///
-/// #show outline.entry.where(
-/// level: 1
-/// ): it => {
-/// v(12pt, weak: true)
-/// strong(it)
-/// }
-///
-/// #outline(indent: auto)
-///
-/// = Introduction
-/// = Background
-/// == History
-/// == State of the Art
-/// = Analysis
-/// == Setup
-/// ```
-///
-/// To completely customize an entry's line, you can also build it from scratch
-/// by accessing the `level`, `element`, `body`, `fill` and `page` fields on the entry.
-///
-/// Display: Outline Entry
-/// Category: meta
-#[element(Show)]
-pub struct OutlineEntry {
- /// The nesting level of this outline entry. Starts at `{1}` for top-level
- /// entries.
- #[required]
- pub level: NonZeroUsize,
-
- /// The element this entry refers to. Its location will be available
- /// through the [`location`]($type/content.location) method on content
- /// and can be [linked]($func/link) to.
- #[required]
- pub element: Content,
-
- /// The content which is displayed in place of the referred element at its
- /// entry in the outline. For a heading, this would be its number followed
- /// by the heading's title, for example.
- #[required]
- pub body: Content,
-
- /// The content used to fill the space between the element's outline and
- /// its page number, as defined by the outline element this entry is
- /// located in. When `{none}`, empty space is inserted in that gap instead.
- ///
- /// Note that, when using show rules to override outline entries, it is
- /// recommended to wrap the filling content in a [`box`]($func/box) with
- /// fractional width. For example, `{box(width: 1fr, repeat[-])}` would show
- /// precisely as many `-` characters as necessary to fill a particular gap.
- #[required]
- pub fill: Option<Content>,
-
- /// The page number of the element this entry links to, formatted with the
- /// numbering set for the referenced page.
- #[required]
- pub page: Content,
-}
-
-impl OutlineEntry {
- /// Generates an OutlineEntry from the given element, if possible (errors if
- /// the element does not implement `Outlinable`). If the element should not
- /// be outlined (e.g. heading with 'outlined: false'), does not generate an
- /// entry instance (returns `Ok(None)`).
- fn from_outlinable(
- vt: &mut Vt,
- span: Span,
- elem: Content,
- fill: Option<Content>,
- ) -> SourceResult<Option<Self>> {
- let Some(outlinable) = elem.with::<dyn Outlinable>() else {
- bail!(span, "cannot outline {}", elem.func().name());
- };
-
- let Some(body) = outlinable.outline(vt)? else {
- return Ok(None);
- };
-
- let location = elem.location().unwrap();
- let page_numbering = vt
- .introspector
- .page_numbering(location)
- .cast::<Option<Numbering>>()
- .unwrap()
- .unwrap_or_else(|| {
- Numbering::Pattern(NumberingPattern::from_str("1").unwrap())
- });
-
- let page = Counter::new(CounterKey::Page)
- .at(vt, location)?
- .display(vt, &page_numbering)?;
-
- Ok(Some(Self::new(outlinable.level(), elem, body, fill, page)))
- }
-}
-
-impl Show for OutlineEntry {
- fn show(&self, _vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- let mut seq = vec![];
- let elem = self.element();
-
- // In case a user constructs an outline entry with an arbitrary element.
- let Some(location) = elem.location() else {
- bail!(self.span(), "cannot outline {}", elem.func().name())
- };
-
- // The body text remains overridable.
- seq.push(self.body().linked(Destination::Location(location)));
-
- // Add filler symbols between the section name and page number.
- if let Some(filler) = self.fill() {
- seq.push(SpaceElem::new().pack());
- seq.push(
- BoxElem::new()
- .with_body(Some(filler))
- .with_width(Fr::one().into())
- .pack(),
- );
- seq.push(SpaceElem::new().pack());
- } else {
- seq.push(HElem::new(Fr::one().into()).pack());
- }
-
- // Add the page number.
- let page = self.page().linked(Destination::Location(location));
- seq.push(page);
-
- Ok(Content::sequence(seq))
- }
-}
diff --git a/library/src/meta/query.rs b/library/src/meta/query.rs
deleted file mode 100644
index 826b812e..00000000
--- a/library/src/meta/query.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use crate::prelude::*;
-
-/// Finds elements in the document.
-///
-/// The `query` functions lets you search your document for elements of a
-/// particular type or with a particular label.
-///
-/// To use it, you first need to retrieve the current document location with the
-/// [`locate`]($func/locate) function. You can then decide whether you want to
-/// find all elements, just the ones before that location, or just the ones
-/// after it.
-///
-/// ## Finding elements { #finding-elements }
-/// In the example below, we create a custom page header that displays the text
-/// "Typst Academy" in small capitals and the current section title. On the
-/// first page, the section title is omitted because the header is before the
-/// first section heading.
-///
-/// To realize this layout, we call `locate` and then query for all headings
-/// after the current location. The function we pass to locate is called twice
-/// in this case: Once per page.
-///
-/// - On the first page the query for all headings before the current location
-/// yields an empty array: There are no previous headings. We check for this
-/// case and and just display "Typst Academy".
-///
-/// - For the second page, we retrieve the last element from the query's result.
-/// This is the latest heading before the current position and as such, it is
-/// the heading of the section we are currently in. We access its content
-/// through the `body` field and display it alongside "Typst Academy".
-///
-/// ```example
-/// >>> #set page(
-/// >>> width: 240pt,
-/// >>> height: 180pt,
-/// >>> margin: (top: 35pt, rest: 15pt),
-/// >>> header-ascent: 12pt,
-/// >>> )
-/// #set page(header: locate(loc => {
-/// let elems = query(
-/// selector(heading).before(loc),
-/// loc,
-/// )
-/// let academy = smallcaps[
-/// Typst Academy
-/// ]
-/// if elems == () {
-/// align(right, academy)
-/// } else {
-/// let body = elems.last().body
-/// academy + h(1fr) + emph(body)
-/// }
-/// }))
-///
-/// = Introduction
-/// #lorem(23)
-///
-/// = Background
-/// #lorem(30)
-///
-/// = Analysis
-/// #lorem(15)
-/// ```
-///
-/// ## A word of caution { #caution }
-/// To resolve all your queries, Typst evaluates and layouts parts of the
-/// document multiple times. However, there is no guarantee that your queries
-/// can actually be completely resolved. If you aren't careful a query can
-/// affect itself—leading to a result that never stabilizes.
-///
-/// In the example below, we query for all headings in the document. We then
-/// generate as many headings. In the beginning, there's just one heading,
-/// titled `Real`. Thus, `count` is `1` and one `Fake` heading is generated.
-/// Typst sees that the query's result has changed and processes it again. This
-/// time, `count` is `2` and two `Fake` headings are generated. This goes on and
-/// on. As we can see, the output has five headings. This is because Typst
-/// simply gives up after five attempts.
-///
-/// In general, you should try not to write queries that affect themselves.
-/// The same words of caution also apply to other introspection features like
-/// [counters]($func/counter) and [state]($func/state).
-///
-/// ```example
-/// = Real
-/// #locate(loc => {
-/// let elems = query(heading, loc)
-/// let count = elems.len()
-/// count * [= Fake]
-/// })
-/// ```
-///
-/// ## Migration Hints { #migration-hints }
-/// The `before` and `after` arguments have been removed in version 0.3.0. You
-/// can now use flexible selector combinator methods instead. For example,
-/// `query(heading, before: loc)` becomes `query(heading.before(loc), loc)`.
-/// Please refer to the [selector documentation]($type/selector) for more
-/// details.
-///
-/// Display: Query
-/// Category: meta
-#[func]
-pub fn query(
- /// Can be an element function like a `heading` or `figure`, a `{<label>}`
- /// or a more complex selector like `{heading.where(level: 1)}`.
- ///
- /// Currently, only a subset of element functions is supported. Aside from
- /// headings and figures, this includes equations, references and all
- /// elements with an explicit label. As a result, you _can_ query for e.g.
- /// [`strong`]($func/strong) elements, but you will find only those that
- /// have an explicit label attached to them. This limitation will be
- /// resolved in the future.
- target: LocatableSelector,
- /// Can be any location. Why is it required then? As noted before, Typst has
- /// to evaluate parts of your code multiple times to determine the values of
- /// all state. By only allowing this function within
- /// [`locate`]($func/locate) calls, the amount of code that can depend on
- /// the query's result is reduced. If you could call it directly at the top
- /// level of a module, the evaluation of the whole module and its exports
- /// could depend on the query's result.
- location: Location,
- /// The virtual machine.
- vm: &mut Vm,
-) -> Array {
- let _ = location;
- let vec = vm.vt.introspector.query(&target.0);
- vec.into_iter()
- .map(|elem| Value::Content(elem.into_inner()))
- .collect()
-}
-
-/// Turns a value into a selector. The following values are accepted:
-/// - An element function like a `heading` or `figure`.
-/// - A `{<label>}`.
-/// - A more complex selector like `{heading.where(level: 1)}`.
-///
-/// Display: Selector
-/// Category: meta
-#[func]
-pub fn selector(
- /// Can be an element function like a `heading` or `figure`, a `{<label>}`
- /// or a more complex selector like `{heading.where(level: 1)}`.
- target: Selector,
-) -> Selector {
- target
-}
diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs
deleted file mode 100644
index 5bd04431..00000000
--- a/library/src/meta/reference.rs
+++ /dev/null
@@ -1,276 +0,0 @@
-use super::{BibliographyElem, CiteElem, Counter, Figurable, Numbering};
-use crate::meta::FootnoteElem;
-use crate::prelude::*;
-use crate::text::TextElem;
-
-/// A reference to a label or bibliography.
-///
-/// Produces a textual reference to a label. For example, a reference to a
-/// heading will yield an appropriate string such as "Section 1" for a reference
-/// to the first heading. The references are also links to the respective
-/// element. Reference syntax can also be used to [cite]($func/cite) from a
-/// bibliography.
-///
-/// Referenceable elements include [headings]($func/heading),
-/// [figures]($func/figure), [equations]($func/math.equation), and
-/// [footnotes]($func/footnote). To create a custom referenceable element like a
-/// theorem, you can create a figure of a custom [`kind`]($func/figure.kind) and
-/// write a show rule for it. In the future, there might be a more direct way to
-/// define a custom referenceable element.
-///
-/// If you just want to link to a labelled element and not get an automatic
-/// textual reference, consider using the [`link`]($func/link) function instead.
-///
-/// ## Example { #example }
-/// ```example
-/// #set heading(numbering: "1.")
-/// #set math.equation(numbering: "(1)")
-///
-/// = Introduction <intro>
-/// Recent developments in
-/// typesetting software have
-/// rekindled hope in previously
-/// frustrated researchers. @distress
-/// As shown in @results, we ...
-///
-/// = Results <results>
-/// We discuss our approach in
-/// comparison with others.
-///
-/// == Performance <perf>
-/// @slow demonstrates what slow
-/// software looks like.
-/// $ O(n) = 2^n $ <slow>
-///
-/// #bibliography("works.bib")
-/// ```
-///
-/// ## Syntax { #syntax }
-/// This function also has dedicated syntax: A reference to a label can be
-/// created by typing an `@` followed by the name of the label (e.g.
-/// `[= Introduction <intro>]` can be referenced by typing `[@intro]`).
-///
-/// To customize the supplement, add content in square brackets after the
-/// reference: `[@intro[Chapter]]`.
-///
-/// ## Customization { #customization }
-/// If you write a show rule for references, you can access the referenced
-/// element through the `element` field of the reference. The `element` may
-/// be `{none}` even if it exists if Typst hasn't discovered it yet, so you
-/// always need to handle that case in your code.
-///
-/// ```example
-/// #set heading(numbering: "1.")
-/// #set math.equation(numbering: "(1)")
-///
-/// #show ref: it => {
-/// let eq = math.equation
-/// let el = it.element
-/// if el != none and el.func() == eq {
-/// // Override equation references.
-/// numbering(
-/// el.numbering,
-/// ..counter(eq).at(el.location())
-/// )
-/// } else {
-/// // Other references as usual.
-/// it
-/// }
-/// }
-///
-/// = Beginnings <beginning>
-/// In @beginning we prove @pythagoras.
-/// $ a^2 + b^2 = c^2 $ <pythagoras>
-/// ```
-///
-/// Display: Reference
-/// Category: meta
-#[element(Synthesize, Locatable, Show)]
-pub struct RefElem {
- /// The target label that should be referenced.
- #[required]
- pub target: Label,
-
- /// A supplement for the reference.
- ///
- /// For references to headings or figures, this is added before the
- /// referenced number. For citations, this can be used to add a page number.
- ///
- /// If a function is specified, it is passed the referenced element and
- /// should return content.
- ///
- /// ```example
- /// #set heading(numbering: "1.")
- /// #set ref(supplement: it => {
- /// if it.func() == heading {
- /// "Chapter"
- /// } else {
- /// "Thing"
- /// }
- /// })
- ///
- /// = Introduction <intro>
- /// In @intro, we see how to turn
- /// Sections into Chapters. And
- /// in @intro[Part], it is done
- /// manually.
- /// ```
- pub supplement: Smart<Option<Supplement>>,
-
- /// A synthesized citation.
- #[synthesized]
- pub citation: Option<CiteElem>,
-
- /// The referenced element.
- #[synthesized]
- pub element: Option<Content>,
-}
-
-impl Synthesize for RefElem {
- fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
- let citation = self.to_citation(vt, styles)?;
- self.push_citation(Some(citation));
- self.push_element(None);
-
- let target = self.target();
- if !BibliographyElem::has(vt, &target.0) {
- if let Ok(elem) = vt.introspector.query_label(&target) {
- self.push_element(Some(elem.into_inner()));
- return Ok(());
- }
- }
-
- Ok(())
- }
-}
-
-impl Show for RefElem {
- #[tracing::instrument(name = "RefElem::show", skip_all)]
- fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- Ok(vt.delayed(|vt| {
- let target = self.target();
- let elem = vt.introspector.query_label(&self.target());
- let span = self.span();
-
- if BibliographyElem::has(vt, &target.0) {
- if elem.is_ok() {
- bail!(span, "label occurs in the document and its bibliography");
- }
-
- return Ok(self.to_citation(vt, styles)?.pack().spanned(span));
- }
-
- let elem = elem.at(span)?;
-
- if elem.func() == FootnoteElem::func() {
- return Ok(FootnoteElem::with_label(target).pack().spanned(span));
- }
-
- let refable = elem
- .with::<dyn Refable>()
- .ok_or_else(|| {
- if elem.can::<dyn Figurable>() {
- eco_format!(
- "cannot reference {} directly, try putting it into a figure",
- elem.func().name()
- )
- } else {
- eco_format!("cannot reference {}", elem.func().name())
- }
- })
- .at(span)?;
-
- let numbering = refable
- .numbering()
- .ok_or_else(|| {
- eco_format!(
- "cannot reference {} without numbering",
- elem.func().name()
- )
- })
- .hint(eco_format!(
- "you can enable heading numbering with `#set {}(numbering: \"1.\")`",
- elem.func().name()
- ))
- .at(span)?;
-
- let numbers = refable
- .counter()
- .at(vt, elem.location().unwrap())?
- .display(vt, &numbering.trimmed())?;
-
- let supplement = match self.supplement(styles) {
- Smart::Auto => refable.supplement(),
- Smart::Custom(None) => Content::empty(),
- Smart::Custom(Some(supplement)) => {
- supplement.resolve(vt, [(*elem).clone()])?
- }
- };
-
- let mut content = numbers;
- if !supplement.is_empty() {
- content = supplement + TextElem::packed("\u{a0}") + content;
- }
-
- Ok(content.linked(Destination::Location(elem.location().unwrap())))
- }))
- }
-}
-
-impl RefElem {
- /// Turn the reference into a citation.
- pub fn to_citation(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<CiteElem> {
- let mut elem = CiteElem::new(vec![self.target().0]);
- elem.0.set_location(self.0.location().unwrap());
- elem.synthesize(vt, styles)?;
- elem.push_supplement(match self.supplement(styles) {
- Smart::Custom(Some(Supplement::Content(content))) => Some(content),
- _ => None,
- });
-
- Ok(elem)
- }
-}
-
-/// Additional content for a reference.
-pub enum Supplement {
- Content(Content),
- Func(Func),
-}
-
-impl Supplement {
- /// Tries to resolve the supplement into its content.
- pub fn resolve<T: IntoValue>(
- &self,
- vt: &mut Vt,
- args: impl IntoIterator<Item = T>,
- ) -> SourceResult<Content> {
- Ok(match self {
- Supplement::Content(content) => content.clone(),
- Supplement::Func(func) => func.call_vt(vt, args)?.display(),
- })
- }
-}
-
-cast! {
- Supplement,
- self => match self {
- Self::Content(v) => v.into_value(),
- Self::Func(v) => v.into_value(),
- },
- v: Content => Self::Content(v),
- v: Func => Self::Func(v),
-}
-
-/// Marks an element as being able to be referenced. This is used to implement
-/// the `@ref` element.
-pub trait Refable {
- /// The supplement, if not overridden by the reference.
- fn supplement(&self) -> Content;
-
- /// Returns the counter of this element.
- fn counter(&self) -> Counter;
-
- /// Returns the numbering of this element.
- fn numbering(&self) -> Option<Numbering>;
-}
diff --git a/library/src/meta/state.rs b/library/src/meta/state.rs
deleted file mode 100644
index aee53a29..00000000
--- a/library/src/meta/state.rs
+++ /dev/null
@@ -1,440 +0,0 @@
-use std::fmt::{self, Debug, Formatter, Write};
-
-use ecow::{eco_vec, EcoVec};
-use typst::eval::Tracer;
-use typst::model::DelayedErrors;
-
-use crate::prelude::*;
-
-/// Manages stateful parts of your document.
-///
-/// Let's say you have some computations in your document and want to remember
-/// the result of your last computation to use it in the next one. You might try
-/// something similar to the code below and expect it to output 10, 13, 26, and
-/// 21. However this **does not work** in Typst. If you test this code, you will
-/// see that Typst complains with the following error message: _Variables from
-/// outside the function are read-only and cannot be modified._
-///
-/// ```typ
-/// #let x = 0
-/// #let compute(expr) = {
-/// x = eval(
-/// expr.replace("x", str(x))
-/// )
-/// [New value is #x. ]
-/// }
-///
-/// #compute("10") \
-/// #compute("x + 3") \
-/// #compute("x * 2") \
-/// #compute("x - 5")
-/// ```
-///
-/// ## State and document markup { #state-and-markup }
-/// Why does it do that? Because, in general, this kind of computation with side
-/// effects is problematic in document markup and Typst is upfront about that.
-/// For the results to make sense, the computation must proceed in the same
-/// order in which the results will be laid out in the document. In our simple
-/// example, that's the case, but in general it might not be.
-///
-/// Let's look at a slightly different, but similar kind of state: The heading
-/// numbering. We want to increase the heading counter at each heading. Easy
-/// enough, right? Just add one. Well, it's not that simple. Consider the
-/// following example:
-///
-/// ```example
-/// #set heading(numbering: "1.")
-/// #let template(body) = [
-/// = Outline
-/// ...
-/// #body
-/// ]
-///
-/// #show: template
-///
-/// = Introduction
-/// ...
-/// ```
-///
-/// Here, Typst first processes the body of the document after the show rule,
-/// sees the `Introduction` heading, then passes the resulting content to the
-/// `template` function and only then sees the `Outline`. Just counting up would
-/// number the `Introduction` with `1` and the `Outline` with `2`.
-///
-/// ## Managing state in Typst { #state-in-typst }
-/// So what do we do instead? We use Typst's state management system. Calling
-/// the `state` function with an identifying string key and an optional initial
-/// value gives you a state value which exposes a few methods. The two most
-/// important ones are `display` and `update`:
-///
-/// - The `display` method shows the current value of the state. You can
-/// optionally give it a function that receives the value and formats it in
-/// some way.
-///
-/// - The `update` method modifies the state. You can give it any value. If
-/// given a non-function value, it sets the state to that value. If given a
-/// function, that function receives the previous state and has to return the
-/// new state.
-///
-/// Our initial example would now look like this:
-///
-/// ```example
-/// #let s = state("x", 0)
-/// #let compute(expr) = [
-/// #s.update(x =>
-/// eval(expr.replace("x", str(x)))
-/// )
-/// New value is #s.display().
-/// ]
-///
-/// #compute("10") \
-/// #compute("x + 3") \
-/// #compute("x * 2") \
-/// #compute("x - 5")
-/// ```
-///
-/// State managed by Typst is always updated in layout order, not in evaluation
-/// order. The `update` method returns content and its effect occurs at the
-/// position where the returned content is inserted into the document.
-///
-/// As a result, we can now also store some of the computations in
-/// variables, but they still show the correct results:
-///
-/// ```example
-/// >>> #let s = state("x", 0)
-/// >>> #let compute(expr) = [
-/// >>> #s.update(x =>
-/// >>> eval(expr.replace("x", str(x)))
-/// >>> )
-/// >>> New value is #s.display().
-/// >>> ]
-/// <<< ...
-///
-/// #let more = [
-/// #compute("x * 2") \
-/// #compute("x - 5")
-/// ]
-///
-/// #compute("10") \
-/// #compute("x + 3") \
-/// #more
-/// ```
-///
-/// This example is of course a bit silly, but in practice this is often exactly
-/// what you want! A good example are heading counters, which is why Typst's
-/// [counting system]($func/counter) is very similar to its state system.
-///
-/// ## Time Travel { #time-travel }
-/// By using Typst's state management system you also get time travel
-/// capabilities! By combining the state system with [`locate`]($func/locate)
-/// and [`query`]($func/query), we can find out what the value of the state will
-/// be at any position in the document from anywhere else. In particular, the
-/// `at` method gives us the value of the state at any location and the `final`
-/// methods gives us the value of the state at the end of the document.
-///
-/// ```example
-/// >>> #let s = state("x", 0)
-/// >>> #let compute(expr) = [
-/// >>> #s.update(x => {
-/// >>> eval(expr.replace("x", str(x)))
-/// >>> })
-/// >>> New value is #s.display().
-/// >>> ]
-/// <<< ...
-///
-/// Value at `<here>` is
-/// #locate(loc => s.at(
-/// query(<here>, loc)
-/// .first()
-/// .location()
-/// ))
-///
-/// #compute("10") \
-/// #compute("x + 3") \
-/// *Here.* <here> \
-/// #compute("x * 2") \
-/// #compute("x - 5")
-/// ```
-///
-/// ## A word of caution { #caution }
-/// To resolve the values of all states, Typst evaluates parts of your code
-/// multiple times. However, there is no guarantee that your state manipulation
-/// can actually be completely resolved.
-///
-/// For instance, if you generate state updates depending on the final value of
-/// a state, the results might never converge. The example below illustrates
-/// this. We initialize our state with `1` and then update it to its own final
-/// value plus 1. So it should be `2`, but then its final value is `2`, so it
-/// should be `3`, and so on. This example display `4` because Typst simply
-/// gives up after a few attempts.
-///
-/// ```example
-/// #let s = state("x", 1)
-/// #locate(loc => {
-/// s.update(s.final(loc) + 1)
-/// })
-/// #s.display()
-/// ```
-///
-/// In general, you should _typically_ not generate state updates from within
-/// `locate` calls or `display` calls of state or counters. Instead, pass a
-/// function to `update` that determines the value of the state based on its
-/// previous value.
-///
-/// ## Methods
-/// ### display()
-/// Displays the value of the state.
-///
-/// - format: function (positional)
-/// A function which receives the value of the state and can return arbitrary
-/// content which is then displayed. If this is omitted, the value is directly
-/// displayed.
-///
-/// - returns: content
-///
-/// ### update()
-/// Updates the value of the state.
-///
-/// The update will be in effect at the position where the returned content is
-/// inserted into the document. If you don't put the output into the document,
-/// nothing happens! This would be the case, for example, if you write
-/// `{let _ = state("key").update(7)}`. State updates are always applied in
-/// layout order and in that case, Typst wouldn't know when to update the state.
-///
-/// - value: any or function (positional, required)
-/// If given a non function-value, sets the state to that value. If given a
-/// function, that function receives the previous state and has to return the
-/// new state.
-///
-/// - returns: content
-///
-/// ### at()
-/// Gets the value of the state at the given location.
-///
-/// - location: location (positional, required)
-/// The location at which the state's value should be retrieved. A suitable
-/// location can be retrieved from [`locate`]($func/locate) or
-/// [`query`]($func/query).
-///
-/// - returns: any
-///
-/// ### final()
-/// Gets the value of the state at the end of the document.
-///
-/// - location: location (positional, required)
-/// Can be any location. Why is it required then? As noted before, Typst has
-/// to evaluate parts of your code multiple times to determine the values of
-/// all state. By only allowing this method within [`locate`]($func/locate)
-/// calls, the amount of code that can depend on the method's result is
-/// reduced. If you could call `final` directly at the top level of a module,
-/// the evaluation of the whole module and its exports could depend on the
-/// state's value.
-///
-/// - returns: any
-///
-/// Display: State
-/// Category: meta
-#[func]
-pub fn state(
- /// The key that identifies this state.
- key: Str,
- /// The initial value of the state.
- #[default]
- init: Value,
-) -> State {
- State { key, init }
-}
-
-/// A state.
-#[derive(Clone, PartialEq, Hash)]
-pub struct State {
- /// The key that identifies the state.
- key: Str,
- /// The initial value of the state.
- init: Value,
-}
-
-impl State {
- /// Call a method on a state.
- #[tracing::instrument(skip(vm))]
- 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_value(),
- "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_value(),
- _ => bail!(span, "type state has no method `{}`", method),
- };
- args.finish()?;
- Ok(value)
- }
-
- /// Display the current value of the state.
- pub fn display(self, func: Option<Func>) -> Content {
- DisplayElem::new(self, func).pack()
- }
-
- /// Get the value of the state at the given location.
- #[tracing::instrument(skip(self, vt))]
- pub fn at(self, vt: &mut Vt, location: Location) -> SourceResult<Value> {
- let sequence = self.sequence(vt)?;
- let offset = vt.introspector.query(&self.selector().before(location, true)).len();
- Ok(sequence[offset].clone())
- }
-
- /// Get the value of the state at the final location.
- #[tracing::instrument(skip(self, vt))]
- pub fn final_(self, vt: &mut Vt, _: Location) -> 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 {
- UpdateElem::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,
- vt.introspector,
- vt.locator.track(),
- TrackedMut::reborrow_mut(&mut vt.delayed),
- TrackedMut::reborrow_mut(&mut vt.tracer),
- )
- }
-
- /// Memoized implementation of `sequence`.
- #[comemo::memoize]
- fn sequence_impl(
- &self,
- world: Tracked<dyn World + '_>,
- introspector: Tracked<Introspector>,
- locator: Tracked<Locator>,
- delayed: TrackedMut<DelayedErrors>,
- tracer: TrackedMut<Tracer>,
- ) -> SourceResult<EcoVec<Value>> {
- let mut locator = Locator::chained(locator);
- let mut vt = Vt {
- world,
- introspector,
- locator: &mut locator,
- delayed,
- tracer,
- };
- let mut state = self.init.clone();
- let mut stops = eco_vec![state.clone()];
-
- for elem in introspector.query(&self.selector()) {
- let elem = elem.to::<UpdateElem>().unwrap();
- match elem.update() {
- StateUpdate::Set(value) => state = value,
- StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?,
- }
- stops.push(state.clone());
- }
-
- Ok(stops)
- }
-
- /// The selector for this state's updates.
- fn selector(&self) -> Selector {
- Selector::Elem(UpdateElem::func(), Some(dict! { "state" => self.clone() }))
- }
-}
-
-impl Debug for State {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("state(")?;
- self.key.fmt(f)?;
- f.write_str(", ")?;
- self.init.fmt(f)?;
- f.write_char(')')
- }
-}
-
-cast! {
- type State: "state",
-}
-
-/// 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 Debug for StateUpdate {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad("..")
- }
-}
-
-cast! {
- type StateUpdate: "state update",
- v: Func => Self::Func(v),
- v: Value => Self::Set(v),
-}
-
-/// Executes a display of a state.
-///
-/// Display: State
-/// Category: special
-#[element(Locatable, Show)]
-struct DisplayElem {
- /// The state.
- #[required]
- state: State,
-
- /// The function to display the state with.
- #[required]
- func: Option<Func>,
-}
-
-impl Show for DisplayElem {
- #[tracing::instrument(name = "DisplayElem::show", skip(self, vt))]
- fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(vt.delayed(|vt| {
- let location = self.0.location().unwrap();
- let value = self.state().at(vt, location)?;
- 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
-#[element(Locatable, Show)]
-struct UpdateElem {
- /// The state.
- #[required]
- state: State,
-
- /// The update to perform on the state.
- #[required]
- update: StateUpdate,
-}
-
-impl Show for UpdateElem {
- #[tracing::instrument(name = "UpdateElem::show")]
- fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(Content::empty())
- }
-}