use std::ops::Add;
use ecow::{eco_format, EcoString};
use crate::diag::{bail, HintedStrResult, SourceResult, StrResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Packed,
Reflect, Repr, Resolve, Show, StyleChain, Value,
};
use crate::layout::{Abs, Axes, Axis, Dir, Side};
use crate::text::TextElem;
#[elem(Show)]
pub struct AlignElem {
#[positional]
#[fold]
#[default]
pub alignment: Alignment,
#[required]
pub body: Content,
}
impl Show for Packed<AlignElem> {
#[typst_macros::time(name = "align", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(self.body.clone().aligned(self.alignment(styles)))
}
}
#[ty(scope)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Alignment {
H(HAlignment),
V(VAlignment),
Both(HAlignment, VAlignment),
}
impl Alignment {
pub const fn x(self) -> Option<HAlignment> {
match self {
Self::H(h) | Self::Both(h, _) => Some(h),
Self::V(_) => None,
}
}
pub const fn y(self) -> Option<VAlignment> {
match self {
Self::V(v) | Self::Both(_, v) => Some(v),
Self::H(_) => None,
}
}
pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
Axes::new(
self.x().unwrap_or_default().fix(text_dir),
self.y().unwrap_or_default().fix(text_dir),
)
}
}
#[scope]
impl Alignment {
pub const START: Self = Alignment::H(HAlignment::Start);
pub const LEFT: Self = Alignment::H(HAlignment::Left);
pub const CENTER: Self = Alignment::H(HAlignment::Center);
pub const RIGHT: Self = Alignment::H(HAlignment::Right);
pub const END: Self = Alignment::H(HAlignment::End);
pub const TOP: Self = Alignment::V(VAlignment::Top);
pub const HORIZON: Self = Alignment::V(VAlignment::Horizon);
pub const BOTTOM: Self = Alignment::V(VAlignment::Bottom);
#[func]
pub const fn axis(self) -> Option<Axis> {
match self {
Self::H(_) => Some(Axis::X),
Self::V(_) => Some(Axis::Y),
Self::Both(..) => None,
}
}
#[func(title = "Inverse")]
pub const fn inv(self) -> Alignment {
match self {
Self::H(h) => Self::H(h.inv()),
Self::V(v) => Self::V(v.inv()),
Self::Both(h, v) => Self::Both(h.inv(), v.inv()),
}
}
}
impl Default for Alignment {
fn default() -> Self {
HAlignment::default() + VAlignment::default()
}
}
impl Add for Alignment {
type Output = StrResult<Self>;
fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(Self::H(h), Self::V(v)) | (Self::V(v), Self::H(h)) => Ok(h + v),
(Self::H(_), Self::H(_)) => bail!("cannot add two horizontal alignments"),
(Self::V(_), Self::V(_)) => bail!("cannot add two vertical alignments"),
(Self::H(_), Self::Both(..)) | (Self::Both(..), Self::H(_)) => {
bail!("cannot add a horizontal and a 2D alignment")
}
(Self::V(_), Self::Both(..)) | (Self::Both(..), Self::V(_)) => {
bail!("cannot add a vertical and a 2D alignment")
}
(Self::Both(..), Self::Both(..)) => {
bail!("cannot add two 2D alignments")
}
}
}
}
impl Repr for Alignment {
fn repr(&self) -> EcoString {
match self {
Self::H(h) => h.repr(),
Self::V(v) => v.repr(),
Self::Both(h, v) => eco_format!("{} + {}", h.repr(), v.repr()),
}
}
}
impl Fold for Alignment {
fn fold(self, outer: Self) -> Self {
match (self, outer) {
(Self::H(h), Self::V(v) | Self::Both(_, v)) => Self::Both(h, v),
(Self::V(v), Self::H(h) | Self::Both(h, _)) => Self::Both(h, v),
_ => self,
}
}
}
impl Resolve for Alignment {
type Output = Axes<FixedAlignment>;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.fix(TextElem::dir_in(styles))
}
}
impl From<Side> for Alignment {
fn from(side: Side) -> Self {
match side {
Side::Left => Self::LEFT,
Side::Top => Self::TOP,
Side::Right => Self::RIGHT,
Side::Bottom => Self::BOTTOM,
}
}
}
pub trait FixAlignment {
fn fix(self, dir: Dir) -> FixedAlignment;
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub enum HAlignment {
#[default]
Start,
Left,
Center,
Right,
End,
}
impl HAlignment {
pub const fn inv(self) -> Self {
match self {
Self::Start => Self::End,
Self::Left => Self::Right,
Self::Center => Self::Center,
Self::Right => Self::Left,
Self::End => Self::Start,
}
}
}
impl FixAlignment for HAlignment {
fn fix(self, dir: Dir) -> FixedAlignment {
match (self, dir.is_positive()) {
(Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
(Self::Left, _) => FixedAlignment::Start,
(Self::Center, _) => FixedAlignment::Center,
(Self::Right, _) => FixedAlignment::End,
(Self::End, true) | (Self::Start, false) => FixedAlignment::End,
}
}
}
impl Repr for HAlignment {
fn repr(&self) -> EcoString {
match self {
Self::Start => "start".into(),
Self::Left => "left".into(),
Self::Center => "center".into(),
Self::Right => "right".into(),
Self::End => "end".into(),
}
}
}
impl Add<VAlignment> for HAlignment {
type Output = Alignment;
fn add(self, rhs: VAlignment) -> Self::Output {
Alignment::Both(self, rhs)
}
}
impl From<HAlignment> for Alignment {
fn from(align: HAlignment) -> Self {
Self::H(align)
}
}
impl TryFrom<Alignment> for HAlignment {
type Error = EcoString;
fn try_from(value: Alignment) -> StrResult<Self> {
match value {
Alignment::H(h) => Ok(h),
v => bail!(
"expected `start`, `left`, `center`, `right`, or `end`, found {}",
v.repr()
),
}
}
}
impl Resolve for HAlignment {
type Output = FixedAlignment;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.fix(TextElem::dir_in(styles))
}
}
cast! {
HAlignment,
self => Alignment::H(self).into_value(),
align: Alignment => Self::try_from(align)?,
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub enum OuterHAlignment {
#[default]
Start,
Left,
Right,
End,
}
impl FixAlignment for OuterHAlignment {
fn fix(self, dir: Dir) -> FixedAlignment {
match (self, dir.is_positive()) {
(Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
(Self::Left, _) => FixedAlignment::Start,
(Self::Right, _) => FixedAlignment::End,
(Self::End, true) | (Self::Start, false) => FixedAlignment::End,
}
}
}
impl Resolve for OuterHAlignment {
type Output = FixedAlignment;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.fix(TextElem::dir_in(styles))
}
}
impl From<OuterHAlignment> for HAlignment {
fn from(value: OuterHAlignment) -> Self {
match value {
OuterHAlignment::Start => Self::Start,
OuterHAlignment::Left => Self::Left,
OuterHAlignment::Right => Self::Right,
OuterHAlignment::End => Self::End,
}
}
}
impl TryFrom<Alignment> for OuterHAlignment {
type Error = EcoString;
fn try_from(value: Alignment) -> StrResult<Self> {
match value {
Alignment::H(HAlignment::Start) => Ok(Self::Start),
Alignment::H(HAlignment::Left) => Ok(Self::Left),
Alignment::H(HAlignment::Right) => Ok(Self::Right),
Alignment::H(HAlignment::End) => Ok(Self::End),
v => bail!("expected `start`, `left`, `right`, or `end`, found {}", v.repr()),
}
}
}
cast! {
OuterHAlignment,
self => HAlignment::from(self).into_value(),
align: Alignment => Self::try_from(align)?,
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum VAlignment {
#[default]
Top,
Horizon,
Bottom,
}
impl VAlignment {
pub const fn inv(self) -> Self {
match self {
Self::Top => Self::Bottom,
Self::Horizon => Self::Horizon,
Self::Bottom => Self::Top,
}
}
pub fn position(self, extent: Abs) -> Abs {
match self {
Self::Top => Abs::zero(),
Self::Horizon => extent / 2.0,
Self::Bottom => extent,
}
}
}
impl FixAlignment for VAlignment {
fn fix(self, _: Dir) -> FixedAlignment {
match self {
Self::Top => FixedAlignment::Start,
Self::Horizon => FixedAlignment::Center,
Self::Bottom => FixedAlignment::End,
}
}
}
impl Repr for VAlignment {
fn repr(&self) -> EcoString {
match self {
Self::Top => "top".into(),
Self::Horizon => "horizon".into(),
Self::Bottom => "bottom".into(),
}
}
}
impl Add<HAlignment> for VAlignment {
type Output = Alignment;
fn add(self, rhs: HAlignment) -> Self::Output {
Alignment::Both(rhs, self)
}
}
impl Resolve for VAlignment {
type Output = FixedAlignment;
fn resolve(self, _: StyleChain) -> Self::Output {
self.fix(Dir::TTB)
}
}
impl From<VAlignment> for Alignment {
fn from(align: VAlignment) -> Self {
Self::V(align)
}
}
impl TryFrom<Alignment> for VAlignment {
type Error = EcoString;
fn try_from(value: Alignment) -> StrResult<Self> {
match value {
Alignment::V(v) => Ok(v),
v => bail!("expected `top`, `horizon`, or `bottom`, found {}", v.repr()),
}
}
}
cast! {
VAlignment,
self => Alignment::V(self).into_value(),
align: Alignment => Self::try_from(align)?,
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum OuterVAlignment {
#[default]
Top,
Bottom,
}
impl FixAlignment for OuterVAlignment {
fn fix(self, _: Dir) -> FixedAlignment {
match self {
Self::Top => FixedAlignment::Start,
Self::Bottom => FixedAlignment::End,
}
}
}
impl From<OuterVAlignment> for VAlignment {
fn from(value: OuterVAlignment) -> Self {
match value {
OuterVAlignment::Top => Self::Top,
OuterVAlignment::Bottom => Self::Bottom,
}
}
}
impl TryFrom<Alignment> for OuterVAlignment {
type Error = EcoString;
fn try_from(value: Alignment) -> StrResult<Self> {
match value {
Alignment::V(VAlignment::Top) => Ok(Self::Top),
Alignment::V(VAlignment::Bottom) => Ok(Self::Bottom),
v => bail!("expected `top` or `bottom`, found {}", v.repr()),
}
}
}
cast! {
OuterVAlignment,
self => VAlignment::from(self).into_value(),
align: Alignment => Self::try_from(align)?,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum SpecificAlignment<H, V> {
H(H),
V(V),
Both(H, V),
}
impl<H, V> SpecificAlignment<H, V>
where
H: Default + Copy + FixAlignment,
V: Default + Copy + FixAlignment,
{
pub const fn x(self) -> Option<H> {
match self {
Self::H(h) | Self::Both(h, _) => Some(h),
Self::V(_) => None,
}
}
pub const fn y(self) -> Option<V> {
match self {
Self::V(v) | Self::Both(_, v) => Some(v),
Self::H(_) => None,
}
}
pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
Axes::new(
self.x().unwrap_or_default().fix(text_dir),
self.y().unwrap_or_default().fix(text_dir),
)
}
}
impl<H, V> Resolve for SpecificAlignment<H, V>
where
H: Default + Copy + FixAlignment,
V: Default + Copy + FixAlignment,
{
type Output = Axes<FixedAlignment>;
fn resolve(self, styles: StyleChain) -> Self::Output {
self.fix(TextElem::dir_in(styles))
}
}
impl<H, V> From<SpecificAlignment<H, V>> for Alignment
where
HAlignment: From<H>,
VAlignment: From<V>,
{
fn from(value: SpecificAlignment<H, V>) -> Self {
type FromType<H, V> = SpecificAlignment<H, V>;
match value {
FromType::H(h) => Self::H(HAlignment::from(h)),
FromType::V(v) => Self::V(VAlignment::from(v)),
FromType::Both(h, v) => Self::Both(HAlignment::from(h), VAlignment::from(v)),
}
}
}
impl<H, V> Reflect for SpecificAlignment<H, V>
where
H: Reflect,
V: Reflect,
{
fn input() -> CastInfo {
Alignment::input()
}
fn output() -> CastInfo {
Alignment::output()
}
fn castable(value: &Value) -> bool {
H::castable(value) || V::castable(value)
}
}
impl<H, V> IntoValue for SpecificAlignment<H, V>
where
HAlignment: From<H>,
VAlignment: From<V>,
{
fn into_value(self) -> Value {
Alignment::from(self).into_value()
}
}
impl<H, V> FromValue for SpecificAlignment<H, V>
where
H: Reflect + TryFrom<Alignment, Error = EcoString>,
V: Reflect + TryFrom<Alignment, Error = EcoString>,
{
fn from_value(value: Value) -> HintedStrResult<Self> {
if Alignment::castable(&value) {
let align = Alignment::from_value(value)?;
let result = match align {
Alignment::H(_) => Self::H(H::try_from(align)?),
Alignment::V(_) => Self::V(V::try_from(align)?),
Alignment::Both(h, v) => {
Self::Both(H::try_from(h.into())?, V::try_from(v.into())?)
}
};
return Ok(result);
}
Err(Self::error(&value))
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FixedAlignment {
Start,
Center,
End,
}
impl FixedAlignment {
pub fn position(self, extent: Abs) -> Abs {
match self {
Self::Start => Abs::zero(),
Self::Center => extent / 2.0,
Self::End => extent,
}
}
pub const fn inv(self) -> Self {
match self {
Self::Start => Self::End,
Self::Center => Self::Center,
Self::End => Self::Start,
}
}
}
impl From<Side> for FixedAlignment {
fn from(side: Side) -> Self {
match side {
Side::Left => Self::Start,
Side::Top => Self::Start,
Side::Right => Self::End,
Side::Bottom => Self::End,
}
}
}