layout/
geom.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::convert::From;
6use std::fmt;
7use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
8
9use app_units::Au;
10use malloc_size_of_derive::MallocSizeOf;
11use style::Zero;
12use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection, WritingMode};
13use style::values::computed::{CSSPixelLength, LengthPercentage};
14use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr;
15use style_traits::CSSPixel;
16
17use crate::ContainingBlock;
18use crate::sizing::Size;
19
20pub type PhysicalPoint<U> = euclid::Point2D<U, CSSPixel>;
21pub type PhysicalSize<U> = euclid::Size2D<U, CSSPixel>;
22pub type PhysicalVec<U> = euclid::Vector2D<U, CSSPixel>;
23pub type PhysicalRect<U> = euclid::Rect<U, CSSPixel>;
24pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;
25pub type AuOrAuto = AutoOr<Au>;
26pub type LengthPercentageOrAuto<'a> = AutoOr<&'a LengthPercentage>;
27
28#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
29pub struct LogicalVec2<T> {
30    pub inline: T,
31    pub block: T,
32}
33
34#[derive(Clone, Copy)]
35pub struct LogicalRect<T> {
36    pub start_corner: LogicalVec2<T>,
37    pub size: LogicalVec2<T>,
38}
39
40#[derive(Clone, Copy, Debug, Default)]
41pub struct LogicalSides<T> {
42    pub inline_start: T,
43    pub inline_end: T,
44    pub block_start: T,
45    pub block_end: T,
46}
47
48#[derive(Clone, Copy, Debug)]
49pub(crate) struct LogicalSides1D<T> {
50    pub start: T,
51    pub end: T,
52}
53
54impl<T> LogicalSides1D<T> {
55    pub(crate) fn map<U>(&self, f: impl Fn(&T) -> U) -> LogicalSides1D<U> {
56        LogicalSides1D {
57            start: f(&self.start),
58            end: f(&self.end),
59        }
60    }
61}
62
63impl LogicalSides1D<AutoOr<&LengthPercentage>> {
64    pub(crate) fn percentages_relative_to(&self, basis: Au) -> LogicalSides1D<AutoOr<Au>> {
65        self.map(|value| value.map(|length_percentage| length_percentage.to_used_value(basis)))
66    }
67}
68
69impl<T: fmt::Debug> fmt::Debug for LogicalVec2<T> {
70    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71        // Not using f.debug_struct on purpose here, to keep {:?} output somewhat compact
72        f.write_str("Vec2 { i: ")?;
73        self.inline.fmt(f)?;
74        f.write_str(", b: ")?;
75        self.block.fmt(f)?;
76        f.write_str(" }")
77    }
78}
79
80impl<T: Default> Default for LogicalVec2<T> {
81    fn default() -> Self {
82        Self {
83            inline: T::default(),
84            block: T::default(),
85        }
86    }
87}
88
89impl<T: Copy> From<T> for LogicalVec2<T> {
90    fn from(value: T) -> Self {
91        Self {
92            inline: value,
93            block: value,
94        }
95    }
96}
97
98impl<T> LogicalVec2<T> {
99    pub fn map_inline_and_block_axes<U>(
100        &self,
101        inline_f: impl FnOnce(&T) -> U,
102        block_f: impl FnOnce(&T) -> U,
103    ) -> LogicalVec2<U> {
104        LogicalVec2 {
105            inline: inline_f(&self.inline),
106            block: block_f(&self.block),
107        }
108    }
109}
110
111impl<T: Clone> LogicalVec2<Size<T>> {
112    pub fn map_inline_and_block_sizes<U>(
113        &self,
114        inline_f: impl FnOnce(T) -> U,
115        block_f: impl FnOnce(T) -> U,
116    ) -> LogicalVec2<Size<U>> {
117        self.map_inline_and_block_axes(|size| size.map(inline_f), |size| size.map(block_f))
118    }
119}
120
121impl<T: Clone> LogicalVec2<T> {
122    pub fn from_physical_size(physical_size: &PhysicalSize<T>, mode: WritingMode) -> Self {
123        // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
124        let (i, b) = if mode.is_horizontal() {
125            (&physical_size.width, &physical_size.height)
126        } else {
127            (&physical_size.height, &physical_size.width)
128        };
129        LogicalVec2 {
130            inline: i.clone(),
131            block: b.clone(),
132        }
133    }
134
135    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> LogicalVec2<U> {
136        LogicalVec2 {
137            inline: f(&self.inline),
138            block: f(&self.block),
139        }
140    }
141
142    pub(crate) fn map_with<U, V>(
143        &self,
144        other: &LogicalVec2<U>,
145        f: impl Fn(&T, &U) -> V,
146    ) -> LogicalVec2<V> {
147        LogicalVec2 {
148            inline: f(&self.inline, &other.inline),
149            block: f(&self.block, &other.block),
150        }
151    }
152}
153
154impl<T: Add<Output = T> + Copy> Add<LogicalVec2<T>> for LogicalVec2<T> {
155    type Output = LogicalVec2<T>;
156    fn add(self, other: Self) -> Self::Output {
157        LogicalVec2 {
158            inline: self.inline + other.inline,
159            block: self.block + other.block,
160        }
161    }
162}
163
164impl<T: Sub<Output = T> + Copy> Sub<LogicalVec2<T>> for LogicalVec2<T> {
165    type Output = LogicalVec2<T>;
166    fn sub(self, other: Self) -> Self::Output {
167        LogicalVec2 {
168            inline: self.inline - other.inline,
169            block: self.block - other.block,
170        }
171    }
172}
173
174impl<T: AddAssign<T> + Copy> AddAssign<LogicalVec2<T>> for LogicalVec2<T> {
175    fn add_assign(&mut self, other: LogicalVec2<T>) {
176        self.inline += other.inline;
177        self.block += other.block;
178    }
179}
180
181impl<T: SubAssign<T> + Copy> SubAssign<LogicalVec2<T>> for LogicalVec2<T> {
182    fn sub_assign(&mut self, other: LogicalVec2<T>) {
183        self.inline -= other.inline;
184        self.block -= other.block;
185    }
186}
187
188impl<T: Neg<Output = T> + Copy> Neg for LogicalVec2<T> {
189    type Output = LogicalVec2<T>;
190    fn neg(self) -> Self::Output {
191        Self {
192            inline: -self.inline,
193            block: -self.block,
194        }
195    }
196}
197
198impl<T: Zero> LogicalVec2<T> {
199    pub fn zero() -> Self {
200        Self {
201            inline: T::zero(),
202            block: T::zero(),
203        }
204    }
205}
206
207impl<T: Clone> LogicalVec2<AutoOr<T>> {
208    pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalVec2<T> {
209        self.map(|t| t.auto_is(&f))
210    }
211}
212
213impl<T: Zero> LogicalRect<T> {
214    pub fn zero() -> Self {
215        Self {
216            start_corner: LogicalVec2::zero(),
217            size: LogicalVec2::zero(),
218        }
219    }
220}
221
222impl fmt::Debug for LogicalRect<Au> {
223    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224        write!(
225            f,
226            "Rect(i{}×b{} @ (i{},b{}))",
227            self.size.inline.to_f32_px(),
228            self.size.block.to_f32_px(),
229            self.start_corner.inline.to_f32_px(),
230            self.start_corner.block.to_f32_px(),
231        )
232    }
233}
234
235impl<T: Clone> LogicalVec2<T> {
236    pub fn to_physical_size(&self, mode: WritingMode) -> PhysicalSize<T> {
237        // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
238        let (x, y) = if mode.is_horizontal() {
239            (&self.inline, &self.block)
240        } else {
241            (&self.block, &self.inline)
242        };
243        PhysicalSize::new(x.clone(), y.clone())
244    }
245}
246
247impl<T: Copy + Neg<Output = T>> LogicalVec2<T> {
248    pub fn to_physical_vector(&self, mode: WritingMode) -> PhysicalVec<T> {
249        if mode.is_horizontal() {
250            if mode.is_bidi_ltr() {
251                PhysicalVec::new(self.inline, self.block)
252            } else {
253                PhysicalVec::new(-self.inline, self.block)
254            }
255        } else if mode.is_inline_tb() {
256            PhysicalVec::new(self.block, self.inline)
257        } else {
258            PhysicalVec::new(-self.block, self.inline)
259        }
260    }
261}
262
263impl<T: Clone> LogicalSides<T> {
264    pub fn from_physical(sides: &PhysicalSides<T>, mode: WritingMode) -> Self {
265        // https://drafts.csswg.org/css-writing-modes/#logical-to-physical
266        let block_flow = mode.block_flow_direction();
267        let (bs, be) = match mode.block_flow_direction() {
268            BlockFlowDirection::TopToBottom => (&sides.top, &sides.bottom),
269            BlockFlowDirection::RightToLeft => (&sides.right, &sides.left),
270            BlockFlowDirection::LeftToRight => (&sides.left, &sides.right),
271        };
272        use BlockFlowDirection::TopToBottom;
273        let (is, ie) = match (block_flow, mode.inline_base_direction()) {
274            (TopToBottom, InlineBaseDirection::LeftToRight) => (&sides.left, &sides.right),
275            (TopToBottom, InlineBaseDirection::RightToLeft) => (&sides.right, &sides.left),
276            (_, InlineBaseDirection::LeftToRight) => (&sides.top, &sides.bottom),
277            (_, InlineBaseDirection::RightToLeft) => (&sides.bottom, &sides.top),
278        };
279        LogicalSides {
280            inline_start: is.clone(),
281            inline_end: ie.clone(),
282            block_start: bs.clone(),
283            block_end: be.clone(),
284        }
285    }
286}
287
288impl<T> LogicalSides<T> {
289    pub fn map<U>(&self, f: impl Fn(&T) -> U) -> LogicalSides<U> {
290        LogicalSides {
291            inline_start: f(&self.inline_start),
292            inline_end: f(&self.inline_end),
293            block_start: f(&self.block_start),
294            block_end: f(&self.block_end),
295        }
296    }
297
298    pub fn map_inline_and_block_axes<U>(
299        &self,
300        inline_f: impl Fn(&T) -> U,
301        block_f: impl Fn(&T) -> U,
302    ) -> LogicalSides<U> {
303        LogicalSides {
304            inline_start: inline_f(&self.inline_start),
305            inline_end: inline_f(&self.inline_end),
306            block_start: block_f(&self.block_start),
307            block_end: block_f(&self.block_end),
308        }
309    }
310
311    pub fn inline_sum(&self) -> T::Output
312    where
313        T: Add + Copy,
314    {
315        self.inline_start + self.inline_end
316    }
317
318    pub fn block_sum(&self) -> T::Output
319    where
320        T: Add + Copy,
321    {
322        self.block_start + self.block_end
323    }
324
325    pub fn sum(&self) -> LogicalVec2<T::Output>
326    where
327        T: Add + Copy,
328    {
329        LogicalVec2 {
330            inline: self.inline_sum(),
331            block: self.block_sum(),
332        }
333    }
334
335    pub fn to_physical(&self, mode: WritingMode) -> PhysicalSides<T>
336    where
337        T: Clone,
338    {
339        let top;
340        let right;
341        let bottom;
342        let left;
343        if mode.is_vertical() {
344            if mode.is_vertical_lr() {
345                left = self.block_start.clone();
346                right = self.block_end.clone();
347            } else {
348                right = self.block_start.clone();
349                left = self.block_end.clone();
350            }
351
352            if mode.is_inline_tb() {
353                top = self.inline_start.clone();
354                bottom = self.inline_end.clone();
355            } else {
356                bottom = self.inline_start.clone();
357                top = self.inline_end.clone();
358            }
359        } else {
360            top = self.block_start.clone();
361            bottom = self.block_end.clone();
362            if mode.is_bidi_ltr() {
363                left = self.inline_start.clone();
364                right = self.inline_end.clone();
365            } else {
366                right = self.inline_start.clone();
367                left = self.inline_end.clone();
368            }
369        }
370        PhysicalSides::new(top, right, bottom, left)
371    }
372}
373
374impl<T: Copy> LogicalSides<T> {
375    pub fn start_offset(&self) -> LogicalVec2<T> {
376        LogicalVec2 {
377            inline: self.inline_start,
378            block: self.block_start,
379        }
380    }
381
382    #[inline]
383    pub(crate) fn inline_sides(&self) -> LogicalSides1D<T> {
384        LogicalSides1D::new(self.inline_start, self.inline_end)
385    }
386
387    #[inline]
388    pub(crate) fn block_sides(&self) -> LogicalSides1D<T> {
389        LogicalSides1D::new(self.block_start, self.block_end)
390    }
391}
392
393impl LogicalSides<LengthPercentage> {
394    pub fn percentages_relative_to(&self, basis: Au) -> LogicalSides<Au> {
395        self.map(|value| value.to_used_value(basis))
396    }
397}
398
399impl LogicalSides<LengthPercentageOrAuto<'_>> {
400    pub fn percentages_relative_to(&self, basis: Au) -> LogicalSides<AuOrAuto> {
401        self.map(|value| value.map(|value| value.to_used_value(basis)))
402    }
403}
404
405impl<T: Clone> LogicalSides<AutoOr<T>> {
406    pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalSides<T> {
407        self.map(|s| s.auto_is(&f))
408    }
409}
410
411impl<T: Add<Output = T> + Copy> Add<LogicalSides<T>> for LogicalSides<T> {
412    type Output = LogicalSides<T>;
413
414    fn add(self, other: Self) -> Self::Output {
415        LogicalSides {
416            inline_start: self.inline_start + other.inline_start,
417            inline_end: self.inline_end + other.inline_end,
418            block_start: self.block_start + other.block_start,
419            block_end: self.block_end + other.block_end,
420        }
421    }
422}
423
424impl<T: Sub<Output = T> + Copy> Sub<LogicalSides<T>> for LogicalSides<T> {
425    type Output = LogicalSides<T>;
426
427    fn sub(self, other: Self) -> Self::Output {
428        LogicalSides {
429            inline_start: self.inline_start - other.inline_start,
430            inline_end: self.inline_end - other.inline_end,
431            block_start: self.block_start - other.block_start,
432            block_end: self.block_end - other.block_end,
433        }
434    }
435}
436
437impl<T: Neg<Output = T> + Copy> Neg for LogicalSides<T> {
438    type Output = LogicalSides<T>;
439    fn neg(self) -> Self::Output {
440        Self {
441            inline_start: -self.inline_start,
442            inline_end: -self.inline_end,
443            block_start: -self.block_start,
444            block_end: -self.block_end,
445        }
446    }
447}
448
449impl<T: Zero> LogicalSides<T> {
450    pub(crate) fn zero() -> LogicalSides<T> {
451        Self {
452            inline_start: T::zero(),
453            inline_end: T::zero(),
454            block_start: T::zero(),
455            block_end: T::zero(),
456        }
457    }
458}
459
460impl From<LogicalSides<CSSPixelLength>> for LogicalSides<Au> {
461    fn from(value: LogicalSides<CSSPixelLength>) -> Self {
462        Self {
463            inline_start: value.inline_start.into(),
464            inline_end: value.inline_end.into(),
465            block_start: value.block_start.into(),
466            block_end: value.block_end.into(),
467        }
468    }
469}
470
471impl From<LogicalSides<Au>> for LogicalSides<CSSPixelLength> {
472    fn from(value: LogicalSides<Au>) -> Self {
473        Self {
474            inline_start: value.inline_start.into(),
475            inline_end: value.inline_end.into(),
476            block_start: value.block_start.into(),
477            block_end: value.block_end.into(),
478        }
479    }
480}
481
482impl<T> LogicalSides1D<T> {
483    #[inline]
484    pub(crate) fn new(start: T, end: T) -> Self {
485        Self { start, end }
486    }
487}
488
489impl<T> LogicalSides1D<AutoOr<T>> {
490    #[inline]
491    pub(crate) fn either_specified(&self) -> bool {
492        !self.start.is_auto() || !self.end.is_auto()
493    }
494
495    #[inline]
496    pub(crate) fn either_auto(&self) -> bool {
497        self.start.is_auto() || self.end.is_auto()
498    }
499}
500
501impl<T> LogicalRect<T> {
502    pub fn max_inline_position(&self) -> T
503    where
504        T: Add<Output = T> + Copy,
505    {
506        self.start_corner.inline + self.size.inline
507    }
508
509    pub fn max_block_position(&self) -> T
510    where
511        T: Add<Output = T> + Copy,
512    {
513        self.start_corner.block + self.size.block
514    }
515
516    pub fn inflate(&self, sides: &LogicalSides<T>) -> Self
517    where
518        T: Add<Output = T> + Copy,
519        T: Sub<Output = T> + Copy,
520    {
521        Self {
522            start_corner: LogicalVec2 {
523                inline: self.start_corner.inline - sides.inline_start,
524                block: self.start_corner.block - sides.block_start,
525            },
526            size: LogicalVec2 {
527                inline: self.size.inline + sides.inline_sum(),
528                block: self.size.block + sides.block_sum(),
529            },
530        }
531    }
532
533    pub fn deflate(&self, sides: &LogicalSides<T>) -> Self
534    where
535        T: Add<Output = T> + Copy,
536        T: Sub<Output = T> + Copy,
537    {
538        LogicalRect {
539            start_corner: LogicalVec2 {
540                inline: self.start_corner.inline + sides.inline_start,
541                block: self.start_corner.block + sides.block_start,
542            },
543            size: LogicalVec2 {
544                inline: self.size.inline - sides.inline_sum(),
545                block: self.size.block - sides.block_sum(),
546            },
547        }
548    }
549}
550
551impl LogicalRect<Au> {
552    pub(crate) fn as_physical(
553        &self,
554        containing_block: Option<&ContainingBlock<'_>>,
555    ) -> PhysicalRect<Au> {
556        let mode = containing_block.map_or_else(WritingMode::horizontal_tb, |containing_block| {
557            containing_block.style.writing_mode
558        });
559        let (x, y, width, height) = if mode.is_vertical() {
560            // TODO: Bottom-to-top writing modes are not supported.
561            (
562                self.start_corner.block,
563                self.start_corner.inline,
564                self.size.block,
565                self.size.inline,
566            )
567        } else {
568            let y = self.start_corner.block;
569            let x = match containing_block {
570                Some(containing_block) if !mode.is_bidi_ltr() => {
571                    containing_block.size.inline - self.max_inline_position()
572                },
573                _ => self.start_corner.inline,
574            };
575            (x, y, self.size.inline, self.size.block)
576        };
577
578        PhysicalRect::new(PhysicalPoint::new(x, y), PhysicalSize::new(width, height))
579    }
580}
581
582impl From<LogicalVec2<CSSPixelLength>> for LogicalVec2<Au> {
583    fn from(value: LogicalVec2<CSSPixelLength>) -> Self {
584        LogicalVec2 {
585            inline: value.inline.into(),
586            block: value.block.into(),
587        }
588    }
589}
590
591impl From<LogicalVec2<Au>> for LogicalVec2<CSSPixelLength> {
592    fn from(value: LogicalVec2<Au>) -> Self {
593        LogicalVec2 {
594            inline: value.inline.into(),
595            block: value.block.into(),
596        }
597    }
598}
599
600impl From<LogicalRect<Au>> for LogicalRect<CSSPixelLength> {
601    fn from(value: LogicalRect<Au>) -> Self {
602        LogicalRect {
603            start_corner: value.start_corner.into(),
604            size: value.size.into(),
605        }
606    }
607}
608
609impl From<LogicalRect<CSSPixelLength>> for LogicalRect<Au> {
610    fn from(value: LogicalRect<CSSPixelLength>) -> Self {
611        LogicalRect {
612            start_corner: value.start_corner.into(),
613            size: value.size.into(),
614        }
615    }
616}
617
618pub(crate) trait ToLogical<Unit, LogicalType> {
619    fn to_logical(&self, writing_mode: WritingMode) -> LogicalType;
620}
621
622impl<Unit: Copy> ToLogical<Unit, LogicalVec2<Unit>> for PhysicalSize<Unit> {
623    fn to_logical(&self, writing_mode: WritingMode) -> LogicalVec2<Unit> {
624        LogicalVec2::from_physical_size(self, writing_mode)
625    }
626}
627
628impl<Unit: Copy> ToLogical<Unit, LogicalSides<Unit>> for PhysicalSides<Unit> {
629    fn to_logical(&self, writing_mode: WritingMode) -> LogicalSides<Unit> {
630        LogicalSides::from_physical(self, writing_mode)
631    }
632}
633
634pub(crate) trait ToLogicalWithContainingBlock<LogicalType> {
635    fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalType;
636}
637
638impl ToLogicalWithContainingBlock<LogicalVec2<Au>> for PhysicalPoint<Au> {
639    fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalVec2<Au> {
640        let writing_mode = containing_block.style.writing_mode;
641        // TODO: Bottom-to-top and right-to-left vertical writing modes are not supported yet.
642        if writing_mode.is_vertical() {
643            LogicalVec2 {
644                inline: self.y,
645                block: self.x,
646            }
647        } else {
648            LogicalVec2 {
649                inline: if writing_mode.is_bidi_ltr() {
650                    self.x
651                } else {
652                    containing_block.size.inline - self.x
653                },
654                block: self.y,
655            }
656        }
657    }
658}
659
660impl ToLogicalWithContainingBlock<LogicalRect<Au>> for PhysicalRect<Au> {
661    fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalRect<Au> {
662        let inline_start;
663        let block_start;
664        let inline;
665        let block;
666
667        let writing_mode = containing_block.style.writing_mode;
668        if writing_mode.is_vertical() {
669            // TODO: Bottom-to-top and right-to-left vertical writing modes are not supported yet.
670            inline = self.size.height;
671            block = self.size.width;
672            block_start = self.origin.x;
673            inline_start = self.origin.y;
674        } else {
675            inline = self.size.width;
676            block = self.size.height;
677            block_start = self.origin.y;
678            if writing_mode.is_bidi_ltr() {
679                inline_start = self.origin.x;
680            } else {
681                inline_start = containing_block.size.inline - (self.origin.x + self.size.width);
682            }
683        }
684        LogicalRect {
685            start_corner: LogicalVec2 {
686                inline: inline_start,
687                block: block_start,
688            },
689            size: LogicalVec2 { inline, block },
690        }
691    }
692}