use std::str::FromStr;
use comemo::Track;
use smallvec::{smallvec, SmallVec};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, Context, NativeElement, Packed, Show, Smart,
StyleChain, Styles,
};
use crate::introspection::Locator;
use crate::layout::{
Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment,
Length, Regions, Sizing, VAlignment, VElem,
};
use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem};
use crate::text::TextElem;
#[elem(scope, title = "Numbered List", Show)]
pub struct EnumElem {
#[default(true)]
pub tight: bool,
#[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))]
#[borrowed]
pub numbering: Numbering,
#[default(1)]
pub start: usize,
#[default(false)]
pub full: bool,
#[resolve]
pub indent: Length,
#[resolve]
#[default(Em::new(0.5).into())]
pub body_indent: Length,
pub spacing: Smart<Length>,
#[default(HAlignment::End + VAlignment::Top)]
pub number_align: Alignment,
#[variadic]
pub children: Vec<Packed<EnumItem>>,
#[internal]
#[fold]
#[ghost]
parents: SmallVec<[usize; 4]>,
}
#[scope]
impl EnumElem {
#[elem]
type EnumItem;
}
impl Show for Packed<EnumElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut realized = BlockElem::multi_layouter(self.clone(), layout_enum)
.pack()
.spanned(self.span());
if self.tight(styles) {
let leading = ParElem::leading_in(styles);
let spacing =
VElem::new(leading.into()).with_weak(true).with_attach(true).pack();
realized = spacing + realized;
}
Ok(realized)
}
}
#[typst_macros::time(span = elem.span())]
fn layout_enum(
elem: &Packed<EnumElem>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let numbering = elem.numbering(styles);
let indent = elem.indent(styles);
let body_indent = elem.body_indent(styles);
let gutter = elem.spacing(styles).unwrap_or_else(|| {
if elem.tight(styles) {
ParElem::leading_in(styles).into()
} else {
ParElem::spacing_in(styles).into()
}
});
let mut cells = vec![];
let mut locator = locator.split();
let mut number = elem.start(styles);
let mut parents = EnumElem::parents_in(styles);
let full = elem.full(styles);
let number_align = elem.number_align(styles);
for item in elem.children() {
number = item.number(styles).unwrap_or(number);
let context = Context::new(None, Some(styles));
let resolved = if full {
parents.push(number);
let content = numbering.apply(engine, context.track(), &parents)?.display();
parents.pop();
content
} else {
match numbering {
Numbering::Pattern(pattern) => {
TextElem::packed(pattern.apply_kth(parents.len(), number))
}
other => other.apply(engine, context.track(), &[number])?.display(),
}
};
let resolved =
resolved.aligned(number_align).styled(TextElem::set_overhang(false));
cells.push(Cell::new(Content::empty(), locator.next(&())));
cells.push(Cell::new(resolved, locator.next(&())));
cells.push(Cell::new(Content::empty(), locator.next(&())));
cells.push(Cell::new(
item.body.clone().styled(EnumElem::set_parents(smallvec![number])),
locator.next(&item.body.span()),
));
number = number.saturating_add(1);
}
let grid = CellGrid::new(
Axes::with_x(&[
Sizing::Rel(indent.into()),
Sizing::Auto,
Sizing::Rel(body_indent.into()),
Sizing::Auto,
]),
Axes::with_y(&[gutter.into()]),
cells,
);
let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
layouter.layout(engine)
}
#[elem(name = "item", title = "Numbered List Item")]
pub struct EnumItem {
#[positional]
pub number: Option<usize>,
#[required]
pub body: Content,
}
cast! {
EnumItem,
array: Array => {
let mut iter = array.into_iter();
let (number, body) = match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => (a.cast()?, b.cast()?),
_ => bail!("array must contain exactly two entries"),
};
Self::new(body).with_number(number)
},
v: Content => v.unpack::<Self>().unwrap_or_else(Self::new),
}
impl ListLike for EnumElem {
type Item = EnumItem;
fn create(children: Vec<Packed<Self::Item>>, tight: bool) -> Self {
Self::new(children).with_tight(tight)
}
}
impl ListItemLike for EnumItem {
fn styled(mut item: Packed<Self>, styles: Styles) -> Packed<Self> {
item.body.style_in_place(styles);
item
}
}