mirror of
https://github.com/typst/typst
synced 2025-05-18 02:55:28 +08:00
Fix and simplify reference supplements
- Fixes #873 by properly handling `none` supplement for`ref`. - Fixes #523 by adding a `supplement` parameter to `math.equation` - In the future, we can remove supplement functions in favor of show-set rules with fine-grained selectors. Currently, this is not possible because show-set + synthesis doesn't play well together
This commit is contained in:
parent
183997d5fe
commit
f4fd6855e7
@ -42,8 +42,10 @@ use self::fragment::*;
|
||||
use self::row::*;
|
||||
use self::spacing::*;
|
||||
use crate::layout::{HElem, ParElem, Spacing};
|
||||
use crate::meta::Refable;
|
||||
use crate::meta::{Count, Counter, CounterUpdate, LocalName, Numbering};
|
||||
use crate::meta::Supplement;
|
||||
use crate::meta::{
|
||||
Count, Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::text::{
|
||||
families, variant, FontFamily, FontList, LinebreakElem, SpaceElem, TextElem, TextSize,
|
||||
@ -140,7 +142,8 @@ pub fn module() -> Module {
|
||||
/// Display: Equation
|
||||
/// Category: math
|
||||
#[element(
|
||||
Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName, Refable
|
||||
Locatable, Synthesize, Show, Finalize, Layout, LayoutMath, Count, LocalName, Refable,
|
||||
Outlinable
|
||||
)]
|
||||
pub struct EquationElem {
|
||||
/// Whether the equation is displayed as a separate block.
|
||||
@ -160,15 +163,44 @@ pub struct EquationElem {
|
||||
/// ```
|
||||
pub numbering: Option<Numbering>,
|
||||
|
||||
/// A supplement for the equation.
|
||||
///
|
||||
/// For references to equations, this is added before the referenced number.
|
||||
///
|
||||
/// If a function is specified, it is passed the referenced equation and
|
||||
/// should return content.
|
||||
///
|
||||
/// ```example
|
||||
/// #set math.equation(numbering: "(1)", supplement: [Eq.])
|
||||
///
|
||||
/// We define:
|
||||
/// $ phi.alt := (1 + sqrt(5)) / 2 $ <ratio>
|
||||
///
|
||||
/// With @ratio, we get:
|
||||
/// $ F_n = floor(1 / sqrt(5) phi.alt^n) $
|
||||
/// ```
|
||||
pub supplement: Smart<Option<Supplement>>,
|
||||
|
||||
/// The contents of the equation.
|
||||
#[required]
|
||||
pub body: Content,
|
||||
}
|
||||
|
||||
impl Synthesize for EquationElem {
|
||||
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
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().into()])?
|
||||
}
|
||||
};
|
||||
|
||||
self.push_block(self.block(styles));
|
||||
self.push_numbering(self.numbering(styles));
|
||||
self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -302,41 +334,45 @@ impl LocalName for EquationElem {
|
||||
}
|
||||
|
||||
impl Refable for EquationElem {
|
||||
fn reference(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
supplement: Option<Content>,
|
||||
lang: Lang,
|
||||
region: Option<Region>,
|
||||
) -> SourceResult<Content> {
|
||||
// first we create the supplement of the heading
|
||||
let mut supplement =
|
||||
supplement.unwrap_or_else(|| TextElem::packed(self.local_name(lang, region)));
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
// we append a space if the supplement is not empty
|
||||
if !supplement.is_empty() {
|
||||
supplement += TextElem::packed('\u{a0}')
|
||||
};
|
||||
|
||||
// we check for a numbering
|
||||
let Some(numbering) = self.numbering(StyleChain::default()) else {
|
||||
bail!(self.span(), "only numbered equations can be referenced");
|
||||
};
|
||||
|
||||
// we get the counter and display it
|
||||
let numbers = Counter::of(Self::func())
|
||||
.at(vt, self.0.location().expect("missing location"))?
|
||||
.display(vt, &numbering.trimmed())?;
|
||||
|
||||
Ok(supplement + numbers)
|
||||
fn counter(&self) -> Counter {
|
||||
Counter::of(Self::func())
|
||||
}
|
||||
|
||||
fn numbering(&self) -> Option<Numbering> {
|
||||
self.numbering(StyleChain::default())
|
||||
}
|
||||
}
|
||||
|
||||
fn counter(&self) -> Counter {
|
||||
Counter::of(Self::func())
|
||||
impl Outlinable for EquationElem {
|
||||
fn outline(&self, vt: &mut Vt) -> SourceResult<Option<Content>> {
|
||||
let Some(numbering) = self.numbering(StyleChain::default()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// After synthesis, this should always be custom content.
|
||||
let mut supplement = match self.supplement(StyleChain::default()) {
|
||||
Smart::Custom(Some(Supplement::Content(content))) => content,
|
||||
_ => Content::empty(),
|
||||
};
|
||||
|
||||
if !supplement.is_empty() {
|
||||
supplement += TextElem::packed("\u{a0}");
|
||||
}
|
||||
|
||||
let numbers = self
|
||||
.counter()
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
|
||||
Ok(Some(supplement + numbers))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ use super::{
|
||||
Count, Counter, CounterKey, CounterUpdate, LocalName, Numbering, NumberingPattern,
|
||||
};
|
||||
use crate::layout::{BlockElem, VElem};
|
||||
use crate::meta::{Refable, Supplement};
|
||||
use crate::meta::{Outlinable, Refable, Supplement};
|
||||
use crate::prelude::*;
|
||||
use crate::text::TextElem;
|
||||
use crate::visualize::ImageElem;
|
||||
@ -77,7 +77,7 @@ use crate::visualize::ImageElem;
|
||||
///
|
||||
/// Display: Figure
|
||||
/// Category: meta
|
||||
#[element(Locatable, Synthesize, Count, Show, Finalize, Refable)]
|
||||
#[element(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)]
|
||||
pub struct FigureElem {
|
||||
/// The content of the figure. Often, an [image]($func/image).
|
||||
#[required]
|
||||
@ -120,8 +120,9 @@ pub struct FigureElem {
|
||||
/// language]($func/text.lang). If you are using a custom figure type, you
|
||||
/// will need to manually specify the supplement.
|
||||
///
|
||||
/// This can also be set to a function that receives the figure's body to
|
||||
/// select the supplement based on the figure's contents.
|
||||
/// 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(
|
||||
@ -131,8 +132,7 @@ pub struct FigureElem {
|
||||
/// kind: "foo",
|
||||
/// )
|
||||
/// ```
|
||||
#[default(Smart::Auto)]
|
||||
pub supplement: Smart<Supplement>,
|
||||
pub supplement: Smart<Option<Supplement>>,
|
||||
|
||||
/// How to number the figure. Accepts a
|
||||
/// [numbering pattern or function]($func/numbering).
|
||||
@ -163,52 +163,54 @@ pub struct FigureElem {
|
||||
|
||||
impl Synthesize for FigureElem {
|
||||
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
// Determine the figure's kind.
|
||||
let kind = match self.kind(styles) {
|
||||
Smart::Auto => self
|
||||
.find_figurable()
|
||||
.map(|elem| FigureKind::Elem(elem.func()))
|
||||
.unwrap_or_else(|| FigureKind::Elem(ImageElem::func())),
|
||||
Smart::Custom(kind) => kind,
|
||||
};
|
||||
|
||||
let content = match &kind {
|
||||
FigureKind::Elem(func) => self.find_of_elem(*func),
|
||||
FigureKind::Name(_) => None,
|
||||
}
|
||||
.unwrap_or_else(|| self.body());
|
||||
|
||||
let numbering = self.numbering(styles);
|
||||
|
||||
// We get the supplement or `None`. The supplement must either be set
|
||||
// manually or the content identification must have succeeded.
|
||||
let supplement = match self.supplement(styles) {
|
||||
Smart::Auto => match &kind {
|
||||
FigureKind::Elem(func) => {
|
||||
let elem = Content::new(*func).with::<dyn LocalName>().map(|c| {
|
||||
TextElem::packed(c.local_name(
|
||||
TextElem::lang_in(styles),
|
||||
TextElem::region_in(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()))
|
||||
});
|
||||
|
||||
if numbering.is_some() {
|
||||
Some(elem
|
||||
.ok_or("unable to determine the figure's `supplement`, please specify it manually")
|
||||
.at(self.span())?)
|
||||
} else {
|
||||
elem
|
||||
// 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")
|
||||
}
|
||||
FigureKind::Name(_) => {
|
||||
if numbering.is_some() {
|
||||
bail!(self.span(), "please specify the figure's supplement")
|
||||
} else {
|
||||
None
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
},
|
||||
Smart::Custom(supp) => Some(supp.resolve(vt, [content.into()])?),
|
||||
FigureKind::Name(_) => None,
|
||||
};
|
||||
|
||||
let target = descendant.unwrap_or_else(|| self.body());
|
||||
supplement.resolve(vt, [target.into()])?
|
||||
}
|
||||
};
|
||||
|
||||
// Construct the figure's counter.
|
||||
@ -221,9 +223,7 @@ impl Synthesize for FigureElem {
|
||||
|
||||
self.push_caption(self.caption(styles));
|
||||
self.push_kind(Smart::Custom(kind));
|
||||
self.push_supplement(Smart::Custom(Supplement::Content(
|
||||
supplement.unwrap_or_default(),
|
||||
)));
|
||||
self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
|
||||
self.push_numbering(numbering);
|
||||
self.push_outlined(self.outlined(styles));
|
||||
self.push_counter(Some(counter));
|
||||
@ -235,16 +235,15 @@ impl Synthesize for FigureElem {
|
||||
impl Show for FigureElem {
|
||||
#[tracing::instrument(name = "FigureElem::show", skip_all)]
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
// We build the body of the figure.
|
||||
let mut realized = self.body();
|
||||
|
||||
// We build the caption, if any.
|
||||
if self.caption(styles).is_some() {
|
||||
// Build the caption, if any.
|
||||
if let Some(caption) = self.full_caption(vt)? {
|
||||
realized += VElem::weak(self.gap(styles).into()).pack();
|
||||
realized += self.show_caption(vt)?;
|
||||
realized += caption;
|
||||
}
|
||||
|
||||
// We wrap the contents in a block.
|
||||
// Wrap the contents in a block.
|
||||
Ok(BlockElem::new()
|
||||
.with_body(Some(realized))
|
||||
.pack()
|
||||
@ -270,100 +269,60 @@ impl Count for FigureElem {
|
||||
}
|
||||
|
||||
impl Refable for FigureElem {
|
||||
fn reference(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
supplement: Option<Content>,
|
||||
_: Lang,
|
||||
_: Option<Region>,
|
||||
) -> SourceResult<Content> {
|
||||
// If the figure is not numbered, we cannot reference it.
|
||||
// Otherwise we build the supplement and numbering scheme.
|
||||
let Some(desc) = self.show_supplement_and_numbering(vt, supplement)? else {
|
||||
bail!(self.span(), "cannot reference unnumbered figure")
|
||||
};
|
||||
|
||||
Ok(desc)
|
||||
}
|
||||
|
||||
fn outline(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
_: Lang,
|
||||
_: Option<Region>,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
// If the figure is not outlined, it is not referenced.
|
||||
if !self.outlined(StyleChain::default()) {
|
||||
return Ok(None);
|
||||
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(),
|
||||
}
|
||||
|
||||
self.show_caption(vt).map(Some)
|
||||
}
|
||||
|
||||
fn numbering(&self) -> Option<Numbering> {
|
||||
self.numbering(StyleChain::default())
|
||||
}
|
||||
|
||||
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 {
|
||||
/// Determines the type of the figure by looking at the content, finding all
|
||||
/// [`Figurable`] elements and sorting them by priority then returning the highest.
|
||||
pub fn find_figurable(&self) -> Option<Content> {
|
||||
self.body().query_first(Selector::can::<dyn Figurable>()).cloned()
|
||||
}
|
||||
|
||||
/// Finds the element with the given function in the figure's content.
|
||||
/// Returns `None` if no element with the given function is found.
|
||||
pub fn find_of_elem(&self, func: ElemFunc) -> Option<Content> {
|
||||
self.body().query_first(Selector::Elem(func, None)).cloned()
|
||||
}
|
||||
|
||||
/// Builds the supplement and numbering of the figure. Returns [`None`] if
|
||||
/// there is no numbering.
|
||||
pub fn show_supplement_and_numbering(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
external_supplement: Option<Content>,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
if let (Some(numbering), Some(supplement), Some(counter)) = (
|
||||
self.numbering(StyleChain::default()),
|
||||
self.supplement(StyleChain::default())
|
||||
.as_custom()
|
||||
.and_then(|s| s.as_content()),
|
||||
self.counter(),
|
||||
) {
|
||||
let mut name = external_supplement.unwrap_or(supplement);
|
||||
if !name.is_empty() {
|
||||
name += TextElem::packed("\u{a0}");
|
||||
}
|
||||
|
||||
let number = counter
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.display(vt, &numbering)?
|
||||
.spanned(self.span());
|
||||
|
||||
Ok(Some(name + number))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the caption for the figure. If there is a numbering, will also
|
||||
/// try to show the supplement and the numbering.
|
||||
pub fn show_caption(&self, vt: &mut Vt) -> SourceResult<Content> {
|
||||
/// 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(Content::empty());
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if let Some(sup_and_num) = self.show_supplement_and_numbering(vt, None)? {
|
||||
caption = sup_and_num + TextElem::packed(": ") + caption;
|
||||
if let (
|
||||
Smart::Custom(Some(Supplement::Content(mut supplement))),
|
||||
Some(counter),
|
||||
Some(numbering),
|
||||
) = (
|
||||
self.supplement(StyleChain::default()),
|
||||
self.counter(),
|
||||
self.numbering(StyleChain::default()),
|
||||
) {
|
||||
let numbers =
|
||||
counter.at(vt, self.0.location().unwrap())?.display(vt, &numbering)?;
|
||||
|
||||
if !supplement.is_empty() {
|
||||
supplement += TextElem::packed("\u{a0}");
|
||||
}
|
||||
|
||||
caption = supplement + numbers + TextElem::packed(": ") + caption;
|
||||
}
|
||||
|
||||
Ok(caption)
|
||||
Ok(Some(caption))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use typst::font::FontWeight;
|
||||
use typst::util::option_eq;
|
||||
|
||||
use super::{Counter, CounterUpdate, LocalName, Numbering, Refable};
|
||||
use super::{Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable};
|
||||
use crate::layout::{BlockElem, HElem, VElem};
|
||||
use crate::meta::{Count, Supplement};
|
||||
use crate::prelude::*;
|
||||
@ -42,7 +42,7 @@ use crate::text::{SpaceElem, TextElem, TextSize};
|
||||
///
|
||||
/// Display: Heading
|
||||
/// Category: meta
|
||||
#[element(Locatable, Synthesize, Count, Show, Finalize, LocalName, Refable)]
|
||||
#[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)]
|
||||
@ -62,11 +62,13 @@ pub struct HeadingElem {
|
||||
|
||||
/// A supplement for the heading.
|
||||
///
|
||||
/// For references to headings, this is added before the
|
||||
/// referenced number.
|
||||
/// 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")
|
||||
/// #set heading(numbering: "1.", supplement: [Chapter])
|
||||
///
|
||||
/// = Introduction <intro>
|
||||
/// In @intro, we see how to turn
|
||||
@ -74,7 +76,6 @@ pub struct HeadingElem {
|
||||
/// in @intro[Part], it is done
|
||||
/// manually.
|
||||
/// ```
|
||||
#[default(Smart::Auto)]
|
||||
pub supplement: Smart<Option<Supplement>>,
|
||||
|
||||
/// Whether the heading should appear in the outline.
|
||||
@ -98,12 +99,21 @@ pub struct HeadingElem {
|
||||
}
|
||||
|
||||
impl Synthesize for HeadingElem {
|
||||
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
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().into()])?
|
||||
}
|
||||
};
|
||||
|
||||
self.push_level(self.level(styles));
|
||||
self.push_numbering(self.numbering(styles));
|
||||
self.push_supplement(self.supplement(styles));
|
||||
self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
|
||||
self.push_outlined(self.outlined(styles));
|
||||
self.push_supplement(self.supplement(styles));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -160,77 +170,42 @@ cast_from_value! {
|
||||
}
|
||||
|
||||
impl Refable for HeadingElem {
|
||||
fn reference(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
supplement: Option<Content>,
|
||||
lang: Lang,
|
||||
region: Option<Region>,
|
||||
) -> SourceResult<Content> {
|
||||
// Create the supplement of the heading.
|
||||
let mut supplement = if let Some(supplement) = supplement {
|
||||
supplement
|
||||
} else {
|
||||
match self.supplement(StyleChain::default()) {
|
||||
Smart::Auto => TextElem::packed(self.local_name(lang, region)),
|
||||
Smart::Custom(None) => Content::empty(),
|
||||
Smart::Custom(Some(supplement)) => {
|
||||
supplement.resolve(vt, std::iter::once(Value::from(self.clone())))?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Append a non-breaking space if the supplement is not empty.
|
||||
if !supplement.is_empty() {
|
||||
supplement += TextElem::packed('\u{a0}')
|
||||
};
|
||||
|
||||
// Check for a numbering.
|
||||
let Some(numbering) = self.numbering(StyleChain::default()) else {
|
||||
bail!(self.span(), "only numbered headings can be referenced");
|
||||
};
|
||||
|
||||
// Get the counter and display it.
|
||||
let numbers = Counter::of(Self::func())
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.display(vt, &numbering.trimmed())?;
|
||||
|
||||
Ok(supplement + numbers)
|
||||
}
|
||||
|
||||
fn level(&self) -> usize {
|
||||
self.level(StyleChain::default()).get()
|
||||
}
|
||||
|
||||
fn numbering(&self) -> Option<Numbering> {
|
||||
self.numbering(StyleChain::default())
|
||||
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 outline(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
_: Lang,
|
||||
_: Option<Region>,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
// Check whether the heading is outlined.
|
||||
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);
|
||||
}
|
||||
|
||||
// Build the numbering followed by the title.
|
||||
let mut start = self.body();
|
||||
let mut content = self.body();
|
||||
if let Some(numbering) = self.numbering(StyleChain::default()) {
|
||||
let numbers = Counter::of(HeadingElem::func())
|
||||
let numbers = Counter::of(Self::func())
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
start = numbers + SpaceElem::new().pack() + start;
|
||||
content = numbers + SpaceElem::new().pack() + content;
|
||||
};
|
||||
|
||||
Ok(Some(start))
|
||||
Ok(Some(content))
|
||||
}
|
||||
|
||||
fn level(&self) -> NonZeroUsize {
|
||||
self.level(StyleChain::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ 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) {
|
||||
@ -55,4 +56,9 @@ pub(super) fn define(global: &mut Scope) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -86,8 +86,11 @@ pub struct OutlineElem {
|
||||
/// caption: [Experiment results],
|
||||
/// )
|
||||
/// ```
|
||||
#[default(Selector::Elem(HeadingElem::func(), Some(dict! { "outlined" => true })))]
|
||||
pub target: Selector,
|
||||
#[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.
|
||||
@ -157,23 +160,21 @@ impl Show for OutlineElem {
|
||||
}
|
||||
|
||||
let indent = self.indent(styles);
|
||||
let depth = self.depth(styles).map_or(usize::MAX, NonZeroUsize::get);
|
||||
let lang = TextElem::lang_in(styles);
|
||||
let region = TextElem::region_in(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));
|
||||
let elems = vt.introspector.query(&self.target(styles).0);
|
||||
|
||||
for elem in &elems {
|
||||
let Some(refable) = elem.with::<dyn Refable>() else {
|
||||
bail!(elem.span(), "outlined elements must be referenceable");
|
||||
let Some(outlinable) = elem.with::<dyn Outlinable>() else {
|
||||
bail!(self.span(), "cannot outline {}", elem.func().name());
|
||||
};
|
||||
|
||||
if depth < refable.level() {
|
||||
if depth < outlinable.level() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(outline) = refable.outline(vt, lang, region)? else {
|
||||
let Some(outline) = outlinable.outline(vt)? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@ -183,8 +184,8 @@ impl Show for OutlineElem {
|
||||
// This is only applicable for elements with a hierarchy/level.
|
||||
while ancestors
|
||||
.last()
|
||||
.and_then(|ancestor| ancestor.with::<dyn Refable>())
|
||||
.map_or(false, |last| last.level() >= refable.level())
|
||||
.and_then(|ancestor| ancestor.with::<dyn Outlinable>())
|
||||
.map_or(false, |last| last.level() >= outlinable.level())
|
||||
{
|
||||
ancestors.pop();
|
||||
}
|
||||
@ -193,10 +194,10 @@ impl Show for OutlineElem {
|
||||
if indent {
|
||||
let mut hidden = Content::empty();
|
||||
for ancestor in &ancestors {
|
||||
let ancestor_refable = ancestor.with::<dyn Refable>().unwrap();
|
||||
let ancestor_outlinable = ancestor.with::<dyn Outlinable>().unwrap();
|
||||
|
||||
if let Some(numbering) = ancestor_refable.numbering() {
|
||||
let numbers = ancestor_refable
|
||||
if let Some(numbering) = ancestor_outlinable.numbering() {
|
||||
let numbers = ancestor_outlinable
|
||||
.counter()
|
||||
.at(vt, ancestor.location().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
@ -285,3 +286,15 @@ impl LocalName for OutlineElem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,9 @@ pub struct RefElem {
|
||||
/// 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 => {
|
||||
@ -149,43 +152,57 @@ impl Show for RefElem {
|
||||
|
||||
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!(self.span(), "label occurs in the document and its bibliography");
|
||||
bail!(span, "label occurs in the document and its bibliography");
|
||||
}
|
||||
|
||||
return Ok(self.to_citation(vt, styles)?.pack().spanned(self.span()));
|
||||
return Ok(self.to_citation(vt, styles)?.pack().spanned(span));
|
||||
}
|
||||
|
||||
let elem = elem.at(self.span())?;
|
||||
if !elem.can::<dyn Refable>() {
|
||||
if elem.can::<dyn Figurable>() {
|
||||
bail!(
|
||||
self.span(),
|
||||
"cannot reference {} directly, try putting it into a figure",
|
||||
elem.func().name()
|
||||
);
|
||||
} else {
|
||||
bail!(self.span(), "cannot reference {}", elem.func().name());
|
||||
}
|
||||
}
|
||||
let elem = elem.at(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())
|
||||
})
|
||||
.at(span)?;
|
||||
|
||||
let numbers = refable
|
||||
.counter()
|
||||
.at(vt, elem.location().unwrap())?
|
||||
.display(vt, &numbering.trimmed())?;
|
||||
|
||||
let supplement = match self.supplement(styles) {
|
||||
Smart::Auto | Smart::Custom(None) => None,
|
||||
Smart::Auto => refable.supplement(),
|
||||
Smart::Custom(None) => Content::empty(),
|
||||
Smart::Custom(Some(supplement)) => {
|
||||
Some(supplement.resolve(vt, [(*elem).clone().into()])?)
|
||||
supplement.resolve(vt, [(*elem).clone().into()])?
|
||||
}
|
||||
};
|
||||
|
||||
let lang = TextElem::lang_in(styles);
|
||||
let region = TextElem::region_in(styles);
|
||||
let reference = elem
|
||||
.with::<dyn Refable>()
|
||||
.expect("element should be refable")
|
||||
.reference(vt, supplement, lang, region)?;
|
||||
let mut content = numbers;
|
||||
if !supplement.is_empty() {
|
||||
content = supplement + TextElem::packed("\u{a0}") + content;
|
||||
}
|
||||
|
||||
Ok(reference.linked(Destination::Location(elem.location().unwrap())))
|
||||
Ok(content.linked(Destination::Location(elem.location().unwrap())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,19 +234,10 @@ impl Supplement {
|
||||
vt: &mut Vt,
|
||||
args: impl IntoIterator<Item = Value>,
|
||||
) -> SourceResult<Content> {
|
||||
match self {
|
||||
Supplement::Content(content) => Ok(content.clone()),
|
||||
Supplement::Func(func) => func.call_vt(vt, args).map(|v| v.display()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to get the content of the supplement.
|
||||
/// Returns `None` if the supplement is a function.
|
||||
pub fn as_content(self) -> Option<Content> {
|
||||
match self {
|
||||
Supplement::Content(content) => Some(content),
|
||||
_ => None,
|
||||
}
|
||||
Ok(match self {
|
||||
Supplement::Content(content) => content.clone(),
|
||||
Supplement::Func(func) => func.call_vt(vt, args)?.display(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,46 +255,14 @@ cast_to_value! {
|
||||
}
|
||||
|
||||
/// Marks an element as being able to be referenced. This is used to implement
|
||||
/// the `@ref` element. It is expected to build the [`Content`] that gets linked
|
||||
/// by the [`RefElem`].
|
||||
/// the `@ref` element.
|
||||
pub trait Refable {
|
||||
/// Tries to build a reference content for this element.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `vt` - The virtual typesetter.
|
||||
/// - `supplement` - The supplement of the reference.
|
||||
/// - `lang`: The language of the reference.
|
||||
/// - `region`: The region of the reference.
|
||||
fn reference(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
supplement: Option<Content>,
|
||||
lang: Lang,
|
||||
region: Option<Region>,
|
||||
) -> SourceResult<Content>;
|
||||
|
||||
/// Tries to build an outline element for this element.
|
||||
/// If this returns `None`, the outline will not include this element.
|
||||
/// By default this just calls [`Refable::reference`].
|
||||
fn outline(
|
||||
&self,
|
||||
vt: &mut Vt,
|
||||
lang: Lang,
|
||||
region: Option<Region>,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
self.reference(vt, None, lang, region).map(Some)
|
||||
}
|
||||
|
||||
/// Returns the level of this element.
|
||||
/// This is used to determine the level of the outline.
|
||||
/// By default this returns `0`.
|
||||
fn level(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
/// Returns the numbering of this element.
|
||||
fn numbering(&self) -> Option<Numbering>;
|
||||
/// The supplement, if not overriden 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>;
|
||||
}
|
||||
|
@ -463,6 +463,12 @@ impl Cast for LocatableSelector {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocatableSelector> for Value {
|
||||
fn from(value: LocatableSelector) -> Self {
|
||||
value.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A selector that can be used with show rules.
|
||||
///
|
||||
/// Hopefully, this is made obsolete by a more powerful showing mechanism in the
|
||||
@ -518,6 +524,12 @@ impl Cast for ShowableSelector {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ShowableSelector> for Value {
|
||||
fn from(value: ShowableSelector) -> Self {
|
||||
value.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A show rule transformation that can be applied to a match.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum Transform {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 39 KiB |
@ -21,102 +21,28 @@ As seen in @intro, we proceed.
|
||||
@foo
|
||||
|
||||
---
|
||||
#set heading(numbering: "1.", supplement: [Chapter])
|
||||
#set math.equation(numbering: "(1)", supplement: [Eq.])
|
||||
|
||||
#show ref: it => {
|
||||
if it.element != none and it.element.func() == figure {
|
||||
let element = it.element
|
||||
"["
|
||||
element.supplement
|
||||
"-"
|
||||
str(element.counter.at(element.location()).at(0))
|
||||
"]"
|
||||
// it
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
= Intro
|
||||
#figure(
|
||||
image("/cylinder.svg", height: 3cm),
|
||||
caption: [A sylinder.],
|
||||
image("/cylinder.svg", height: 1cm),
|
||||
caption: [A cylinder.],
|
||||
supplement: "Fig",
|
||||
) <fig1>
|
||||
|
||||
#figure(
|
||||
image("/tiger.jpg", height: 3cm),
|
||||
image("/tiger.jpg", height: 1cm),
|
||||
caption: [A tiger.],
|
||||
supplement: "Figg",
|
||||
supplement: "Tig",
|
||||
) <fig2>
|
||||
|
||||
#figure(
|
||||
$ A = 1 $,
|
||||
kind: "equation",
|
||||
supplement: "Equa",
|
||||
$ A = 1 $ <eq1>
|
||||
|
||||
) <eq1>
|
||||
@fig1
|
||||
#set math.equation(supplement: none)
|
||||
$ A = 1 $ <eq2>
|
||||
|
||||
@fig2
|
||||
@fig1, @fig2, @eq1, (@eq2)
|
||||
|
||||
@eq1
|
||||
|
||||
---
|
||||
#set heading(numbering: (..nums) => {
|
||||
nums.pos().map(str).join(".")
|
||||
}, supplement: [Chapt])
|
||||
|
||||
#show ref: it => {
|
||||
if it.element != none and it.element.func() == heading {
|
||||
let element = it.element
|
||||
"["
|
||||
emph(element.supplement)
|
||||
"-"
|
||||
numbering(element.numbering, ..counter(heading).at(element.location()))
|
||||
"]"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
= Introduction <intro>
|
||||
|
||||
= Summary <sum>
|
||||
|
||||
== Subsection <sub>
|
||||
|
||||
@intro
|
||||
|
||||
@sum
|
||||
|
||||
@sub
|
||||
|
||||
---
|
||||
|
||||
#show ref: it => {
|
||||
if it.element != none {
|
||||
if it.element.func() == text {
|
||||
let element = it.element
|
||||
"["
|
||||
element
|
||||
"]"
|
||||
} else if it.element.func() == underline {
|
||||
let element = it.element
|
||||
"{"
|
||||
element
|
||||
"}"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
@txt
|
||||
|
||||
Ref something unreferable <txt>
|
||||
|
||||
@under
|
||||
#underline[
|
||||
Some underline text.
|
||||
] <under>
|
||||
#set ref(supplement: none)
|
||||
@fig1, @fig2, @eq1, @eq2
|
||||
|
Loading…
x
Reference in New Issue
Block a user