use once_cell::unsync::Lazy;
use smallvec::SmallVec;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Resolve,
Smart, StyleChain, Value,
};
use crate::introspection::Locator;
use crate::layout::{
layout_fragment, layout_frame, Abs, Axes, Corners, Em, Fr, Fragment, Frame,
FrameKind, Length, Region, Regions, Rel, Sides, Size, Spacing,
};
use crate::utils::Numeric;
use crate::visualize::{clip_rect, Paint, Stroke};
#[elem]
pub struct BoxElem {
pub width: Sizing,
pub height: Smart<Rel<Length>>,
#[resolve]
pub baseline: Rel<Length>,
pub fill: Option<Paint>,
#[resolve]
#[fold]
pub stroke: Sides<Option<Option<Stroke>>>,
#[resolve]
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub inset: Sides<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[default(false)]
pub clip: bool,
#[positional]
#[borrowed]
pub body: Option<Content>,
}
impl Packed<BoxElem> {
#[typst_macros::time(name = "box", span = self.span())]
pub fn layout(
&self,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Frame> {
let width = self.width(styles);
let height = self.height(styles);
let inset = self.inset(styles).unwrap_or_default();
let pod = unbreakable_pod(&width, &height.into(), &inset, styles, region);
let mut frame = match self.body(styles) {
None => Frame::hard(Size::zero()),
Some(body) => layout_frame(engine, body, locator, styles, pod)?
.with_kind(FrameKind::Hard),
};
frame.set_size(pod.expand.select(pod.size, frame.size()));
if !inset.is_zero() {
crate::layout::grow(&mut frame, &inset);
}
let fill = self.fill(styles);
let stroke = self
.stroke(styles)
.unwrap_or_default()
.map(|s| s.map(Stroke::unwrap_or_default));
let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
if self.clip(styles) {
let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
frame.clip(clip_rect(size, &radius, &stroke));
}
if fill.is_some() || stroke.iter().any(Option::is_some) {
frame.fill_and_stroke(fill, &stroke, &outset, &radius, self.span());
}
if let Some(label) = self.label() {
frame.label(label);
}
let shift = self.baseline(styles).relative_to(frame.height());
if !shift.is_zero() {
frame.set_baseline(frame.baseline() - shift);
}
Ok(frame)
}
}
#[elem(Construct)]
pub struct InlineElem {
#[required]
#[internal]
body: callbacks::InlineCallback,
}
impl Construct for InlineElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually");
}
}
impl InlineElem {
#[allow(clippy::type_complexity)]
pub fn layouter<T: NativeElement>(
captured: Packed<T>,
callback: fn(
content: &Packed<T>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>>,
) -> Self {
Self::new(callbacks::InlineCallback::new(captured, callback))
}
}
impl Packed<InlineElem> {
pub fn layout(
&self,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>> {
self.body().call(engine, locator, styles, region)
}
}
#[derive(Debug, Clone)]
pub enum InlineItem {
Space(Abs, bool),
Frame(Frame),
}
#[elem]
pub struct BlockElem {
pub width: Smart<Rel<Length>>,
pub height: Sizing,
#[default(true)]
pub breakable: bool,
pub fill: Option<Paint>,
#[resolve]
#[fold]
pub stroke: Sides<Option<Option<Stroke>>>,
#[resolve]
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub inset: Sides<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[external]
#[default(Em::new(1.2).into())]
pub spacing: Spacing,
#[parse(
let spacing = args.named("spacing")?;
args.named("above")?.or(spacing)
)]
pub above: Smart<Spacing>,
#[parse(args.named("below")?.or(spacing))]
pub below: Smart<Spacing>,
#[default(false)]
pub clip: bool,
#[default(false)]
pub sticky: bool,
#[positional]
#[borrowed]
pub body: Option<BlockBody>,
}
impl BlockElem {
pub fn single_layouter<T: NativeElement>(
captured: Packed<T>,
f: fn(
content: &Packed<T>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame>,
) -> Self {
Self::new()
.with_breakable(false)
.with_body(Some(BlockBody::SingleLayouter(
callbacks::BlockSingleCallback::new(captured, f),
)))
}
pub fn multi_layouter<T: NativeElement>(
captured: Packed<T>,
f: fn(
content: &Packed<T>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment>,
) -> Self {
Self::new().with_body(Some(BlockBody::MultiLayouter(
callbacks::BlockMultiCallback::new(captured, f),
)))
}
}
impl Packed<BlockElem> {
#[typst_macros::time(name = "block", span = self.span())]
pub fn layout_single(
&self,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
let width = self.width(styles);
let height = self.height(styles);
let inset = self.inset(styles).unwrap_or_default();
let pod = unbreakable_pod(&width.into(), &height, &inset, styles, region.size);
let body = self.body(styles);
let mut frame = match body {
None => Frame::hard(Size::zero()),
Some(BlockBody::Content(body)) => {
layout_frame(engine, body, locator.relayout(), styles, pod)?
}
Some(BlockBody::SingleLayouter(callback)) => {
callback.call(engine, locator, styles, pod)?
}
Some(BlockBody::MultiLayouter(callback)) => {
let expand = (pod.expand | region.expand) & pod.size.map(Abs::is_finite);
let pod = Region { expand, ..pod };
callback.call(engine, locator, styles, pod.into())?.into_frame()
}
};
if matches!(body, None | Some(BlockBody::Content(_))) {
frame.set_kind(FrameKind::Hard);
}
frame.set_size(pod.expand.select(pod.size, frame.size()));
if !inset.is_zero() {
crate::layout::grow(&mut frame, &inset);
}
let fill = self.fill(styles);
let stroke = self
.stroke(styles)
.unwrap_or_default()
.map(|s| s.map(Stroke::unwrap_or_default));
let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
if self.clip(styles) {
let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
frame.clip(clip_rect(size, &radius, &stroke));
}
if fill.is_some() || stroke.iter().any(Option::is_some) {
frame.fill_and_stroke(fill, &stroke, &outset, &radius, self.span());
}
if let Some(label) = self.label() {
frame.label(label);
}
Ok(frame)
}
}
impl Packed<BlockElem> {
#[typst_macros::time(name = "block", span = self.span())]
pub fn layout_multiple(
&self,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let width = self.width(styles);
let height = self.height(styles);
let inset = self.inset(styles).unwrap_or_default();
let mut buf = SmallVec::<[Abs; 2]>::new();
let pod =
breakable_pod(&width.into(), &height, &inset, styles, regions, &mut buf);
let body = self.body(styles);
let mut fragment = match body {
None => {
let mut frames = vec![];
frames.push(Frame::hard(Size::zero()));
if pod.expand.y {
let mut iter = pod;
while !iter.backlog.is_empty() {
frames.push(Frame::hard(Size::zero()));
iter.next();
}
}
Fragment::frames(frames)
}
Some(BlockBody::Content(body)) => {
let mut fragment =
layout_fragment(engine, body, locator.relayout(), styles, pod)?;
if !pod.expand.x
&& fragment
.as_slice()
.windows(2)
.any(|w| !w[0].width().approx_eq(w[1].width()))
{
let max_width = fragment
.iter()
.map(|frame| frame.width())
.max()
.unwrap_or_default();
let pod = Regions {
size: Size::new(max_width, pod.size.y),
expand: Axes::new(true, pod.expand.y),
..pod
};
fragment = layout_fragment(engine, body, locator, styles, pod)?;
}
fragment
}
Some(BlockBody::SingleLayouter(callback)) => {
let pod = Region::new(pod.base(), pod.expand);
callback.call(engine, locator, styles, pod).map(Fragment::frame)?
}
Some(BlockBody::MultiLayouter(callback)) => {
let expand = (pod.expand | regions.expand) & pod.size.map(Abs::is_finite);
let pod = Regions { expand, ..pod };
callback.call(engine, locator, styles, pod)?
}
};
let fill = self.fill(styles);
let stroke = self
.stroke(styles)
.unwrap_or_default()
.map(|s| s.map(Stroke::unwrap_or_default));
let outset = Lazy::new(|| self.outset(styles).unwrap_or_default());
let radius = Lazy::new(|| self.radius(styles).unwrap_or_default());
let clip = self.clip(styles);
let has_fill_or_stroke = fill.is_some() || stroke.iter().any(Option::is_some);
let has_inset = !inset.is_zero();
let is_explicit = matches!(body, None | Some(BlockBody::Content(_)));
let mut skip_first = false;
if let [first, rest @ ..] = fragment.as_slice() {
skip_first = has_fill_or_stroke
&& first.is_empty()
&& rest.iter().any(|frame| !frame.is_empty());
}
for (i, (frame, region)) in fragment.iter_mut().zip(pod.iter()).enumerate() {
if is_explicit {
frame.set_kind(FrameKind::Hard);
}
frame.set_size(pod.expand.select(region, frame.size()));
if has_inset {
crate::layout::grow(frame, &inset);
}
if clip {
let size = frame.size() + outset.relative_to(frame.size()).sum_by_axis();
frame.clip(clip_rect(size, &radius, &stroke));
}
if has_fill_or_stroke && (i > 0 || !skip_first) {
frame.fill_and_stroke(
fill.clone(),
&stroke,
&outset,
&radius,
self.span(),
);
}
}
if let Some(label) = self.label() {
for frame in fragment.iter_mut() {
frame.label(label);
}
}
Ok(fragment)
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum BlockBody {
Content(Content),
SingleLayouter(callbacks::BlockSingleCallback),
MultiLayouter(callbacks::BlockMultiCallback),
}
impl Default for BlockBody {
fn default() -> Self {
Self::Content(Content::default())
}
}
cast! {
BlockBody,
self => match self {
Self::Content(content) => content.into_value(),
_ => Value::Auto,
},
v: Content => Self::Content(v),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Sizing {
Auto,
Rel(Rel),
Fr(Fr),
}
impl Sizing {
pub fn is_auto(self) -> bool {
matches!(self, Self::Auto)
}
pub fn is_fractional(self) -> bool {
matches!(self, Self::Fr(_))
}
}
impl Default for Sizing {
fn default() -> Self {
Self::Auto
}
}
impl From<Smart<Rel>> for Sizing {
fn from(smart: Smart<Rel>) -> Self {
match smart {
Smart::Auto => Self::Auto,
Smart::Custom(rel) => Self::Rel(rel),
}
}
}
impl<T: Into<Spacing>> From<T> for Sizing {
fn from(spacing: T) -> Self {
match spacing.into() {
Spacing::Rel(rel) => Self::Rel(rel),
Spacing::Fr(fr) => Self::Fr(fr),
}
}
}
cast! {
Sizing,
self => match self {
Self::Auto => Value::Auto,
Self::Rel(rel) => rel.into_value(),
Self::Fr(fr) => fr.into_value(),
},
_: AutoValue => Self::Auto,
v: Rel<Length> => Self::Rel(v),
v: Fr => Self::Fr(v),
}
fn unbreakable_pod(
width: &Sizing,
height: &Sizing,
inset: &Sides<Rel<Abs>>,
styles: StyleChain,
base: Size,
) -> Region {
let mut size = Size::new(
match width {
Sizing::Auto | Sizing::Fr(_) => base.x,
Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.x),
},
match height {
Sizing::Auto | Sizing::Fr(_) => base.y,
Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.y),
},
);
if !inset.is_zero() {
size = crate::layout::shrink(size, inset);
}
let expand = Axes::new(
*width != Sizing::Auto && size.x.is_finite(),
*height != Sizing::Auto && size.y.is_finite(),
);
Region::new(size, expand)
}
fn breakable_pod<'a>(
width: &Sizing,
height: &Sizing,
inset: &Sides<Rel<Abs>>,
styles: StyleChain,
regions: Regions,
buf: &'a mut SmallVec<[Abs; 2]>,
) -> Regions<'a> {
let base = regions.base();
let first;
let full;
let backlog: &mut [Abs];
let last;
match height {
Sizing::Auto | Sizing::Fr(_) => {
first = regions.size.y;
full = regions.full;
buf.extend_from_slice(regions.backlog);
backlog = buf;
last = regions.last;
}
Sizing::Rel(rel) => {
let resolved = rel.resolve(styles).relative_to(base.y);
full = resolved;
(first, backlog) = distribute(resolved, regions, buf);
last = None;
}
};
let mut size = Size::new(
match width {
Sizing::Auto | Sizing::Fr(_) => regions.size.x,
Sizing::Rel(rel) => rel.resolve(styles).relative_to(base.x),
},
first,
);
let (mut full, mut last) = (full, last);
if !inset.is_zero() {
crate::layout::shrink_multiple(&mut size, &mut full, backlog, &mut last, inset);
}
let expand = Axes::new(
*width != Sizing::Auto && size.x.is_finite(),
*height != Sizing::Auto && size.y.is_finite(),
);
Regions { size, full, backlog, last, expand }
}
fn distribute<'a>(
height: Abs,
mut regions: Regions,
buf: &'a mut SmallVec<[Abs; 2]>,
) -> (Abs, &'a mut [Abs]) {
let mut remaining = height;
loop {
let limited = regions.size.y.clamp(Abs::zero(), remaining);
buf.push(limited);
remaining -= limited;
if remaining.approx_empty()
|| !regions.may_break()
|| (!regions.may_progress() && limited.approx_empty())
{
break;
}
regions.next();
}
if !remaining.approx_empty() {
if let Some(last) = buf.last_mut() {
*last += remaining;
}
}
(buf[0], &mut buf[1..])
}
mod callbacks {
use super::*;
macro_rules! callback {
($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => {
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct $name {
captured: Content,
f: fn(&Content, $($param_ty),*) -> $ret,
}
impl $name {
pub fn new<T: NativeElement>(
captured: Packed<T>,
f: fn(&Packed<T>, $($param_ty),*) -> $ret,
) -> Self {
Self {
captured: captured.pack(),
#[allow(clippy::missing_transmute_annotations)]
f: unsafe { std::mem::transmute(f) },
}
}
pub fn call(&self, $($param: $param_ty),*) -> $ret {
(self.f)(&self.captured, $($param),*)
}
}
};
}
callback! {
InlineCallback = (
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>>
}
callback! {
BlockSingleCallback = (
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame>
}
callback! {
BlockMultiCallback = (
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment>
}
}