pub mod resolve;
use std::num::{NonZeroU32, NonZeroUsize};
use std::sync::Arc;
use comemo::Track;
use smallvec::{SmallVec, smallvec};
use typst_utils::NonZeroExt;
use crate::diag::{At, HintedStrResult, HintedString, SourceResult, bail};
use crate::engine::Engine;
use crate::foundations::{
Array, CastInfo, Content, Context, Fold, FromValue, Func, IntoValue, Packed, Reflect,
Resolve, Smart, StyleChain, Synthesize, Value, cast, elem, scope,
};
use crate::introspection::Tagged;
use crate::layout::resolve::{CellGrid, grid_to_cellgrid};
use crate::layout::{
Alignment, Length, OuterHAlignment, OuterVAlignment, Rel, Sides, Sizing,
};
use crate::model::{TableCell, TableFooter, TableHLine, TableHeader, TableVLine};
use crate::visualize::{Paint, Stroke};
#[elem(scope, Synthesize, Tagged)]
pub struct GridElem {
pub columns: TrackSizings,
pub rows: TrackSizings,
#[external]
pub gutter: TrackSizings,
#[parse(
let gutter = args.named("gutter")?;
args.named("column-gutter")?.or_else(|| gutter.clone())
)]
pub column_gutter: TrackSizings,
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
pub row_gutter: TrackSizings,
#[fold]
pub inset: Celled<Sides<Option<Rel<Length>>>>,
pub align: Celled<Smart<Alignment>>,
pub fill: Celled<Option<Paint>>,
#[fold]
pub stroke: Celled<Sides<Option<Option<Arc<Stroke>>>>>,
#[internal]
#[synthesized]
pub grid: Arc<CellGrid>,
#[variadic]
pub children: Vec<GridChild>,
}
#[scope]
impl GridElem {
#[elem]
type GridCell;
#[elem]
type GridHLine;
#[elem]
type GridVLine;
#[elem]
type GridHeader;
#[elem]
type GridFooter;
}
impl Synthesize for Packed<GridElem> {
fn synthesize(
&mut self,
engine: &mut Engine,
styles: StyleChain,
) -> SourceResult<()> {
let grid = grid_to_cellgrid(self, engine, styles)?;
self.grid = Some(Arc::new(grid));
Ok(())
}
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
cast! {
TrackSizings,
self => self.0.into_value(),
sizing: Sizing => Self(smallvec![sizing]),
count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]),
values: Array => Self(values.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum GridChild {
Header(Packed<GridHeader>),
Footer(Packed<GridFooter>),
Item(GridItem),
}
cast! {
GridChild,
self => match self {
Self::Header(header) => header.into_value(),
Self::Footer(footer) => footer.into_value(),
Self::Item(item) => item.into_value(),
},
v: Content => {
v.try_into()?
},
}
impl TryFrom<Content> for GridChild {
type Error = HintedString;
fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<TableHeader>() {
bail!(
"cannot use `table.header` as a grid header";
hint: "use `grid.header` instead"
)
}
if value.is::<TableFooter>() {
bail!(
"cannot use `table.footer` as a grid footer";
hint: "use `grid.footer` instead"
)
}
value
.into_packed::<GridHeader>()
.map(Self::Header)
.or_else(|value| value.into_packed::<GridFooter>().map(Self::Footer))
.or_else(|value| GridItem::try_from(value).map(Self::Item))
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum GridItem {
HLine(Packed<GridHLine>),
VLine(Packed<GridVLine>),
Cell(Packed<GridCell>),
}
cast! {
GridItem,
self => match self {
Self::HLine(hline) => hline.into_value(),
Self::VLine(vline) => vline.into_value(),
Self::Cell(cell) => cell.into_value(),
},
v: Content => {
v.try_into()?
}
}
impl TryFrom<Content> for GridItem {
type Error = HintedString;
fn try_from(value: Content) -> HintedStrResult<Self> {
if value.is::<GridHeader>() {
bail!("cannot place a grid header within another header or footer");
}
if value.is::<TableHeader>() {
bail!("cannot place a table header within another header or footer");
}
if value.is::<GridFooter>() {
bail!("cannot place a grid footer within another footer or header");
}
if value.is::<TableFooter>() {
bail!("cannot place a table footer within another footer or header");
}
if value.is::<TableCell>() {
bail!(
"cannot use `table.cell` as a grid cell";
hint: "use `grid.cell` instead"
);
}
if value.is::<TableHLine>() {
bail!(
"cannot use `table.hline` as a grid line";
hint: "use `grid.hline` instead"
);
}
if value.is::<TableVLine>() {
bail!(
"cannot use `table.vline` as a grid line";
hint: "use `grid.vline` instead"
);
}
Ok(value
.into_packed::<GridHLine>()
.map(Self::HLine)
.or_else(|value| value.into_packed::<GridVLine>().map(Self::VLine))
.or_else(|value| value.into_packed::<GridCell>().map(Self::Cell))
.unwrap_or_else(|value| {
let span = value.span();
Self::Cell(Packed::new(GridCell::new(value)).spanned(span))
}))
}
}
#[elem(name = "header", title = "Grid Header")]
pub struct GridHeader {
#[default(true)]
pub repeat: bool,
#[default(NonZeroU32::ONE)]
pub level: NonZeroU32,
#[variadic]
pub children: Vec<GridItem>,
}
#[elem(name = "footer", title = "Grid Footer")]
pub struct GridFooter {
#[default(true)]
pub repeat: bool,
#[variadic]
pub children: Vec<GridItem>,
}
#[elem(name = "hline", title = "Grid Horizontal Line")]
pub struct GridHLine {
pub y: Smart<usize>,
pub start: usize,
pub end: Option<NonZeroUsize>,
#[fold]
#[default(Some(Arc::new(Stroke::default())))]
pub stroke: Option<Arc<Stroke>>,
#[default(OuterVAlignment::Top)]
pub position: OuterVAlignment,
}
#[elem(name = "vline", title = "Grid Vertical Line")]
pub struct GridVLine {
pub x: Smart<usize>,
pub start: usize,
pub end: Option<NonZeroUsize>,
#[fold]
#[default(Some(Arc::new(Stroke::default())))]
pub stroke: Option<Arc<Stroke>>,
#[default(OuterHAlignment::Start)]
pub position: OuterHAlignment,
}
#[elem(name = "cell", title = "Grid Cell")]
pub struct GridCell {
#[required]
pub body: Content,
pub x: Smart<usize>,
pub y: Smart<usize>,
#[default(NonZeroUsize::ONE)]
pub colspan: NonZeroUsize,
#[default(NonZeroUsize::ONE)]
pub rowspan: NonZeroUsize,
pub inset: Smart<Sides<Option<Rel<Length>>>>,
pub align: Smart<Alignment>,
pub fill: Smart<Option<Paint>>,
#[fold]
pub stroke: Sides<Option<Option<Arc<Stroke>>>>,
#[internal]
#[parse(Some(false))]
pub is_repeated: bool,
pub breakable: Smart<bool>,
}
cast! {
GridCell,
v: Content => v.into(),
}
impl Default for Packed<GridCell> {
fn default() -> Self {
Packed::new(
GridCell::new(Content::default())
.with_colspan(NonZeroUsize::ONE)
.with_rowspan(NonZeroUsize::ONE),
)
}
}
impl From<Content> for GridCell {
fn from(value: Content) -> Self {
#[allow(clippy::unwrap_or_default)]
value.unpack::<Self>().unwrap_or_else(Self::new)
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Celled<T> {
Value(T),
Func(Func),
Array(Vec<T>),
}
impl<T: Default + Clone + FromValue> Celled<T> {
pub fn resolve(
&self,
engine: &mut Engine,
styles: StyleChain,
x: usize,
y: usize,
) -> SourceResult<T> {
Ok(match self {
Self::Value(value) => value.clone(),
Self::Func(func) => func
.call(engine, Context::new(None, Some(styles)).track(), [x, y])?
.cast()
.at(func.span())?,
Self::Array(array) => x
.checked_rem(array.len())
.and_then(|i| array.get(i))
.cloned()
.unwrap_or_default(),
})
}
}
impl<T: Default> Default for Celled<T> {
fn default() -> Self {
Self::Value(T::default())
}
}
impl<T: Reflect> Reflect for Celled<T> {
fn input() -> CastInfo {
T::input() + Array::input() + Func::input()
}
fn output() -> CastInfo {
T::output() + Array::output() + Func::output()
}
fn castable(value: &Value) -> bool {
Array::castable(value) || Func::castable(value) || T::castable(value)
}
}
impl<T: IntoValue> IntoValue for Celled<T> {
fn into_value(self) -> Value {
match self {
Self::Value(value) => value.into_value(),
Self::Func(func) => func.into_value(),
Self::Array(arr) => arr.into_value(),
}
}
}
impl<T: FromValue> FromValue for Celled<T> {
fn from_value(value: Value) -> HintedStrResult<Self> {
match value {
Value::Func(v) => Ok(Self::Func(v)),
Value::Array(array) => Ok(Self::Array(
array.into_iter().map(T::from_value).collect::<HintedStrResult<_>>()?,
)),
v if T::castable(&v) => Ok(Self::Value(T::from_value(v)?)),
v => Err(Self::error(&v)),
}
}
}
impl<T: Fold> Fold for Celled<T> {
fn fold(self, outer: Self) -> Self {
match (self, outer) {
(Self::Value(inner), Self::Value(outer)) => Self::Value(inner.fold(outer)),
(self_, _) => self_,
}
}
}
impl<T: Resolve> Resolve for Celled<T> {
type Output = ResolvedCelled<T>;
fn resolve(self, styles: StyleChain) -> Self::Output {
match self {
Self::Value(value) => ResolvedCelled(Celled::Value(value.resolve(styles))),
Self::Func(func) => ResolvedCelled(Celled::Func(func)),
Self::Array(values) => ResolvedCelled(Celled::Array(
values.into_iter().map(|value| value.resolve(styles)).collect(),
)),
}
}
}
#[derive(Default, Clone)]
pub struct ResolvedCelled<T: Resolve>(Celled<T::Output>);
impl<T> ResolvedCelled<T>
where
T: FromValue + Resolve,
<T as Resolve>::Output: Default + Clone,
{
pub fn resolve(
&self,
engine: &mut Engine,
styles: StyleChain,
x: usize,
y: usize,
) -> SourceResult<T::Output> {
Ok(match &self.0 {
Celled::Value(value) => value.clone(),
Celled::Func(func) => func
.call(engine, Context::new(None, Some(styles)).track(), [x, y])?
.cast::<T>()
.at(func.span())?
.resolve(styles),
Celled::Array(array) => x
.checked_rem(array.len())
.and_then(|i| array.get(i))
.cloned()
.unwrap_or_default(),
})
}
}