use std::str::FromStr;
use ecow::eco_format;
use smallvec::SmallVec;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
Styles, TargetElem,
};
use crate::html::{attr, tag, HtmlElem};
use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
use crate::model::{
ListItemLike, ListLike, Numbering, NumberingPattern, ParElem, ParbreakElem,
};
#[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,
pub start: Smart<usize>,
#[default(false)]
pub full: bool,
#[default(false)]
pub reversed: 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]
pub parents: SmallVec<[usize; 4]>,
}
#[scope]
impl EnumElem {
#[elem]
type EnumItem;
}
impl Show for Packed<EnumElem> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let tight = self.tight(styles);
if TargetElem::target_in(styles).is_html() {
let mut elem = HtmlElem::new(tag::ol);
if self.reversed(styles) {
elem = elem.with_attr(attr::reversed, "reversed");
}
if let Some(n) = self.start(styles).custom() {
elem = elem.with_attr(attr::start, eco_format!("{n}"));
}
let body = Content::sequence(self.children.iter().map(|item| {
let mut li = HtmlElem::new(tag::li);
if let Some(nr) = item.number(styles) {
li = li.with_attr(attr::value, eco_format!("{nr}"));
}
let mut body = item.body.clone();
if !tight {
body += ParbreakElem::shared();
}
li.with_body(Some(body)).pack().spanned(item.span())
}));
return Ok(elem.with_body(Some(body)).pack().spanned(self.span()));
}
let mut realized =
BlockElem::multi_layouter(self.clone(), engine.routines.layout_enum)
.pack()
.spanned(self.span());
if tight {
let leading = ParElem::leading_in(styles);
let spacing =
VElem::new(leading.into()).with_weak(true).with_attach(true).pack();
realized = spacing + realized;
}
Ok(realized)
}
}
#[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
}
}