use std::num::NonZeroUsize;
use ecow::EcoString;
use typst_utils::NonZeroExt;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
Content, NativeElement, Packed, ShowSet, Smart, StyleChain, Styles, Synthesize, elem,
};
use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Tagged};
use crate::layout::{BlockElem, Em, Length};
use crate::model::{Numbering, Outlinable, Refable, Supplement};
use crate::text::{FontWeight, LocalName, TextElem, TextSize};
#[elem(Locatable, Tagged, Synthesize, Count, ShowSet, LocalName, Refable, Outlinable)]
pub struct HeadingElem {
pub level: Smart<NonZeroUsize>,
#[default(NonZeroUsize::ONE)]
pub depth: NonZeroUsize,
#[default(0)]
pub offset: usize,
pub numbering: Option<Numbering>,
#[internal]
#[synthesized]
pub numbers: EcoString,
pub supplement: Smart<Option<Supplement>>,
#[default(true)]
pub outlined: bool,
#[default(Smart::Auto)]
pub bookmarked: Smart<bool>,
#[default(Smart::Auto)]
pub hanging_indent: Smart<Length>,
#[required]
pub body: Content,
}
impl HeadingElem {
pub fn resolve_level(&self, styles: StyleChain) -> NonZeroUsize {
self.level.get(styles).unwrap_or_else(|| {
NonZeroUsize::new(self.offset.get(styles) + self.depth.get(styles).get())
.expect("overflow to 0 on NoneZeroUsize + usize")
})
}
}
impl Synthesize for Packed<HeadingElem> {
fn synthesize(
&mut self,
engine: &mut Engine,
styles: StyleChain,
) -> SourceResult<()> {
let supplement = match self.supplement.get_ref(styles) {
Smart::Auto => TextElem::packed(Self::local_name_in(styles)),
Smart::Custom(None) => Content::empty(),
Smart::Custom(Some(supplement)) => {
supplement.resolve(engine, styles, [self.clone().pack()])?
}
};
if let Some((numbering, location)) =
self.numbering.get_ref(styles).as_ref().zip(self.location())
&& let Ok(numbers) = self.counter().display_at_loc(
engine,
location,
styles,
numbering,
)
{
self.numbers = Some(numbers.plain_text());
}
let elem = self.as_mut();
elem.level.set(Smart::Custom(elem.resolve_level(styles)));
elem.supplement
.set(Smart::Custom(Some(Supplement::Content(supplement))));
Ok(())
}
}
impl ShowSet for Packed<HeadingElem> {
fn show_set(&self, styles: StyleChain) -> Styles {
let level = self.resolve_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 out = Styles::new();
out.set(TextElem::size, TextSize(size.into()));
out.set(TextElem::weight, FontWeight::BOLD);
out.set(BlockElem::above, Smart::Custom(above.into()));
out.set(BlockElem::below, Smart::Custom(below.into()));
out.set(BlockElem::sticky, true);
out
}
}
impl Count for Packed<HeadingElem> {
fn update(&self) -> Option<CounterUpdate> {
self.numbering
.get_ref(StyleChain::default())
.is_some()
.then(|| CounterUpdate::Step(self.resolve_level(StyleChain::default())))
}
}
impl Refable for Packed<HeadingElem> {
fn supplement(&self) -> Content {
match self.supplement.get_cloned(StyleChain::default()) {
Smart::Custom(Some(Supplement::Content(content))) => content,
_ => Content::empty(),
}
}
fn counter(&self) -> Counter {
Counter::of(HeadingElem::ELEM)
}
fn numbering(&self) -> Option<&Numbering> {
self.numbering.get_ref(StyleChain::default()).as_ref()
}
}
impl Outlinable for Packed<HeadingElem> {
fn outlined(&self) -> bool {
self.outlined.get(StyleChain::default())
}
fn level(&self) -> NonZeroUsize {
self.resolve_level(StyleChain::default())
}
fn prefix(&self, numbers: Content) -> Content {
numbers
}
fn body(&self) -> Content {
self.body.clone()
}
}
impl LocalName for Packed<HeadingElem> {
const KEY: &'static str = "heading";
}