diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-07-02 19:59:52 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-07-02 20:07:43 +0200 |
| commit | ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch) | |
| tree | 2bbc24ddb4124c4bb14dec0e536129d4de37b056 /library/src/meta | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'library/src/meta')
| -rw-r--r-- | library/src/meta/bibliography.rs | 724 | ||||
| -rw-r--r-- | library/src/meta/context.rs | 220 | ||||
| -rw-r--r-- | library/src/meta/counter.rs | 683 | ||||
| -rw-r--r-- | library/src/meta/document.rs | 86 | ||||
| -rw-r--r-- | library/src/meta/figure.rs | 351 | ||||
| -rw-r--r-- | library/src/meta/footnote.rs | 299 | ||||
| -rw-r--r-- | library/src/meta/heading.rs | 239 | ||||
| -rw-r--r-- | library/src/meta/link.rs | 137 | ||||
| -rw-r--r-- | library/src/meta/mod.rs | 64 | ||||
| -rw-r--r-- | library/src/meta/numbering.rs | 525 | ||||
| -rw-r--r-- | library/src/meta/outline.rs | 528 | ||||
| -rw-r--r-- | library/src/meta/query.rs | 145 | ||||
| -rw-r--r-- | library/src/meta/reference.rs | 276 | ||||
| -rw-r--r-- | library/src/meta/state.rs | 440 |
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()) - } -} |
