Add support for page references through new ref.form property (#4729)

This commit is contained in:
Max 2024-11-12 12:54:25 +00:00 committed by GitHub
parent dadc2176e2
commit 8d4f01d284
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 270 additions and 77 deletions

View File

@ -494,7 +494,7 @@ impl MultiSpill<'_, '_> {
engine: &mut Engine,
regions: Regions,
) -> SourceResult<(Frame, Option<Self>)> {
// The first region becomes unchangable and committed to our backlog.
// The first region becomes unchangeable and committed to our backlog.
self.backlog.push(regions.size.y);
// The remaining regions are ephemeral and may be replaced.

View File

@ -723,7 +723,7 @@ fn layout_line_numbers(
continue;
}
// Layout the number and record its width in search of the maximium.
// Layout the number and record its width in search of the maximum.
let frame = layout_line_number(engine, config, &mut locator, &marker.numbering)?;
// Note that this line.y is larger than the previous due to sorting.

View File

@ -35,7 +35,7 @@ pub fn distribute(composer: &mut Composer, regions: Regions) -> FlowResult<Frame
struct Distributor<'a, 'b, 'x, 'y, 'z> {
/// The composer that is used to handle insertions.
composer: &'z mut Composer<'a, 'b, 'x, 'y>,
/// Regions which are continously shrunk as new items are added.
/// Regions which are continuously shrunk as new items are added.
regions: Regions<'z>,
/// Already laid out items, not yet aligned.
items: Vec<Item<'a, 'b>>,

View File

@ -774,7 +774,7 @@ fn shape_segment<'a>(
buffer.guess_segment_properties();
// By default, Harfbuzz will create zero-width space glyphs for default
// ignorables. This is probably useful for GUI apps that want noticable
// ignorables. This is probably useful for GUI apps that want noticeable
// effects on the cursor for those, but for us it's not useful and hurts
// text extraction.
buffer.set_flags(BufferFlags::REMOVE_DEFAULT_IGNORABLES);

View File

@ -23,6 +23,7 @@ pub fn finalize(
foreground,
fill,
numbering,
supplement,
}: LayoutedPage,
) -> SourceResult<Page> {
// If two sided, left becomes inside and right becomes outside.
@ -69,5 +70,5 @@ pub fn finalize(
let number = counter.logical();
counter.step();
Ok(Page { frame, fill, numbering, number })
Ok(Page { frame, fill, numbering, supplement, number })
}

View File

@ -14,7 +14,7 @@ use typst_library::layout::{
};
use typst_library::model::Numbering;
use typst_library::routines::{Pair, Routines};
use typst_library::text::TextElem;
use typst_library::text::{LocalName, TextElem};
use typst_library::visualize::Paint;
use typst_library::World;
use typst_utils::Numeric;
@ -36,6 +36,7 @@ pub struct LayoutedPage {
pub foreground: Option<Frame>,
pub fill: Smart<Option<Paint>>,
pub numbering: Option<Numbering>,
pub supplement: Content,
}
/// Layout a single page suitable for parity adjustment.
@ -128,6 +129,10 @@ fn layout_page_run_impl(
let header_ascent = PageElem::header_ascent_in(styles).relative_to(margin.top);
let footer_descent = PageElem::footer_descent_in(styles).relative_to(margin.bottom);
let numbering = PageElem::numbering_in(styles);
let supplement = match PageElem::supplement_in(styles) {
Smart::Auto => TextElem::packed(PageElem::local_name_in(styles)),
Smart::Custom(content) => content.unwrap_or_default(),
};
let number_align = PageElem::number_align_in(styles);
let binding =
PageElem::binding_in(styles).unwrap_or_else(|| match TextElem::dir_in(styles) {
@ -204,6 +209,7 @@ fn layout_page_run_impl(
inner,
fill: fill.clone(),
numbering: numbering.clone(),
supplement: supplement.clone(),
header: layout_marginal(header, header_size, Alignment::BOTTOM)?,
footer: layout_marginal(footer, footer_size, Alignment::TOP)?,
background: layout_marginal(background, full_size, mid)?,

View File

@ -21,6 +21,8 @@ pub struct Introspector {
pages: usize,
/// The page numberings, indexed by page number minus 1.
page_numberings: Vec<Option<Numbering>>,
/// The page supplements, indexed by page number minus 1.
page_supplements: Vec<Content>,
/// All introspectable elements.
elems: Vec<Pair>,
@ -266,6 +268,12 @@ impl Introspector {
.and_then(|slot| slot.as_ref())
}
/// Gets the page supplement for the given location, if any.
pub fn page_supplement(&self, location: Location) -> Content {
let page = self.page(location);
self.page_supplements.get(page.get() - 1).cloned().unwrap_or_default()
}
/// Try to find a location for an element with the given `key` hash
/// that is closest after the `anchor`.
///
@ -339,6 +347,7 @@ impl Clone for QueryCache {
#[derive(Default)]
struct IntrospectorBuilder {
page_numberings: Vec<Option<Numbering>>,
page_supplements: Vec<Content>,
seen: HashSet<Location>,
insertions: MultiMap<Location, Vec<Pair>>,
keys: MultiMap<u128, Location>,
@ -355,11 +364,13 @@ impl IntrospectorBuilder {
/// Build the introspector.
fn build(mut self, pages: &[Page]) -> Introspector {
self.page_numberings.reserve(pages.len());
self.page_supplements.reserve(pages.len());
// Discover all elements.
let mut root = Vec::new();
for (i, page) in pages.iter().enumerate() {
self.page_numberings.push(page.numbering.clone());
self.page_supplements.push(page.supplement.clone());
self.discover(
&mut root,
&page.frame,
@ -379,6 +390,7 @@ impl IntrospectorBuilder {
Introspector {
pages: pages.len(),
page_numberings: self.page_numberings,
page_supplements: self.page_supplements,
elems,
keys: self.keys,
locations: self.locations,

View File

@ -17,6 +17,7 @@ use crate::layout::{
Sides, SpecificAlignment,
};
use crate::model::Numbering;
use crate::text::LocalName;
use crate::visualize::{Color, Paint};
/// Layouts its child onto one or multiple pages.
@ -222,6 +223,19 @@ pub struct PageElem {
#[ghost]
pub numbering: Option<Numbering>,
/// A supplement for the pages.
///
/// For page references, this is added before the page number.
///
/// ```example
/// #set page(numbering: "1.", supplement: [p.])
///
/// = Introduction <intro>
/// We are on #ref(<intro>, form: "page")!
/// ```
#[ghost]
pub supplement: Smart<Option<Content>>,
/// The alignment of the page numbering.
///
/// If the vertical component is `top`, the numbering is placed into the
@ -376,6 +390,10 @@ impl Construct for PageElem {
}
}
impl LocalName for PageElem {
const KEY: &'static str = "page";
}
/// A manual page break.
///
/// Must not be used inside any containers.
@ -449,6 +467,8 @@ pub struct Page {
pub fill: Smart<Option<Paint>>,
/// The page's numbering.
pub numbering: Option<Numbering>,
/// The page's supplement.
pub supplement: Content,
/// The logical page number (controlled by `counter(page)` and may thus not
/// match the physical number).
pub number: usize,

View File

@ -4,10 +4,10 @@ use ecow::eco_format;
use crate::diag::{bail, At, Hint, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Content, Context, Func, IntoValue, Label, NativeElement, Packed, Show,
Smart, StyleChain, Synthesize,
cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed,
Show, Smart, StyleChain, Synthesize,
};
use crate::introspection::{Counter, Locatable};
use crate::introspection::{Counter, CounterKey, Locatable};
use crate::math::EquationElem;
use crate::model::{
BibliographyElem, CiteElem, Destination, Figurable, FootnoteElem, Numbering,
@ -16,22 +16,35 @@ 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] from a bibliography.
/// Takes a label and cross-references it. There are two kind of references,
/// determined by its [`form`]($ref.form): `{"normal"}` and `{"page"}`.
///
/// Referenceable elements include [headings]($heading), [figures]($figure),
/// [equations]($math.equation), and [footnotes]($footnote). To create a custom
/// referenceable element like a theorem, you can create a figure of a custom
/// [`kind`]($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.
/// The default, a `{"normal"}` reference, 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] from a bibliography.
///
/// As the default form requires a supplement and numbering, the label must be
/// attached to a _referenceable element_. Referenceable elements include
/// [headings]($heading), [figures]($figure), [equations]($math.equation), and
/// [footnotes]($footnote). To create a custom referenceable element like a
/// theorem, you can create a figure of a custom [`kind`]($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`] function instead.
///
/// A `{"page"}` reference produces a page reference to a label, displaying the
/// page number at its location. You can use the
/// [page's supplement]($page.supplement) to modify the text before the page
/// number. Unlike a `{"normal"}` reference, the label can be attached to any
/// element.
///
/// # Example
/// ```example
/// #set page(numbering: "1")
/// #set heading(numbering: "1.")
/// #set math.equation(numbering: "(1)")
///
@ -40,7 +53,9 @@ use crate::text::TextElem;
/// typesetting software have
/// rekindled hope in previously
/// frustrated researchers. @distress
/// As shown in @results, we ...
/// As shown in @results (see
/// #ref(<results>, form: "page")),
/// we ...
///
/// = Results <results>
/// We discuss our approach in
@ -55,9 +70,9 @@ use crate::text::TextElem;
/// ```
///
/// # 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]`).
/// This function also has dedicated syntax: A `{"normal"}` 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]]`.
@ -95,22 +110,30 @@ use crate::text::TextElem;
pub struct RefElem {
/// The target label that should be referenced.
///
/// Can be a label that is defined in the document or an entry from the
/// Can be a label that is defined in the document or, if the
/// [`form`]($ref.form) is set to `["normal"]`, an entry from the
/// [`bibliography`].
#[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 the [`form`]($ref.form) is set to `{"normal"}`:
/// - 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 the [`form`]($ref.form) is set to `{"page"}`, then this is added
/// before the page number of the label referenced.
///
/// If a function is specified, it is passed the referenced element and
/// should return content.
///
/// ```example
/// #set heading(numbering: "1.")
/// #set ref(supplement: it => {
/// #show ref.where(
/// form: "normal"
/// ): set ref(supplement: it => {
/// if it.func() == heading {
/// "Chapter"
/// } else {
@ -127,6 +150,17 @@ pub struct RefElem {
#[borrowed]
pub supplement: Smart<Option<Supplement>>,
/// The kind of reference to produce.
///
/// ```example
/// #set page(numbering: "1")
///
/// Here <here> we are on
/// #ref(<here>, form: "page").
/// ```
#[default(RefForm::Normal)]
pub form: RefForm,
/// A synthesized citation.
#[synthesized]
pub citation: Option<Packed<CiteElem>>,
@ -167,6 +201,34 @@ impl Show for Packed<RefElem> {
let elem = engine.introspector.query_label(target);
let span = self.span();
let form = self.form(styles);
if form == RefForm::Page {
let elem = elem.at(span)?;
let elem = elem.clone();
let loc = elem.location().unwrap();
let numbering = engine
.introspector
.page_numbering(loc)
.ok_or_else(|| eco_format!("cannot reference without page numbering"))
.hint(eco_format!(
"you can enable page numbering with `#set page(numbering: \"1\")`"
))
.at(span)?;
let supplement = engine.introspector.page_supplement(loc);
return show_reference(
self,
engine,
styles,
Counter::new(CounterKey::Page),
numbering.clone(),
supplement,
elem,
);
}
// RefForm::Normal
if BibliographyElem::has(engine, target) {
if elem.is_ok() {
bail!(span, "label occurs in the document and its bibliography");
@ -212,20 +274,35 @@ impl Show for Packed<RefElem> {
))
.at(span)?;
let loc = elem.location().unwrap();
let numbers = refable.counter().display_at_loc(
show_reference(
self,
engine,
loc,
styles,
&numbering.clone().trimmed(),
)?;
let supplement = match self.supplement(styles).as_ref() {
Smart::Auto => refable.supplement(),
Smart::Custom(None) => Content::empty(),
Smart::Custom(Some(supplement)) => {
supplement.resolve(engine, styles, [elem])?
refable.counter(),
numbering.clone(),
refable.supplement(),
elem,
)
}
}
/// Show a reference.
fn show_reference(
reference: &Packed<RefElem>,
engine: &mut Engine,
styles: StyleChain,
counter: Counter,
numbering: Numbering,
supplement: Content,
elem: Content,
) -> SourceResult<Content> {
let loc = elem.location().unwrap();
let numbers = counter.display_at_loc(engine, loc, styles, &numbering.trimmed())?;
let supplement = match reference.supplement(styles).as_ref() {
Smart::Auto => supplement,
Smart::Custom(None) => Content::empty(),
Smart::Custom(Some(supplement)) => supplement.resolve(engine, styles, [elem])?,
};
let mut content = numbers;
@ -234,7 +311,6 @@ impl Show for Packed<RefElem> {
}
Ok(content.linked(Destination::Location(loc)))
}
}
/// Turn a reference into a citation.
@ -293,6 +369,16 @@ cast! {
v: Func => Self::Func(v),
}
/// The form of the reference.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum RefForm {
/// Produces a textual reference to a label.
#[default]
Normal,
/// Produces a page reference to a label.
Page,
}
/// Marks an element as being able to be referenced. This is used to implement
/// the `@ref` element.
pub trait Refable {

View File

@ -5,3 +5,4 @@ bibliography = المراجع
heading = الفصل
outline = المحتويات
raw = قائمة
page = صفحة

View File

@ -5,3 +5,4 @@ bibliography = Bibliografia
heading = Secció
outline = Índex
raw = Llistat
page = pàgina

View File

@ -5,3 +5,4 @@ bibliography = Bibliografie
heading = Kapitola
outline = Obsah
raw = Seznam
page = strana

View File

@ -5,3 +5,4 @@ bibliography = Bibliografi
heading = Afsnit
outline = Indhold
raw = Liste
page = side

View File

@ -5,3 +5,4 @@ bibliography = Bibliographie
heading = Abschnitt
outline = Inhaltsverzeichnis
raw = Listing
page = Seite

View File

@ -5,3 +5,4 @@ bibliography = Bibliography
heading = Section
outline = Contents
raw = Listing
page = page

View File

@ -5,3 +5,4 @@ bibliography = Bibliografía
heading = Sección
outline = Índice
raw = Listado
page = página

View File

@ -5,3 +5,4 @@ bibliography = Viited
heading = Peatükk
outline = Sisukord
raw = List
page = lk.

View File

@ -5,3 +5,4 @@ bibliography = Viitteet
heading = Osio
outline = Sisällys
raw = Esimerkki
page = sivu

View File

@ -5,3 +5,4 @@ bibliography = Bibliographie
heading = Chapitre
outline = Table des matières
raw = Liste
page = page

View File

@ -5,3 +5,4 @@ bibliography = Bibliografía
heading = Sección
outline = Índice
raw = Listado
page = páxina

View File

@ -5,3 +5,4 @@ bibliography = רשימת מקורות
heading = חלק
outline = תוכן עניינים
raw = קטע מקור
page = עמוד

View File

@ -5,3 +5,4 @@ bibliography = Irodalomjegyzék
heading = Fejezet
outline = Tartalomjegyzék
# raw =
page = oldal

View File

@ -5,3 +5,4 @@ bibliography = Heimildaskrá
heading = Kafli
outline = Efnisyfirlit
raw = Sýnishorn
page = blaðsíða

View File

@ -5,3 +5,4 @@ bibliography = Bibliografia
heading = Sezione
outline = Indice
raw = Codice
page = pag.

View File

@ -5,3 +5,4 @@ bibliography = 参考文献
heading = 節
outline = 目次
raw = リスト
page = ページ

View File

@ -5,3 +5,4 @@ bibliography = Conspectus librorum
heading = Caput
outline = Index capitum
raw = Exemplum
page = charta

View File

@ -5,3 +5,4 @@ bibliography = Bibliografi
heading = Kapittel
outline = Innhold
raw = Utskrift
page = side

View File

@ -5,3 +5,4 @@ bibliography = Bibliografie
heading = Hoofdstuk
outline = Inhoudsopgave
raw = Listing
page = pagina

View File

@ -5,3 +5,4 @@ bibliography = Bibliografi
heading = Kapittel
outline = Innhald
raw = Utskrift
page = side

View File

@ -5,3 +5,4 @@ bibliography = Bibliografia
heading = Sekcja
outline = Spis treści
raw = Program
page = strona

View File

@ -5,3 +5,4 @@
heading = Secção
outline = Índice
# raw =
page = página

View File

@ -5,3 +5,4 @@ bibliography = Bibliografia
heading = Seção
outline = Sumário
raw = Listagem
page = página

View File

@ -6,3 +6,4 @@ heading = Secțiunea
outline = Cuprins
# may be wrong
raw = Listă
page = pagina

View File

@ -5,3 +5,4 @@ bibliography = Библиография
heading = Раздел
outline = Содержание
raw = Листинг
page = с.

View File

@ -5,3 +5,4 @@ bibliography = Literatura
heading = Poglavje
outline = Kazalo
raw = Program
page = stran

View File

@ -5,3 +5,4 @@ bibliography = Bibliografi
heading = Kapitull
outline = Përmbajtja
raw = List
page = faqe

View File

@ -5,3 +5,4 @@ bibliography = Литература
heading = Поглавље
outline = Садржај
raw = Програм
page = страна

View File

@ -5,3 +5,4 @@ bibliography = Bibliografi
heading = Kapitel
outline = Innehåll
raw = Listing
page = sida

View File

@ -5,3 +5,4 @@ bibliography = Bibliograpiya
heading = Seksyon
outline = Talaan ng mga Nilalaman
raw = Listahan
# page =

View File

@ -5,3 +5,4 @@ bibliography = Kaynakça
heading = Bölüm
outline = İçindekiler
raw = Liste
page = sayfa

View File

@ -5,3 +5,4 @@ bibliography = Бібліографія
heading = Розділ
outline = Зміст
raw = Лістинг
page = c.

View File

@ -6,3 +6,4 @@ heading = Phần
outline = Mục lục
# may be wrong
raw = Chương trình
page = trang

View File

@ -5,3 +5,4 @@ bibliography = 書目
heading = 小節
outline = 目錄
raw = 程式
# page =

View File

@ -5,3 +5,4 @@ bibliography = 参考文献
heading = 小节
outline = 目录
raw = 代码
# page =

View File

@ -433,7 +433,7 @@ frequently used for [figures]($figure.placement).
### Use columns anywhere in your document { #columns-anywhere }
To create columns within a nested layout, e.g. within a rectangle, you can use
the [`columns` function]($columns) directly. However, it really should only be
used within nested layouts. At the page-level, the page set rule is preferrable
used within nested layouts. At the page-level, the page set rule is preferable
because it has better interactions with things like page-level floats,
footnotes, and line numbers.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
tests/ref/ref-form-page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -230,6 +230,13 @@ Z
#counter(page).update(53)
#filler
--- page-numbering-hint ---
= Heading <intro>
// Error: 1:21-1:47 cannot reference without page numbering
// Hint: 1:21-1:47 you can enable page numbering with `#set page(numbering: "1")`
Can not be used as #ref(<intro>, form: "page")
--- page-suppress-headers-and-footers ---
#set page(header: none, footer: none, numbering: "1")
Look, ma, no page numbers!

View File

@ -55,6 +55,32 @@ $ A = 1 $ <eq2>
@arrgh
#bibliography("/assets/bib/works.bib")
--- ref-form-page ---
#set page(numbering: "1")
Text <text> is on #ref(<text>, form: "page").
See #ref(<setup>, form: "page").
#set page(supplement: [p.])
== Setup <setup>
Text seen on #ref(<text>, form: "page").
Text seen on #ref(<text>, form: "page", supplement: "Page").
--- ref-form-page-unambiguous ---
// Test that page reference is not ambiguous.
#set page(numbering: "1")
= Introduction <arrgh>
#ref(<arrgh>, form: "page")
#bibliography("/assets/bib/works.bib")
--- ref-form-page-bibliography ---
// Error: 2-28 label `<quark>` does not exist in the document
#ref(<quark>, form: "page")
#bibliography("/assets/bib/works.bib")
--- issue-4536-non-whitespace-before-ref ---
// Test reference with non-whitespace before it.
#figure[] <1>