use std::hash::Hash;
use std::sync::Arc;
use ecow::{eco_format, EcoString};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, repr, scope, ty, Content, Smart, StyleChain};
use crate::introspection::Locator;
use crate::layout::{layout_frame, Abs, Axes, Frame, Length, Region, Size};
use crate::syntax::{Span, Spanned};
use crate::utils::{LazyHash, Numeric};
use crate::visualize::RelativeTo;
use crate::World;
#[ty(scope, cast)]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Pattern(Arc<Repr>);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct Repr {
frame: LazyHash<Frame>,
size: Size,
spacing: Size,
relative: Smart<RelativeTo>,
}
#[scope]
impl Pattern {
#[func(constructor)]
pub fn construct(
engine: &mut Engine,
span: Span,
#[named]
#[default(Spanned::new(Smart::Auto, Span::detached()))]
size: Spanned<Smart<Axes<Length>>>,
#[named]
#[default(Spanned::new(Axes::splat(Length::zero()), Span::detached()))]
spacing: Spanned<Axes<Length>>,
#[named]
#[default(Smart::Auto)]
relative: Smart<RelativeTo>,
body: Content,
) -> SourceResult<Pattern> {
let size_span = size.span;
if let Smart::Custom(size) = size.v {
if !size.x.em.is_zero() || !size.y.em.is_zero() {
bail!(size_span, "pattern tile size must be absolute");
}
if size.x.is_zero()
|| size.y.is_zero()
|| !size.x.is_finite()
|| !size.y.is_finite()
{
bail!(size_span, "pattern tile size must be non-zero and non-infinite");
}
}
if !spacing.v.x.em.is_zero() || !spacing.v.y.em.is_zero() {
bail!(spacing.span, "pattern tile spacing must be absolute");
}
if !spacing.v.x.is_finite() || !spacing.v.y.is_finite() {
bail!(spacing.span, "pattern tile spacing must be finite");
}
let size = size.v.map(|l| l.map(|a| a.abs));
let region = size.unwrap_or_else(|| Axes::splat(Abs::inf()));
let world = engine.world;
let library = world.library();
let locator = Locator::root();
let styles = StyleChain::new(&library.styles);
let pod = Region::new(region, Axes::splat(false));
let mut frame = layout_frame(engine, &body, locator, styles, pod)?;
if let Smart::Custom(size) = size {
frame.set_size(size);
}
if frame.width().is_zero() || frame.height().is_zero() {
bail!(
span, "pattern tile size must be non-zero";
hint: "try setting the size manually"
);
}
Ok(Self(Arc::new(Repr {
size: frame.size(),
frame: LazyHash::new(frame),
spacing: spacing.v.map(|l| l.abs),
relative,
})))
}
}
impl Pattern {
pub fn with_relative(mut self, relative: RelativeTo) -> Self {
if let Some(this) = Arc::get_mut(&mut self.0) {
this.relative = Smart::Custom(relative);
} else {
self.0 = Arc::new(Repr {
relative: Smart::Custom(relative),
..self.0.as_ref().clone()
});
}
self
}
pub fn frame(&self) -> &Frame {
&self.0.frame
}
pub fn size(&self) -> Size {
self.0.size
}
pub fn spacing(&self) -> Size {
self.0.spacing
}
pub fn relative(&self) -> Smart<RelativeTo> {
self.0.relative
}
pub fn unwrap_relative(&self, on_text: bool) -> RelativeTo {
self.0.relative.unwrap_or_else(|| {
if on_text {
RelativeTo::Parent
} else {
RelativeTo::Self_
}
})
}
}
impl repr::Repr for Pattern {
fn repr(&self) -> EcoString {
let mut out =
eco_format!("pattern(({}, {})", self.0.size.x.repr(), self.0.size.y.repr());
if self.0.spacing.is_zero() {
out.push_str(", spacing: (");
out.push_str(&self.0.spacing.x.repr());
out.push_str(", ");
out.push_str(&self.0.spacing.y.repr());
out.push(')');
}
out.push_str(", ..)");
out
}
}