From ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 2 Jul 2023 19:59:52 +0200 Subject: Move everything into `crates/` directory --- library/src/meta/bibliography.rs | 724 --------------------------------------- library/src/meta/context.rs | 220 ------------ library/src/meta/counter.rs | 683 ------------------------------------ library/src/meta/document.rs | 86 ----- library/src/meta/figure.rs | 351 ------------------- library/src/meta/footnote.rs | 299 ---------------- library/src/meta/heading.rs | 239 ------------- library/src/meta/link.rs | 137 -------- library/src/meta/mod.rs | 64 ---- library/src/meta/numbering.rs | 525 ---------------------------- library/src/meta/outline.rs | 528 ---------------------------- library/src/meta/query.rs | 145 -------- library/src/meta/reference.rs | 276 --------------- library/src/meta/state.rs | 440 ------------------------ 14 files changed, 4717 deletions(-) delete mode 100644 library/src/meta/bibliography.rs delete mode 100644 library/src/meta/context.rs delete mode 100644 library/src/meta/counter.rs delete mode 100644 library/src/meta/document.rs delete mode 100644 library/src/meta/figure.rs delete mode 100644 library/src/meta/footnote.rs delete mode 100644 library/src/meta/heading.rs delete mode 100644 library/src/meta/link.rs delete mode 100644 library/src/meta/mod.rs delete mode 100644 library/src/meta/numbering.rs delete mode 100644 library/src/meta/outline.rs delete mode 100644 library/src/meta/query.rs delete mode 100644 library/src/meta/reference.rs delete mode 100644 library/src/meta/state.rs (limited to 'library/src/meta') 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::>("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::>>()?; - - // 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, - - /// 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>, - - /// The bibliography style. - #[default(BibliographyStyle::Ieee)] - pub style: BibliographyStyle, -} - -/// A list of bibliography file paths. -#[derive(Debug, Default, Clone, Hash)] -pub struct BibPaths(Vec); - -cast! { - BibPaths, - self => self.0.into_value(), - v: EcoString => Self(vec![v]), - v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), -} - -impl BibliographyElem { - /// Find the document's bibliography. - pub fn find(introspector: Tracked) -> StrResult { - 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::().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::().unwrap(); - load(&elem.path(), &elem.data()) - }) - .flatten() - .any(|entry| entry.key() == key) - } - - /// Find all bibliography keys. - pub fn keys( - introspector: Tracked, - ) -> Vec<(EcoString, Option)> { - 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 { - 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) -> &'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, - - /// 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, - - /// 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, -} - -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 { - 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::().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>, - references: Vec<(Option, Content)>, -} - -impl Works { - /// Prepare all things need to cite a work or format a bibliography. - fn new(vt: &Vt) -> StrResult> { - 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::() { - Some(reference) => reference.citation().unwrap(), - _ => elem.to::().unwrap().clone(), - }) - .collect(); - Ok(create(bibliography, citations)) - } -} - -/// Generate all citations and the whole bibliography. -#[comemo::memoize] -fn create(bibliography: BibliographyElem, citations: Vec) -> Arc { - 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::>>(); - preliminary.push((citation, entries)); - } - - let mut current = CitationStyle::Numerical; - let mut citation_style: Box = - 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 = 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> { - 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::>(); - 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> { - 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, - 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 { - 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 { - 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 { - // 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(, 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 -/// #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 -/// `{}` 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 `{