1use std::ops::Add;
2
3use ecow::{eco_format, EcoString};
4
5use crate::diag::{bail, HintedStrResult, SourceResult, StrResult};
6use crate::engine::Engine;
7use crate::foundations::{
8 cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Packed,
9 Reflect, Repr, Resolve, Show, StyleChain, Value,
10};
11use crate::layout::{Abs, Axes, Axis, Dir, Side};
12use crate::text::TextElem;
13
14#[elem(Show)]
77pub struct AlignElem {
78 #[positional]
91 #[fold]
92 #[default]
93 pub alignment: Alignment,
94
95 #[required]
97 pub body: Content,
98}
99
100impl Show for Packed<AlignElem> {
101 #[typst_macros::time(name = "align", span = self.span())]
102 fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
103 Ok(self.body.clone().aligned(self.alignment(styles)))
104 }
105}
106
107#[ty(scope)]
149#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
150pub enum Alignment {
151 H(HAlignment),
152 V(VAlignment),
153 Both(HAlignment, VAlignment),
154}
155
156impl Alignment {
157 pub const fn x(self) -> Option<HAlignment> {
159 match self {
160 Self::H(h) | Self::Both(h, _) => Some(h),
161 Self::V(_) => None,
162 }
163 }
164
165 pub const fn y(self) -> Option<VAlignment> {
167 match self {
168 Self::V(v) | Self::Both(_, v) => Some(v),
169 Self::H(_) => None,
170 }
171 }
172
173 pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
175 Axes::new(
176 self.x().unwrap_or_default().fix(text_dir),
177 self.y().unwrap_or_default().fix(text_dir),
178 )
179 }
180}
181
182#[scope]
183impl Alignment {
184 pub const START: Self = Alignment::H(HAlignment::Start);
185 pub const LEFT: Self = Alignment::H(HAlignment::Left);
186 pub const CENTER: Self = Alignment::H(HAlignment::Center);
187 pub const RIGHT: Self = Alignment::H(HAlignment::Right);
188 pub const END: Self = Alignment::H(HAlignment::End);
189 pub const TOP: Self = Alignment::V(VAlignment::Top);
190 pub const HORIZON: Self = Alignment::V(VAlignment::Horizon);
191 pub const BOTTOM: Self = Alignment::V(VAlignment::Bottom);
192
193 #[func]
203 pub const fn axis(self) -> Option<Axis> {
204 match self {
205 Self::H(_) => Some(Axis::X),
206 Self::V(_) => Some(Axis::Y),
207 Self::Both(..) => None,
208 }
209 }
210
211 #[func(title = "Inverse")]
220 pub const fn inv(self) -> Alignment {
221 match self {
222 Self::H(h) => Self::H(h.inv()),
223 Self::V(v) => Self::V(v.inv()),
224 Self::Both(h, v) => Self::Both(h.inv(), v.inv()),
225 }
226 }
227}
228
229impl Default for Alignment {
230 fn default() -> Self {
231 HAlignment::default() + VAlignment::default()
232 }
233}
234
235impl Add for Alignment {
236 type Output = StrResult<Self>;
237
238 fn add(self, rhs: Self) -> Self::Output {
239 match (self, rhs) {
240 (Self::H(h), Self::V(v)) | (Self::V(v), Self::H(h)) => Ok(h + v),
241 (Self::H(_), Self::H(_)) => bail!("cannot add two horizontal alignments"),
242 (Self::V(_), Self::V(_)) => bail!("cannot add two vertical alignments"),
243 (Self::H(_), Self::Both(..)) | (Self::Both(..), Self::H(_)) => {
244 bail!("cannot add a horizontal and a 2D alignment")
245 }
246 (Self::V(_), Self::Both(..)) | (Self::Both(..), Self::V(_)) => {
247 bail!("cannot add a vertical and a 2D alignment")
248 }
249 (Self::Both(..), Self::Both(..)) => {
250 bail!("cannot add two 2D alignments")
251 }
252 }
253 }
254}
255
256impl Repr for Alignment {
257 fn repr(&self) -> EcoString {
258 match self {
259 Self::H(h) => h.repr(),
260 Self::V(v) => v.repr(),
261 Self::Both(h, v) => eco_format!("{} + {}", h.repr(), v.repr()),
262 }
263 }
264}
265
266impl Fold for Alignment {
267 fn fold(self, outer: Self) -> Self {
268 match (self, outer) {
269 (Self::H(h), Self::V(v) | Self::Both(_, v)) => Self::Both(h, v),
270 (Self::V(v), Self::H(h) | Self::Both(h, _)) => Self::Both(h, v),
271 _ => self,
272 }
273 }
274}
275
276impl Resolve for Alignment {
277 type Output = Axes<FixedAlignment>;
278
279 fn resolve(self, styles: StyleChain) -> Self::Output {
280 self.fix(TextElem::dir_in(styles))
281 }
282}
283
284impl From<Side> for Alignment {
285 fn from(side: Side) -> Self {
286 match side {
287 Side::Left => Self::LEFT,
288 Side::Top => Self::TOP,
289 Side::Right => Self::RIGHT,
290 Side::Bottom => Self::BOTTOM,
291 }
292 }
293}
294
295pub trait FixAlignment {
297 fn fix(self, dir: Dir) -> FixedAlignment;
299}
300
301#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
303pub enum HAlignment {
304 #[default]
305 Start,
306 Left,
307 Center,
308 Right,
309 End,
310}
311
312impl HAlignment {
313 pub const fn inv(self) -> Self {
315 match self {
316 Self::Start => Self::End,
317 Self::Left => Self::Right,
318 Self::Center => Self::Center,
319 Self::Right => Self::Left,
320 Self::End => Self::Start,
321 }
322 }
323}
324
325impl FixAlignment for HAlignment {
326 fn fix(self, dir: Dir) -> FixedAlignment {
327 match (self, dir.is_positive()) {
328 (Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
329 (Self::Left, _) => FixedAlignment::Start,
330 (Self::Center, _) => FixedAlignment::Center,
331 (Self::Right, _) => FixedAlignment::End,
332 (Self::End, true) | (Self::Start, false) => FixedAlignment::End,
333 }
334 }
335}
336
337impl Repr for HAlignment {
338 fn repr(&self) -> EcoString {
339 match self {
340 Self::Start => "start".into(),
341 Self::Left => "left".into(),
342 Self::Center => "center".into(),
343 Self::Right => "right".into(),
344 Self::End => "end".into(),
345 }
346 }
347}
348
349impl Add<VAlignment> for HAlignment {
350 type Output = Alignment;
351
352 fn add(self, rhs: VAlignment) -> Self::Output {
353 Alignment::Both(self, rhs)
354 }
355}
356
357impl From<HAlignment> for Alignment {
358 fn from(align: HAlignment) -> Self {
359 Self::H(align)
360 }
361}
362
363impl TryFrom<Alignment> for HAlignment {
364 type Error = EcoString;
365
366 fn try_from(value: Alignment) -> StrResult<Self> {
367 match value {
368 Alignment::H(h) => Ok(h),
369 v => bail!(
370 "expected `start`, `left`, `center`, `right`, or `end`, found {}",
371 v.repr()
372 ),
373 }
374 }
375}
376
377impl Resolve for HAlignment {
378 type Output = FixedAlignment;
379
380 fn resolve(self, styles: StyleChain) -> Self::Output {
381 self.fix(TextElem::dir_in(styles))
382 }
383}
384
385cast! {
386 HAlignment,
387 self => Alignment::H(self).into_value(),
388 align: Alignment => Self::try_from(align)?,
389}
390
391#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
394pub enum OuterHAlignment {
395 #[default]
396 Start,
397 Left,
398 Right,
399 End,
400}
401
402impl FixAlignment for OuterHAlignment {
403 fn fix(self, dir: Dir) -> FixedAlignment {
404 match (self, dir.is_positive()) {
405 (Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
406 (Self::Left, _) => FixedAlignment::Start,
407 (Self::Right, _) => FixedAlignment::End,
408 (Self::End, true) | (Self::Start, false) => FixedAlignment::End,
409 }
410 }
411}
412
413impl Resolve for OuterHAlignment {
414 type Output = FixedAlignment;
415
416 fn resolve(self, styles: StyleChain) -> Self::Output {
417 self.fix(TextElem::dir_in(styles))
418 }
419}
420
421impl From<OuterHAlignment> for HAlignment {
422 fn from(value: OuterHAlignment) -> Self {
423 match value {
424 OuterHAlignment::Start => Self::Start,
425 OuterHAlignment::Left => Self::Left,
426 OuterHAlignment::Right => Self::Right,
427 OuterHAlignment::End => Self::End,
428 }
429 }
430}
431
432impl TryFrom<Alignment> for OuterHAlignment {
433 type Error = EcoString;
434
435 fn try_from(value: Alignment) -> StrResult<Self> {
436 match value {
437 Alignment::H(HAlignment::Start) => Ok(Self::Start),
438 Alignment::H(HAlignment::Left) => Ok(Self::Left),
439 Alignment::H(HAlignment::Right) => Ok(Self::Right),
440 Alignment::H(HAlignment::End) => Ok(Self::End),
441 v => bail!("expected `start`, `left`, `right`, or `end`, found {}", v.repr()),
442 }
443 }
444}
445
446cast! {
447 OuterHAlignment,
448 self => HAlignment::from(self).into_value(),
449 align: Alignment => Self::try_from(align)?,
450}
451
452#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
454pub enum VAlignment {
455 #[default]
456 Top,
457 Horizon,
458 Bottom,
459}
460
461impl VAlignment {
462 pub const fn inv(self) -> Self {
464 match self {
465 Self::Top => Self::Bottom,
466 Self::Horizon => Self::Horizon,
467 Self::Bottom => Self::Top,
468 }
469 }
470
471 pub fn position(self, extent: Abs) -> Abs {
474 match self {
475 Self::Top => Abs::zero(),
476 Self::Horizon => extent / 2.0,
477 Self::Bottom => extent,
478 }
479 }
480}
481
482impl FixAlignment for VAlignment {
483 fn fix(self, _: Dir) -> FixedAlignment {
484 match self {
486 Self::Top => FixedAlignment::Start,
487 Self::Horizon => FixedAlignment::Center,
488 Self::Bottom => FixedAlignment::End,
489 }
490 }
491}
492
493impl Repr for VAlignment {
494 fn repr(&self) -> EcoString {
495 match self {
496 Self::Top => "top".into(),
497 Self::Horizon => "horizon".into(),
498 Self::Bottom => "bottom".into(),
499 }
500 }
501}
502
503impl Add<HAlignment> for VAlignment {
504 type Output = Alignment;
505
506 fn add(self, rhs: HAlignment) -> Self::Output {
507 Alignment::Both(rhs, self)
508 }
509}
510
511impl Resolve for VAlignment {
512 type Output = FixedAlignment;
513
514 fn resolve(self, _: StyleChain) -> Self::Output {
515 self.fix(Dir::TTB)
516 }
517}
518
519impl From<VAlignment> for Alignment {
520 fn from(align: VAlignment) -> Self {
521 Self::V(align)
522 }
523}
524
525impl TryFrom<Alignment> for VAlignment {
526 type Error = EcoString;
527
528 fn try_from(value: Alignment) -> StrResult<Self> {
529 match value {
530 Alignment::V(v) => Ok(v),
531 v => bail!("expected `top`, `horizon`, or `bottom`, found {}", v.repr()),
532 }
533 }
534}
535
536cast! {
537 VAlignment,
538 self => Alignment::V(self).into_value(),
539 align: Alignment => Self::try_from(align)?,
540}
541
542#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
545pub enum OuterVAlignment {
546 #[default]
547 Top,
548 Bottom,
549}
550
551impl FixAlignment for OuterVAlignment {
552 fn fix(self, _: Dir) -> FixedAlignment {
553 match self {
555 Self::Top => FixedAlignment::Start,
556 Self::Bottom => FixedAlignment::End,
557 }
558 }
559}
560
561impl From<OuterVAlignment> for VAlignment {
562 fn from(value: OuterVAlignment) -> Self {
563 match value {
564 OuterVAlignment::Top => Self::Top,
565 OuterVAlignment::Bottom => Self::Bottom,
566 }
567 }
568}
569
570impl TryFrom<Alignment> for OuterVAlignment {
571 type Error = EcoString;
572
573 fn try_from(value: Alignment) -> StrResult<Self> {
574 match value {
575 Alignment::V(VAlignment::Top) => Ok(Self::Top),
576 Alignment::V(VAlignment::Bottom) => Ok(Self::Bottom),
577 v => bail!("expected `top` or `bottom`, found {}", v.repr()),
578 }
579 }
580}
581
582cast! {
583 OuterVAlignment,
584 self => VAlignment::from(self).into_value(),
585 align: Alignment => Self::try_from(align)?,
586}
587
588#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
595pub enum SpecificAlignment<H, V> {
596 H(H),
597 V(V),
598 Both(H, V),
599}
600
601impl<H, V> SpecificAlignment<H, V>
602where
603 H: Default + Copy + FixAlignment,
604 V: Default + Copy + FixAlignment,
605{
606 pub const fn x(self) -> Option<H> {
608 match self {
609 Self::H(h) | Self::Both(h, _) => Some(h),
610 Self::V(_) => None,
611 }
612 }
613
614 pub const fn y(self) -> Option<V> {
616 match self {
617 Self::V(v) | Self::Both(_, v) => Some(v),
618 Self::H(_) => None,
619 }
620 }
621
622 pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
624 Axes::new(
625 self.x().unwrap_or_default().fix(text_dir),
626 self.y().unwrap_or_default().fix(text_dir),
627 )
628 }
629}
630
631impl<H, V> Resolve for SpecificAlignment<H, V>
632where
633 H: Default + Copy + FixAlignment,
634 V: Default + Copy + FixAlignment,
635{
636 type Output = Axes<FixedAlignment>;
637
638 fn resolve(self, styles: StyleChain) -> Self::Output {
639 self.fix(TextElem::dir_in(styles))
640 }
641}
642
643impl<H, V> From<SpecificAlignment<H, V>> for Alignment
644where
645 HAlignment: From<H>,
646 VAlignment: From<V>,
647{
648 fn from(value: SpecificAlignment<H, V>) -> Self {
649 type FromType<H, V> = SpecificAlignment<H, V>;
650 match value {
651 FromType::H(h) => Self::H(HAlignment::from(h)),
652 FromType::V(v) => Self::V(VAlignment::from(v)),
653 FromType::Both(h, v) => Self::Both(HAlignment::from(h), VAlignment::from(v)),
654 }
655 }
656}
657
658impl<H, V> Reflect for SpecificAlignment<H, V>
659where
660 H: Reflect,
661 V: Reflect,
662{
663 fn input() -> CastInfo {
664 Alignment::input()
665 }
666
667 fn output() -> CastInfo {
668 Alignment::output()
669 }
670
671 fn castable(value: &Value) -> bool {
672 H::castable(value) || V::castable(value)
673 }
674}
675
676impl<H, V> IntoValue for SpecificAlignment<H, V>
677where
678 HAlignment: From<H>,
679 VAlignment: From<V>,
680{
681 fn into_value(self) -> Value {
682 Alignment::from(self).into_value()
683 }
684}
685
686impl<H, V> FromValue for SpecificAlignment<H, V>
687where
688 H: Reflect + TryFrom<Alignment, Error = EcoString>,
689 V: Reflect + TryFrom<Alignment, Error = EcoString>,
690{
691 fn from_value(value: Value) -> HintedStrResult<Self> {
692 if Alignment::castable(&value) {
693 let align = Alignment::from_value(value)?;
694 let result = match align {
695 Alignment::H(_) => Self::H(H::try_from(align)?),
696 Alignment::V(_) => Self::V(V::try_from(align)?),
697 Alignment::Both(h, v) => {
698 Self::Both(H::try_from(h.into())?, V::try_from(v.into())?)
699 }
700 };
701 return Ok(result);
702 }
703 Err(Self::error(&value))
704 }
705}
706
707#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
712pub enum FixedAlignment {
713 Start,
714 Center,
715 End,
716}
717
718impl FixedAlignment {
719 pub fn position(self, extent: Abs) -> Abs {
722 match self {
723 Self::Start => Abs::zero(),
724 Self::Center => extent / 2.0,
725 Self::End => extent,
726 }
727 }
728
729 pub const fn inv(self) -> Self {
731 match self {
732 Self::Start => Self::End,
733 Self::Center => Self::Center,
734 Self::End => Self::Start,
735 }
736 }
737}
738
739impl From<Side> for FixedAlignment {
740 fn from(side: Side) -> Self {
741 match side {
742 Side::Left => Self::Start,
743 Side::Top => Self::Start,
744 Side::Right => Self::End,
745 Side::Bottom => Self::End,
746 }
747 }
748}