use comemo::Track;
use ecow::eco_format;
use crate::diag::{At, Hint, SourceResult, bail};
use crate::engine::Engine;
use crate::foundations::{
Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed, Repr, Smart,
StyleChain, Synthesize, cast, elem,
};
use crate::introspection::{Counter, CounterKey, Locatable, Tagged};
use crate::math::EquationElem;
use crate::model::{
BibliographyElem, CiteElem, DirectLinkElem, Figurable, FootnoteElem, Numbering,
};
use crate::text::TextElem;
#[elem(title = "Reference", Locatable, Tagged, Synthesize)]
pub struct RefElem {
#[required]
pub target: Label,
pub supplement: Smart<Option<Supplement>>,
#[default(RefForm::Normal)]
pub form: RefForm,
#[synthesized]
pub citation: Option<Packed<CiteElem>>,
#[synthesized]
pub element: Option<Content>,
}
impl Synthesize for Packed<RefElem> {
fn synthesize(
&mut self,
engine: &mut Engine,
styles: StyleChain,
) -> SourceResult<()> {
let citation = to_citation(self, engine, styles)?;
let elem = self.as_mut();
elem.citation = Some(Some(citation));
elem.element = Some(None);
if !BibliographyElem::has(engine, elem.target)
&& let Ok(found) = engine.introspector.query_label(elem.target).cloned()
{
elem.element = Some(Some(found));
return Ok(());
}
Ok(())
}
}
impl Packed<RefElem> {
pub fn realize(
&self,
engine: &mut Engine,
styles: StyleChain,
) -> SourceResult<Content> {
let elem = engine.introspector.query_label(self.target);
let span = self.span();
let form = self.form.get(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 realize_reference(
self,
engine,
styles,
Counter::new(CounterKey::Page),
numbering.clone(),
supplement,
elem,
);
}
if BibliographyElem::has(engine, self.target) {
if let Ok(elem) = elem {
bail!(
span,
"label `{}` occurs both in the document and its bibliography",
self.target.repr();
hint: "change either the {}'s label or the \
bibliography key to resolve the ambiguity",
elem.func().name(),
);
}
return Ok(to_citation(self, engine, styles)?.pack().spanned(span));
}
let elem = elem.at(span)?;
if let Some(footnote) = elem.to_packed::<FootnoteElem>() {
return Ok(footnote.into_ref(self.target).pack().spanned(span));
}
let elem = elem.clone();
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 {} numbering with `#set {}(numbering: \"1.\")`",
elem.func().name(),
if elem.func() == EquationElem::ELEM {
"math.equation"
} else {
elem.func().name()
}
))
.at(span)?;
realize_reference(
self,
engine,
styles,
refable.counter(),
numbering.clone(),
refable.supplement(),
elem,
)
}
}
fn realize_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.get_ref(styles) {
Smart::Auto => supplement,
Smart::Custom(None) => Content::empty(),
Smart::Custom(Some(supplement)) => supplement.resolve(engine, styles, [elem])?,
};
let alt = {
let supplement = supplement.plain_text();
let numbering = numbers.plain_text();
eco_format!("{supplement} {numbering}",)
};
let mut content = numbers;
if !supplement.is_empty() {
content = supplement + TextElem::packed("\u{a0}") + content;
}
content = content.spanned(reference.span());
Ok(DirectLinkElem::new(loc, content, Some(alt)).pack())
}
fn to_citation(
reference: &Packed<RefElem>,
engine: &mut Engine,
styles: StyleChain,
) -> SourceResult<Packed<CiteElem>> {
let mut elem = Packed::new(CiteElem::new(reference.target).with_supplement(
match reference.supplement.get_cloned(styles) {
Smart::Custom(Some(Supplement::Content(content))) => Some(content),
_ => None,
},
));
elem.synthesize(engine, styles)?;
Ok(elem)
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Supplement {
Content(Content),
Func(Func),
}
impl Supplement {
pub fn resolve<T: IntoValue>(
&self,
engine: &mut Engine,
styles: StyleChain,
args: impl IntoIterator<Item = T>,
) -> SourceResult<Content> {
Ok(match self {
Supplement::Content(content) => content.clone(),
Supplement::Func(func) => func
.call(engine, Context::new(None, Some(styles)).track(), 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),
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum RefForm {
#[default]
Normal,
Page,
}
pub trait Refable {
fn supplement(&self) -> Content;
fn counter(&self) -> Counter;
fn numbering(&self) -> Option<&Numbering>;
}