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