style/
logical_geometry.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
5//! Geometry in flow-relative space.
6
7use crate::properties::style_structs;
8use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D};
9use euclid::num::Zero;
10use std::cmp::{max, min};
11use std::fmt::{self, Debug, Error, Formatter};
12use std::ops::{Add, Sub};
13
14pub enum BlockFlowDirection {
15    TopToBottom,
16    RightToLeft,
17    LeftToRight,
18}
19
20pub enum InlineBaseDirection {
21    LeftToRight,
22    RightToLeft,
23}
24
25/// The writing-mode property (different from the WritingMode enum).
26/// https://drafts.csswg.org/css-writing-modes/#block-flow
27/// Aliases come from https://drafts.csswg.org/css-writing-modes-4/#svg-writing-mode
28#[allow(missing_docs)]
29#[derive(
30    Clone,
31    Copy,
32    Debug,
33    Eq,
34    FromPrimitive,
35    MallocSizeOf,
36    Parse,
37    PartialEq,
38    SpecifiedValueInfo,
39    ToComputedValue,
40    ToCss,
41    ToResolvedValue,
42    ToShmem,
43    ToTyped,
44)]
45#[repr(u8)]
46pub enum WritingModeProperty {
47    #[parse(aliases = "lr,lr-tb,rl,rl-tb")]
48    HorizontalTb,
49    #[parse(aliases = "tb,tb-rl")]
50    VerticalRl,
51    VerticalLr,
52    #[cfg(feature = "gecko")]
53    SidewaysRl,
54    #[cfg(feature = "gecko")]
55    SidewaysLr,
56}
57
58// TODO: improve the readability of the WritingMode serialization, refer to the Debug:fmt()
59#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Serialize)]
60#[repr(C)]
61pub struct WritingMode(u8);
62bitflags!(
63    impl WritingMode: u8 {
64        /// A vertical writing mode; writing-mode is vertical-rl,
65        /// vertical-lr, sideways-lr, or sideways-rl.
66        const VERTICAL = 1 << 0;
67        /// The inline flow direction is reversed against the physical
68        /// direction (i.e. right-to-left or bottom-to-top); writing-mode is
69        /// sideways-lr or direction is rtl (but not both).
70        ///
71        /// (This bit can be derived from the others, but we store it for
72        /// convenience.)
73        const INLINE_REVERSED = 1 << 1;
74        /// A vertical writing mode whose block progression direction is left-
75        /// to-right; writing-mode is vertical-lr or sideways-lr.
76        ///
77        /// Never set without VERTICAL.
78        const VERTICAL_LR = 1 << 2;
79        /// The line-over/line-under sides are inverted with respect to the
80        /// block-start/block-end edge; writing-mode is vertical-lr.
81        ///
82        /// Never set without VERTICAL and VERTICAL_LR.
83        const LINE_INVERTED = 1 << 3;
84        /// direction is rtl.
85        const RTL = 1 << 4;
86        /// All text within a vertical writing mode is displayed sideways
87        /// and runs top-to-bottom or bottom-to-top; set in these cases:
88        ///
89        /// * writing-mode: sideways-rl;
90        /// * writing-mode: sideways-lr;
91        ///
92        /// Never set without VERTICAL.
93        const VERTICAL_SIDEWAYS = 1 << 5;
94        /// Similar to VERTICAL_SIDEWAYS, but is set via text-orientation;
95        /// set in these cases:
96        ///
97        /// * writing-mode: vertical-rl; text-orientation: sideways;
98        /// * writing-mode: vertical-lr; text-orientation: sideways;
99        ///
100        /// Never set without VERTICAL.
101        const TEXT_SIDEWAYS = 1 << 6;
102        /// Horizontal text within a vertical writing mode is displayed with each
103        /// glyph upright; set in these cases:
104        ///
105        /// * writing-mode: vertical-rl; text-orientation: upright;
106        /// * writing-mode: vertical-lr: text-orientation: upright;
107        ///
108        /// Never set without VERTICAL.
109        const UPRIGHT = 1 << 7;
110        /// Writing mode combinations that can be specified in CSS.
111        ///
112        /// * writing-mode: horizontal-tb;
113        const WRITING_MODE_HORIZONTAL_TB = 0;
114        /// * writing-mode: vertical_rl;
115        const WRITING_MODE_VERTICAL_RL = WritingMode::VERTICAL.bits();
116        /// * writing-mode: vertcail-lr;
117        const WRITING_MODE_VERTICAL_LR = WritingMode::VERTICAL.bits() |
118                                         WritingMode::VERTICAL_LR.bits() |
119                                         WritingMode::LINE_INVERTED.bits();
120        /// * writing-mode: sideways-rl;
121        const WRITING_MODE_SIDEWAYS_RL = WritingMode::VERTICAL.bits() |
122                                         WritingMode::VERTICAL_SIDEWAYS.bits();
123        /// * writing-mode: sideways-lr;
124        const WRITING_MODE_SIDEWAYS_LR = WritingMode::VERTICAL.bits() |
125                                         WritingMode::VERTICAL_LR.bits() |
126                                         WritingMode::VERTICAL_SIDEWAYS.bits();
127    }
128);
129
130impl WritingMode {
131    /// Return a WritingMode bitflags from the relevant CSS properties.
132    pub fn new(inheritedbox_style: &style_structs::InheritedBox) -> Self {
133        use crate::properties::longhands::direction::computed_value::T as Direction;
134
135        let mut flags = WritingMode::empty();
136
137        let direction = inheritedbox_style.clone_direction();
138        let writing_mode = inheritedbox_style.clone_writing_mode();
139
140        match direction {
141            Direction::Ltr => {},
142            Direction::Rtl => {
143                flags.insert(WritingMode::RTL);
144            },
145        }
146
147        match writing_mode {
148            WritingModeProperty::HorizontalTb => {
149                if direction == Direction::Rtl {
150                    flags.insert(WritingMode::INLINE_REVERSED);
151                }
152            },
153            WritingModeProperty::VerticalRl => {
154                flags.insert(WritingMode::WRITING_MODE_VERTICAL_RL);
155                if direction == Direction::Rtl {
156                    flags.insert(WritingMode::INLINE_REVERSED);
157                }
158            },
159            WritingModeProperty::VerticalLr => {
160                flags.insert(WritingMode::WRITING_MODE_VERTICAL_LR);
161                if direction == Direction::Rtl {
162                    flags.insert(WritingMode::INLINE_REVERSED);
163                }
164            },
165            #[cfg(feature = "gecko")]
166            WritingModeProperty::SidewaysRl => {
167                flags.insert(WritingMode::WRITING_MODE_SIDEWAYS_RL);
168                if direction == Direction::Rtl {
169                    flags.insert(WritingMode::INLINE_REVERSED);
170                }
171            },
172            #[cfg(feature = "gecko")]
173            WritingModeProperty::SidewaysLr => {
174                flags.insert(WritingMode::WRITING_MODE_SIDEWAYS_LR);
175                if direction == Direction::Ltr {
176                    flags.insert(WritingMode::INLINE_REVERSED);
177                }
178            },
179        }
180
181        #[cfg(feature = "gecko")]
182        {
183            use crate::properties::longhands::text_orientation::computed_value::T as TextOrientation;
184
185            // text-orientation only has an effect for vertical-rl and
186            // vertical-lr values of writing-mode.
187            match writing_mode {
188                WritingModeProperty::VerticalRl | WritingModeProperty::VerticalLr => {
189                    match inheritedbox_style.clone_text_orientation() {
190                        TextOrientation::Mixed => {},
191                        TextOrientation::Upright => {
192                            flags.insert(WritingMode::UPRIGHT);
193
194                            // https://drafts.csswg.org/css-writing-modes-3/#valdef-text-orientation-upright:
195                            //
196                            // > This value causes the used value of direction
197                            // > to be ltr, and for the purposes of bidi
198                            // > reordering, causes all characters to be treated
199                            // > as strong LTR.
200                            flags.remove(WritingMode::RTL);
201                            flags.remove(WritingMode::INLINE_REVERSED);
202                        },
203                        TextOrientation::Sideways => {
204                            flags.insert(WritingMode::TEXT_SIDEWAYS);
205                        },
206                    }
207                },
208                _ => {},
209            }
210        }
211
212        flags
213    }
214
215    /// Returns the `horizontal-tb` value.
216    pub fn horizontal_tb() -> Self {
217        Self::empty()
218    }
219
220    #[inline]
221    pub fn is_vertical(&self) -> bool {
222        self.intersects(WritingMode::VERTICAL)
223    }
224
225    #[inline]
226    pub fn is_horizontal(&self) -> bool {
227        !self.is_vertical()
228    }
229
230    /// Assuming .is_vertical(), does the block direction go left to right?
231    #[inline]
232    pub fn is_vertical_lr(&self) -> bool {
233        self.intersects(WritingMode::VERTICAL_LR)
234    }
235
236    /// Assuming .is_vertical(), does the inline direction go top to bottom?
237    #[inline]
238    pub fn is_inline_tb(&self) -> bool {
239        // https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical
240        !self.intersects(WritingMode::INLINE_REVERSED)
241    }
242
243    #[inline]
244    pub fn is_bidi_ltr(&self) -> bool {
245        !self.intersects(WritingMode::RTL)
246    }
247
248    #[inline]
249    pub fn is_sideways(&self) -> bool {
250        self.intersects(WritingMode::VERTICAL_SIDEWAYS | WritingMode::TEXT_SIDEWAYS)
251    }
252
253    #[inline]
254    pub fn is_upright(&self) -> bool {
255        self.intersects(WritingMode::UPRIGHT)
256    }
257
258    /// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
259    ///
260    /// | Return  | line-left is… | line-right is… |
261    /// |---------|---------------|----------------|
262    /// | `true`  | inline-start  | inline-end     |
263    /// | `false` | inline-end    | inline-start   |
264    #[inline]
265    pub fn line_left_is_inline_start(&self) -> bool {
266        // https://drafts.csswg.org/css-writing-modes/#inline-start
267        // “For boxes with a used direction value of ltr, this means the line-left side.
268        //  For boxes with a used direction value of rtl, this means the line-right side.”
269        self.is_bidi_ltr()
270    }
271
272    #[inline]
273    pub fn inline_start_physical_side(&self) -> PhysicalSide {
274        match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) {
275            (false, _, true) => PhysicalSide::Left,
276            (false, _, false) => PhysicalSide::Right,
277            (true, true, _) => PhysicalSide::Top,
278            (true, false, _) => PhysicalSide::Bottom,
279        }
280    }
281
282    #[inline]
283    pub fn inline_end_physical_side(&self) -> PhysicalSide {
284        match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) {
285            (false, _, true) => PhysicalSide::Right,
286            (false, _, false) => PhysicalSide::Left,
287            (true, true, _) => PhysicalSide::Bottom,
288            (true, false, _) => PhysicalSide::Top,
289        }
290    }
291
292    #[inline]
293    pub fn block_start_physical_side(&self) -> PhysicalSide {
294        match (self.is_vertical(), self.is_vertical_lr()) {
295            (false, _) => PhysicalSide::Top,
296            (true, true) => PhysicalSide::Left,
297            (true, false) => PhysicalSide::Right,
298        }
299    }
300
301    #[inline]
302    pub fn block_end_physical_side(&self) -> PhysicalSide {
303        match (self.is_vertical(), self.is_vertical_lr()) {
304            (false, _) => PhysicalSide::Bottom,
305            (true, true) => PhysicalSide::Right,
306            (true, false) => PhysicalSide::Left,
307        }
308    }
309
310    #[inline]
311    pub fn start_start_physical_corner(&self) -> PhysicalCorner {
312        PhysicalCorner::from_sides(
313            self.block_start_physical_side(),
314            self.inline_start_physical_side(),
315        )
316    }
317
318    #[inline]
319    pub fn start_end_physical_corner(&self) -> PhysicalCorner {
320        PhysicalCorner::from_sides(
321            self.block_start_physical_side(),
322            self.inline_end_physical_side(),
323        )
324    }
325
326    #[inline]
327    pub fn end_start_physical_corner(&self) -> PhysicalCorner {
328        PhysicalCorner::from_sides(
329            self.block_end_physical_side(),
330            self.inline_start_physical_side(),
331        )
332    }
333
334    #[inline]
335    pub fn end_end_physical_corner(&self) -> PhysicalCorner {
336        PhysicalCorner::from_sides(
337            self.block_end_physical_side(),
338            self.inline_end_physical_side(),
339        )
340    }
341
342    #[inline]
343    pub fn block_flow_direction(&self) -> BlockFlowDirection {
344        match (self.is_vertical(), self.is_vertical_lr()) {
345            (false, _) => BlockFlowDirection::TopToBottom,
346            (true, true) => BlockFlowDirection::LeftToRight,
347            (true, false) => BlockFlowDirection::RightToLeft,
348        }
349    }
350
351    #[inline]
352    pub fn inline_base_direction(&self) -> InlineBaseDirection {
353        if self.intersects(WritingMode::RTL) {
354            InlineBaseDirection::RightToLeft
355        } else {
356            InlineBaseDirection::LeftToRight
357        }
358    }
359
360    #[inline]
361    /// Is the text layout vertical?
362    pub fn is_text_vertical(&self) -> bool {
363        self.is_vertical() && !self.is_sideways()
364    }
365}
366
367impl fmt::Display for WritingMode {
368    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
369        if self.is_vertical() {
370            write!(formatter, "V")?;
371            if self.is_vertical_lr() {
372                write!(formatter, " LR")?;
373            } else {
374                write!(formatter, " RL")?;
375            }
376            if self.is_sideways() {
377                write!(formatter, " Sideways")?;
378            }
379            if self.intersects(WritingMode::LINE_INVERTED) {
380                write!(formatter, " Inverted")?;
381            }
382        } else {
383            write!(formatter, "H")?;
384        }
385        if self.is_bidi_ltr() {
386            write!(formatter, " LTR")
387        } else {
388            write!(formatter, " RTL")
389        }
390    }
391}
392
393/// Wherever logical geometry is used, the writing mode is known based on context:
394/// every method takes a `mode` parameter.
395/// However, this context is easy to get wrong.
396/// In debug builds only, logical geometry objects store their writing mode
397/// (in addition to taking it as a parameter to methods) and check it.
398/// In non-debug builds, make this storage zero-size and the checks no-ops.
399#[cfg(not(debug_assertions))]
400#[derive(Clone, Copy, Eq, PartialEq)]
401#[cfg_attr(feature = "servo", derive(Serialize))]
402struct DebugWritingMode;
403
404#[cfg(debug_assertions)]
405#[derive(Clone, Copy, Eq, PartialEq)]
406#[cfg_attr(feature = "servo", derive(Serialize))]
407struct DebugWritingMode {
408    mode: WritingMode,
409}
410
411#[cfg(not(debug_assertions))]
412impl DebugWritingMode {
413    #[inline]
414    fn check(&self, _other: WritingMode) {}
415
416    #[inline]
417    fn check_debug(&self, _other: DebugWritingMode) {}
418
419    #[inline]
420    fn new(_mode: WritingMode) -> DebugWritingMode {
421        DebugWritingMode
422    }
423}
424
425#[cfg(debug_assertions)]
426impl DebugWritingMode {
427    #[inline]
428    fn check(&self, other: WritingMode) {
429        assert_eq!(self.mode, other)
430    }
431
432    #[inline]
433    fn check_debug(&self, other: DebugWritingMode) {
434        assert_eq!(self.mode, other.mode)
435    }
436
437    #[inline]
438    fn new(mode: WritingMode) -> DebugWritingMode {
439        DebugWritingMode { mode }
440    }
441}
442
443impl Debug for DebugWritingMode {
444    #[cfg(not(debug_assertions))]
445    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
446        write!(formatter, "?")
447    }
448
449    #[cfg(debug_assertions)]
450    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
451        write!(formatter, "{}", self.mode)
452    }
453}
454
455// Used to specify the logical direction.
456#[derive(Clone, Copy, Debug, PartialEq)]
457#[cfg_attr(feature = "servo", derive(Serialize))]
458pub enum Direction {
459    Inline,
460    Block,
461}
462
463/// A 2D size in flow-relative dimensions
464#[derive(Clone, Copy, Eq, PartialEq)]
465#[cfg_attr(feature = "servo", derive(Serialize))]
466pub struct LogicalSize<T> {
467    pub inline: T, // inline-size, a.k.a. logical width, a.k.a. measure
468    pub block: T,  // block-size, a.k.a. logical height, a.k.a. extent
469    debug_writing_mode: DebugWritingMode,
470}
471
472impl<T: Debug> Debug for LogicalSize<T> {
473    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
474        write!(
475            formatter,
476            "LogicalSize({:?}, i{:?}×b{:?})",
477            self.debug_writing_mode, self.inline, self.block
478        )
479    }
480}
481
482// Can not implement the Zero trait: its zero() method does not have the `mode` parameter.
483impl<T: Zero> LogicalSize<T> {
484    #[inline]
485    pub fn zero(mode: WritingMode) -> LogicalSize<T> {
486        LogicalSize {
487            inline: Zero::zero(),
488            block: Zero::zero(),
489            debug_writing_mode: DebugWritingMode::new(mode),
490        }
491    }
492}
493
494impl<T> LogicalSize<T> {
495    #[inline]
496    pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize<T> {
497        LogicalSize {
498            inline: inline,
499            block: block,
500            debug_writing_mode: DebugWritingMode::new(mode),
501        }
502    }
503
504    #[inline]
505    pub fn from_physical(mode: WritingMode, size: Size2D<T>) -> LogicalSize<T> {
506        if mode.is_vertical() {
507            LogicalSize::new(mode, size.height, size.width)
508        } else {
509            LogicalSize::new(mode, size.width, size.height)
510        }
511    }
512}
513
514impl<T: Clone> LogicalSize<T> {
515    #[inline]
516    pub fn width(&self, mode: WritingMode) -> T {
517        self.debug_writing_mode.check(mode);
518        if mode.is_vertical() {
519            self.block.clone()
520        } else {
521            self.inline.clone()
522        }
523    }
524
525    #[inline]
526    pub fn set_width(&mut self, mode: WritingMode, width: T) {
527        self.debug_writing_mode.check(mode);
528        if mode.is_vertical() {
529            self.block = width
530        } else {
531            self.inline = width
532        }
533    }
534
535    #[inline]
536    pub fn height(&self, mode: WritingMode) -> T {
537        self.debug_writing_mode.check(mode);
538        if mode.is_vertical() {
539            self.inline.clone()
540        } else {
541            self.block.clone()
542        }
543    }
544
545    #[inline]
546    pub fn set_height(&mut self, mode: WritingMode, height: T) {
547        self.debug_writing_mode.check(mode);
548        if mode.is_vertical() {
549            self.inline = height
550        } else {
551            self.block = height
552        }
553    }
554
555    #[inline]
556    pub fn to_physical(&self, mode: WritingMode) -> Size2D<T> {
557        self.debug_writing_mode.check(mode);
558        if mode.is_vertical() {
559            Size2D::new(self.block.clone(), self.inline.clone())
560        } else {
561            Size2D::new(self.inline.clone(), self.block.clone())
562        }
563    }
564
565    #[inline]
566    pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize<T> {
567        if mode_from == mode_to {
568            self.debug_writing_mode.check(mode_from);
569            self.clone()
570        } else {
571            LogicalSize::from_physical(mode_to, self.to_physical(mode_from))
572        }
573    }
574}
575
576impl<T: Add<T, Output = T>> Add for LogicalSize<T> {
577    type Output = LogicalSize<T>;
578
579    #[inline]
580    fn add(self, other: LogicalSize<T>) -> LogicalSize<T> {
581        self.debug_writing_mode
582            .check_debug(other.debug_writing_mode);
583        LogicalSize {
584            debug_writing_mode: self.debug_writing_mode,
585            inline: self.inline + other.inline,
586            block: self.block + other.block,
587        }
588    }
589}
590
591impl<T: Sub<T, Output = T>> Sub for LogicalSize<T> {
592    type Output = LogicalSize<T>;
593
594    #[inline]
595    fn sub(self, other: LogicalSize<T>) -> LogicalSize<T> {
596        self.debug_writing_mode
597            .check_debug(other.debug_writing_mode);
598        LogicalSize {
599            debug_writing_mode: self.debug_writing_mode,
600            inline: self.inline - other.inline,
601            block: self.block - other.block,
602        }
603    }
604}
605
606/// A 2D point in flow-relative dimensions
607#[derive(Clone, Copy, Eq, PartialEq)]
608#[cfg_attr(feature = "servo", derive(Serialize))]
609pub struct LogicalPoint<T> {
610    /// inline-axis coordinate
611    pub i: T,
612    /// block-axis coordinate
613    pub b: T,
614    debug_writing_mode: DebugWritingMode,
615}
616
617impl<T: Debug> Debug for LogicalPoint<T> {
618    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
619        write!(
620            formatter,
621            "LogicalPoint({:?} (i{:?}, b{:?}))",
622            self.debug_writing_mode, self.i, self.b
623        )
624    }
625}
626
627// Can not implement the Zero trait: its zero() method does not have the `mode` parameter.
628impl<T: Zero> LogicalPoint<T> {
629    #[inline]
630    pub fn zero(mode: WritingMode) -> LogicalPoint<T> {
631        LogicalPoint {
632            i: Zero::zero(),
633            b: Zero::zero(),
634            debug_writing_mode: DebugWritingMode::new(mode),
635        }
636    }
637}
638
639impl<T: Copy> LogicalPoint<T> {
640    #[inline]
641    pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint<T> {
642        LogicalPoint {
643            i: i,
644            b: b,
645            debug_writing_mode: DebugWritingMode::new(mode),
646        }
647    }
648}
649
650impl<T: Copy + Sub<T, Output = T>> LogicalPoint<T> {
651    #[inline]
652    pub fn from_physical(
653        mode: WritingMode,
654        point: Point2D<T>,
655        container_size: Size2D<T>,
656    ) -> LogicalPoint<T> {
657        if mode.is_vertical() {
658            LogicalPoint {
659                i: if mode.is_inline_tb() {
660                    point.y
661                } else {
662                    container_size.height - point.y
663                },
664                b: if mode.is_vertical_lr() {
665                    point.x
666                } else {
667                    container_size.width - point.x
668                },
669                debug_writing_mode: DebugWritingMode::new(mode),
670            }
671        } else {
672            LogicalPoint {
673                i: if mode.is_bidi_ltr() {
674                    point.x
675                } else {
676                    container_size.width - point.x
677                },
678                b: point.y,
679                debug_writing_mode: DebugWritingMode::new(mode),
680            }
681        }
682    }
683
684    #[inline]
685    pub fn x(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
686        self.debug_writing_mode.check(mode);
687        if mode.is_vertical() {
688            if mode.is_vertical_lr() {
689                self.b
690            } else {
691                container_size.width - self.b
692            }
693        } else {
694            if mode.is_bidi_ltr() {
695                self.i
696            } else {
697                container_size.width - self.i
698            }
699        }
700    }
701
702    #[inline]
703    pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D<T>) {
704        self.debug_writing_mode.check(mode);
705        if mode.is_vertical() {
706            self.b = if mode.is_vertical_lr() {
707                x
708            } else {
709                container_size.width - x
710            }
711        } else {
712            self.i = if mode.is_bidi_ltr() {
713                x
714            } else {
715                container_size.width - x
716            }
717        }
718    }
719
720    #[inline]
721    pub fn y(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
722        self.debug_writing_mode.check(mode);
723        if mode.is_vertical() {
724            if mode.is_inline_tb() {
725                self.i
726            } else {
727                container_size.height - self.i
728            }
729        } else {
730            self.b
731        }
732    }
733
734    #[inline]
735    pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D<T>) {
736        self.debug_writing_mode.check(mode);
737        if mode.is_vertical() {
738            self.i = if mode.is_inline_tb() {
739                y
740            } else {
741                container_size.height - y
742            }
743        } else {
744            self.b = y
745        }
746    }
747
748    #[inline]
749    pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> {
750        self.debug_writing_mode.check(mode);
751        if mode.is_vertical() {
752            Point2D::new(
753                if mode.is_vertical_lr() {
754                    self.b
755                } else {
756                    container_size.width - self.b
757                },
758                if mode.is_inline_tb() {
759                    self.i
760                } else {
761                    container_size.height - self.i
762                },
763            )
764        } else {
765            Point2D::new(
766                if mode.is_bidi_ltr() {
767                    self.i
768                } else {
769                    container_size.width - self.i
770                },
771                self.b,
772            )
773        }
774    }
775
776    #[inline]
777    pub fn convert(
778        &self,
779        mode_from: WritingMode,
780        mode_to: WritingMode,
781        container_size: Size2D<T>,
782    ) -> LogicalPoint<T> {
783        if mode_from == mode_to {
784            self.debug_writing_mode.check(mode_from);
785            *self
786        } else {
787            LogicalPoint::from_physical(
788                mode_to,
789                self.to_physical(mode_from, container_size),
790                container_size,
791            )
792        }
793    }
794}
795
796impl<T: Copy + Add<T, Output = T>> LogicalPoint<T> {
797    /// This doesn’t really makes sense,
798    /// but happens when dealing with multiple origins.
799    #[inline]
800    pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> {
801        self.debug_writing_mode
802            .check_debug(other.debug_writing_mode);
803        LogicalPoint {
804            debug_writing_mode: self.debug_writing_mode,
805            i: self.i + other.i,
806            b: self.b + other.b,
807        }
808    }
809}
810
811impl<T: Copy + Add<T, Output = T>> Add<LogicalSize<T>> for LogicalPoint<T> {
812    type Output = LogicalPoint<T>;
813
814    #[inline]
815    fn add(self, other: LogicalSize<T>) -> LogicalPoint<T> {
816        self.debug_writing_mode
817            .check_debug(other.debug_writing_mode);
818        LogicalPoint {
819            debug_writing_mode: self.debug_writing_mode,
820            i: self.i + other.inline,
821            b: self.b + other.block,
822        }
823    }
824}
825
826impl<T: Copy + Sub<T, Output = T>> Sub<LogicalSize<T>> for LogicalPoint<T> {
827    type Output = LogicalPoint<T>;
828
829    #[inline]
830    fn sub(self, other: LogicalSize<T>) -> LogicalPoint<T> {
831        self.debug_writing_mode
832            .check_debug(other.debug_writing_mode);
833        LogicalPoint {
834            debug_writing_mode: self.debug_writing_mode,
835            i: self.i - other.inline,
836            b: self.b - other.block,
837        }
838    }
839}
840
841/// A "margin" in flow-relative dimensions
842/// Represents the four sides of the margins, borders, or padding of a CSS box,
843/// or a combination of those.
844/// A positive "margin" can be added to a rectangle to obtain a bigger rectangle.
845#[derive(Clone, Copy, Eq, PartialEq)]
846#[cfg_attr(feature = "servo", derive(Serialize))]
847pub struct LogicalMargin<T> {
848    pub block_start: T,
849    pub inline_end: T,
850    pub block_end: T,
851    pub inline_start: T,
852    debug_writing_mode: DebugWritingMode,
853}
854
855impl<T: Debug> Debug for LogicalMargin<T> {
856    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
857        let writing_mode_string = if cfg!(debug_assertions) {
858            format!("{:?}, ", self.debug_writing_mode)
859        } else {
860            "".to_owned()
861        };
862
863        write!(
864            formatter,
865            "LogicalMargin({}i:{:?}..{:?} b:{:?}..{:?})",
866            writing_mode_string,
867            self.inline_start,
868            self.inline_end,
869            self.block_start,
870            self.block_end
871        )
872    }
873}
874
875impl<T: Zero> LogicalMargin<T> {
876    #[inline]
877    pub fn zero(mode: WritingMode) -> LogicalMargin<T> {
878        LogicalMargin {
879            block_start: Zero::zero(),
880            inline_end: Zero::zero(),
881            block_end: Zero::zero(),
882            inline_start: Zero::zero(),
883            debug_writing_mode: DebugWritingMode::new(mode),
884        }
885    }
886}
887
888impl<T> LogicalMargin<T> {
889    #[inline]
890    pub fn new(
891        mode: WritingMode,
892        block_start: T,
893        inline_end: T,
894        block_end: T,
895        inline_start: T,
896    ) -> LogicalMargin<T> {
897        LogicalMargin {
898            block_start,
899            inline_end,
900            block_end,
901            inline_start,
902            debug_writing_mode: DebugWritingMode::new(mode),
903        }
904    }
905
906    #[inline]
907    pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> {
908        let block_start;
909        let inline_end;
910        let block_end;
911        let inline_start;
912        if mode.is_vertical() {
913            if mode.is_vertical_lr() {
914                block_start = offsets.left;
915                block_end = offsets.right;
916            } else {
917                block_start = offsets.right;
918                block_end = offsets.left;
919            }
920            if mode.is_inline_tb() {
921                inline_start = offsets.top;
922                inline_end = offsets.bottom;
923            } else {
924                inline_start = offsets.bottom;
925                inline_end = offsets.top;
926            }
927        } else {
928            block_start = offsets.top;
929            block_end = offsets.bottom;
930            if mode.is_bidi_ltr() {
931                inline_start = offsets.left;
932                inline_end = offsets.right;
933            } else {
934                inline_start = offsets.right;
935                inline_end = offsets.left;
936            }
937        }
938        LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start)
939    }
940}
941
942impl<T: Clone> LogicalMargin<T> {
943    #[inline]
944    pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> {
945        LogicalMargin::new(mode, value.clone(), value.clone(), value.clone(), value)
946    }
947
948    #[inline]
949    pub fn top(&self, mode: WritingMode) -> T {
950        self.debug_writing_mode.check(mode);
951        if mode.is_vertical() {
952            if mode.is_inline_tb() {
953                self.inline_start.clone()
954            } else {
955                self.inline_end.clone()
956            }
957        } else {
958            self.block_start.clone()
959        }
960    }
961
962    #[inline]
963    pub fn set_top(&mut self, mode: WritingMode, top: T) {
964        self.debug_writing_mode.check(mode);
965        if mode.is_vertical() {
966            if mode.is_inline_tb() {
967                self.inline_start = top
968            } else {
969                self.inline_end = top
970            }
971        } else {
972            self.block_start = top
973        }
974    }
975
976    #[inline]
977    pub fn right(&self, mode: WritingMode) -> T {
978        self.debug_writing_mode.check(mode);
979        if mode.is_vertical() {
980            if mode.is_vertical_lr() {
981                self.block_end.clone()
982            } else {
983                self.block_start.clone()
984            }
985        } else {
986            if mode.is_bidi_ltr() {
987                self.inline_end.clone()
988            } else {
989                self.inline_start.clone()
990            }
991        }
992    }
993
994    #[inline]
995    pub fn set_right(&mut self, mode: WritingMode, right: T) {
996        self.debug_writing_mode.check(mode);
997        if mode.is_vertical() {
998            if mode.is_vertical_lr() {
999                self.block_end = right
1000            } else {
1001                self.block_start = right
1002            }
1003        } else {
1004            if mode.is_bidi_ltr() {
1005                self.inline_end = right
1006            } else {
1007                self.inline_start = right
1008            }
1009        }
1010    }
1011
1012    #[inline]
1013    pub fn bottom(&self, mode: WritingMode) -> T {
1014        self.debug_writing_mode.check(mode);
1015        if mode.is_vertical() {
1016            if mode.is_inline_tb() {
1017                self.inline_end.clone()
1018            } else {
1019                self.inline_start.clone()
1020            }
1021        } else {
1022            self.block_end.clone()
1023        }
1024    }
1025
1026    #[inline]
1027    pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) {
1028        self.debug_writing_mode.check(mode);
1029        if mode.is_vertical() {
1030            if mode.is_inline_tb() {
1031                self.inline_end = bottom
1032            } else {
1033                self.inline_start = bottom
1034            }
1035        } else {
1036            self.block_end = bottom
1037        }
1038    }
1039
1040    #[inline]
1041    pub fn left(&self, mode: WritingMode) -> T {
1042        self.debug_writing_mode.check(mode);
1043        if mode.is_vertical() {
1044            if mode.is_vertical_lr() {
1045                self.block_start.clone()
1046            } else {
1047                self.block_end.clone()
1048            }
1049        } else {
1050            if mode.is_bidi_ltr() {
1051                self.inline_start.clone()
1052            } else {
1053                self.inline_end.clone()
1054            }
1055        }
1056    }
1057
1058    #[inline]
1059    pub fn set_left(&mut self, mode: WritingMode, left: T) {
1060        self.debug_writing_mode.check(mode);
1061        if mode.is_vertical() {
1062            if mode.is_vertical_lr() {
1063                self.block_start = left
1064            } else {
1065                self.block_end = left
1066            }
1067        } else {
1068            if mode.is_bidi_ltr() {
1069                self.inline_start = left
1070            } else {
1071                self.inline_end = left
1072            }
1073        }
1074    }
1075
1076    #[inline]
1077    pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
1078        self.debug_writing_mode.check(mode);
1079        let top;
1080        let right;
1081        let bottom;
1082        let left;
1083        if mode.is_vertical() {
1084            if mode.is_vertical_lr() {
1085                left = self.block_start.clone();
1086                right = self.block_end.clone();
1087            } else {
1088                right = self.block_start.clone();
1089                left = self.block_end.clone();
1090            }
1091            if mode.is_inline_tb() {
1092                top = self.inline_start.clone();
1093                bottom = self.inline_end.clone();
1094            } else {
1095                bottom = self.inline_start.clone();
1096                top = self.inline_end.clone();
1097            }
1098        } else {
1099            top = self.block_start.clone();
1100            bottom = self.block_end.clone();
1101            if mode.is_bidi_ltr() {
1102                left = self.inline_start.clone();
1103                right = self.inline_end.clone();
1104            } else {
1105                right = self.inline_start.clone();
1106                left = self.inline_end.clone();
1107            }
1108        }
1109        SideOffsets2D::new(top, right, bottom, left)
1110    }
1111
1112    #[inline]
1113    pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
1114        if mode_from == mode_to {
1115            self.debug_writing_mode.check(mode_from);
1116            self.clone()
1117        } else {
1118            LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
1119        }
1120    }
1121}
1122
1123impl<T: PartialEq + Zero> LogicalMargin<T> {
1124    #[inline]
1125    pub fn is_zero(&self) -> bool {
1126        self.block_start == Zero::zero()
1127            && self.inline_end == Zero::zero()
1128            && self.block_end == Zero::zero()
1129            && self.inline_start == Zero::zero()
1130    }
1131}
1132
1133impl<T: Copy + Add<T, Output = T>> LogicalMargin<T> {
1134    #[inline]
1135    pub fn inline_start_end(&self) -> T {
1136        self.inline_start + self.inline_end
1137    }
1138
1139    #[inline]
1140    pub fn block_start_end(&self) -> T {
1141        self.block_start + self.block_end
1142    }
1143
1144    #[inline]
1145    pub fn start_end(&self, direction: Direction) -> T {
1146        match direction {
1147            Direction::Inline => self.inline_start + self.inline_end,
1148            Direction::Block => self.block_start + self.block_end,
1149        }
1150    }
1151
1152    #[inline]
1153    pub fn top_bottom(&self, mode: WritingMode) -> T {
1154        self.debug_writing_mode.check(mode);
1155        if mode.is_vertical() {
1156            self.inline_start_end()
1157        } else {
1158            self.block_start_end()
1159        }
1160    }
1161
1162    #[inline]
1163    pub fn left_right(&self, mode: WritingMode) -> T {
1164        self.debug_writing_mode.check(mode);
1165        if mode.is_vertical() {
1166            self.block_start_end()
1167        } else {
1168            self.inline_start_end()
1169        }
1170    }
1171}
1172
1173impl<T: Add<T, Output = T>> Add for LogicalMargin<T> {
1174    type Output = LogicalMargin<T>;
1175
1176    #[inline]
1177    fn add(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1178        self.debug_writing_mode
1179            .check_debug(other.debug_writing_mode);
1180        LogicalMargin {
1181            debug_writing_mode: self.debug_writing_mode,
1182            block_start: self.block_start + other.block_start,
1183            inline_end: self.inline_end + other.inline_end,
1184            block_end: self.block_end + other.block_end,
1185            inline_start: self.inline_start + other.inline_start,
1186        }
1187    }
1188}
1189
1190impl<T: Sub<T, Output = T>> Sub for LogicalMargin<T> {
1191    type Output = LogicalMargin<T>;
1192
1193    #[inline]
1194    fn sub(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1195        self.debug_writing_mode
1196            .check_debug(other.debug_writing_mode);
1197        LogicalMargin {
1198            debug_writing_mode: self.debug_writing_mode,
1199            block_start: self.block_start - other.block_start,
1200            inline_end: self.inline_end - other.inline_end,
1201            block_end: self.block_end - other.block_end,
1202            inline_start: self.inline_start - other.inline_start,
1203        }
1204    }
1205}
1206
1207/// A rectangle in flow-relative dimensions
1208#[derive(Clone, Copy, Eq, PartialEq)]
1209#[cfg_attr(feature = "servo", derive(Serialize))]
1210pub struct LogicalRect<T> {
1211    pub start: LogicalPoint<T>,
1212    pub size: LogicalSize<T>,
1213    debug_writing_mode: DebugWritingMode,
1214}
1215
1216impl<T: Debug> Debug for LogicalRect<T> {
1217    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
1218        let writing_mode_string = if cfg!(debug_assertions) {
1219            format!("{:?}, ", self.debug_writing_mode)
1220        } else {
1221            "".to_owned()
1222        };
1223
1224        write!(
1225            formatter,
1226            "LogicalRect({}i{:?}×b{:?}, @ (i{:?},b{:?}))",
1227            writing_mode_string, self.size.inline, self.size.block, self.start.i, self.start.b
1228        )
1229    }
1230}
1231
1232impl<T: Zero> LogicalRect<T> {
1233    #[inline]
1234    pub fn zero(mode: WritingMode) -> LogicalRect<T> {
1235        LogicalRect {
1236            start: LogicalPoint::zero(mode),
1237            size: LogicalSize::zero(mode),
1238            debug_writing_mode: DebugWritingMode::new(mode),
1239        }
1240    }
1241}
1242
1243impl<T: Copy> LogicalRect<T> {
1244    #[inline]
1245    pub fn new(
1246        mode: WritingMode,
1247        inline_start: T,
1248        block_start: T,
1249        inline: T,
1250        block: T,
1251    ) -> LogicalRect<T> {
1252        LogicalRect {
1253            start: LogicalPoint::new(mode, inline_start, block_start),
1254            size: LogicalSize::new(mode, inline, block),
1255            debug_writing_mode: DebugWritingMode::new(mode),
1256        }
1257    }
1258
1259    #[inline]
1260    pub fn from_point_size(
1261        mode: WritingMode,
1262        start: LogicalPoint<T>,
1263        size: LogicalSize<T>,
1264    ) -> LogicalRect<T> {
1265        start.debug_writing_mode.check(mode);
1266        size.debug_writing_mode.check(mode);
1267        LogicalRect {
1268            start: start,
1269            size: size,
1270            debug_writing_mode: DebugWritingMode::new(mode),
1271        }
1272    }
1273}
1274
1275impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1276    #[inline]
1277    pub fn from_physical(
1278        mode: WritingMode,
1279        rect: Rect<T>,
1280        container_size: Size2D<T>,
1281    ) -> LogicalRect<T> {
1282        let inline_start;
1283        let block_start;
1284        let inline;
1285        let block;
1286        if mode.is_vertical() {
1287            inline = rect.size.height;
1288            block = rect.size.width;
1289            if mode.is_vertical_lr() {
1290                block_start = rect.origin.x;
1291            } else {
1292                block_start = container_size.width - (rect.origin.x + rect.size.width);
1293            }
1294            if mode.is_inline_tb() {
1295                inline_start = rect.origin.y;
1296            } else {
1297                inline_start = container_size.height - (rect.origin.y + rect.size.height);
1298            }
1299        } else {
1300            inline = rect.size.width;
1301            block = rect.size.height;
1302            block_start = rect.origin.y;
1303            if mode.is_bidi_ltr() {
1304                inline_start = rect.origin.x;
1305            } else {
1306                inline_start = container_size.width - (rect.origin.x + rect.size.width);
1307            }
1308        }
1309        LogicalRect {
1310            start: LogicalPoint::new(mode, inline_start, block_start),
1311            size: LogicalSize::new(mode, inline, block),
1312            debug_writing_mode: DebugWritingMode::new(mode),
1313        }
1314    }
1315
1316    #[inline]
1317    pub fn inline_end(&self) -> T {
1318        self.start.i + self.size.inline
1319    }
1320
1321    #[inline]
1322    pub fn block_end(&self) -> T {
1323        self.start.b + self.size.block
1324    }
1325
1326    #[inline]
1327    pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Rect<T> {
1328        self.debug_writing_mode.check(mode);
1329        let x;
1330        let y;
1331        let width;
1332        let height;
1333        if mode.is_vertical() {
1334            width = self.size.block;
1335            height = self.size.inline;
1336            if mode.is_vertical_lr() {
1337                x = self.start.b;
1338            } else {
1339                x = container_size.width - self.block_end();
1340            }
1341            if mode.is_inline_tb() {
1342                y = self.start.i;
1343            } else {
1344                y = container_size.height - self.inline_end();
1345            }
1346        } else {
1347            width = self.size.inline;
1348            height = self.size.block;
1349            y = self.start.b;
1350            if mode.is_bidi_ltr() {
1351                x = self.start.i;
1352            } else {
1353                x = container_size.width - self.inline_end();
1354            }
1355        }
1356        Rect {
1357            origin: Point2D::new(x, y),
1358            size: Size2D::new(width, height),
1359        }
1360    }
1361
1362    #[inline]
1363    pub fn convert(
1364        &self,
1365        mode_from: WritingMode,
1366        mode_to: WritingMode,
1367        container_size: Size2D<T>,
1368    ) -> LogicalRect<T> {
1369        if mode_from == mode_to {
1370            self.debug_writing_mode.check(mode_from);
1371            *self
1372        } else {
1373            LogicalRect::from_physical(
1374                mode_to,
1375                self.to_physical(mode_from, container_size),
1376                container_size,
1377            )
1378        }
1379    }
1380
1381    pub fn translate_by_size(&self, offset: LogicalSize<T>) -> LogicalRect<T> {
1382        LogicalRect {
1383            start: self.start + offset,
1384            ..*self
1385        }
1386    }
1387
1388    pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> {
1389        LogicalRect {
1390            start: self.start
1391                + LogicalSize {
1392                    inline: offset.i,
1393                    block: offset.b,
1394                    debug_writing_mode: offset.debug_writing_mode,
1395                },
1396            size: self.size,
1397            debug_writing_mode: self.debug_writing_mode,
1398        }
1399    }
1400}
1401
1402impl<T: Copy + Ord + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1403    #[inline]
1404    pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> {
1405        self.debug_writing_mode
1406            .check_debug(other.debug_writing_mode);
1407
1408        let inline_start = min(self.start.i, other.start.i);
1409        let block_start = min(self.start.b, other.start.b);
1410        LogicalRect {
1411            start: LogicalPoint {
1412                i: inline_start,
1413                b: block_start,
1414                debug_writing_mode: self.debug_writing_mode,
1415            },
1416            size: LogicalSize {
1417                inline: max(self.inline_end(), other.inline_end()) - inline_start,
1418                block: max(self.block_end(), other.block_end()) - block_start,
1419                debug_writing_mode: self.debug_writing_mode,
1420            },
1421            debug_writing_mode: self.debug_writing_mode,
1422        }
1423    }
1424}
1425
1426impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Add<LogicalMargin<T>> for LogicalRect<T> {
1427    type Output = LogicalRect<T>;
1428
1429    #[inline]
1430    fn add(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1431        self.debug_writing_mode
1432            .check_debug(other.debug_writing_mode);
1433        LogicalRect {
1434            start: LogicalPoint {
1435                // Growing a rectangle on the start side means pushing its
1436                // start point on the negative direction.
1437                i: self.start.i - other.inline_start,
1438                b: self.start.b - other.block_start,
1439                debug_writing_mode: self.debug_writing_mode,
1440            },
1441            size: LogicalSize {
1442                inline: self.size.inline + other.inline_start_end(),
1443                block: self.size.block + other.block_start_end(),
1444                debug_writing_mode: self.debug_writing_mode,
1445            },
1446            debug_writing_mode: self.debug_writing_mode,
1447        }
1448    }
1449}
1450
1451impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Sub<LogicalMargin<T>> for LogicalRect<T> {
1452    type Output = LogicalRect<T>;
1453
1454    #[inline]
1455    fn sub(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1456        self.debug_writing_mode
1457            .check_debug(other.debug_writing_mode);
1458        LogicalRect {
1459            start: LogicalPoint {
1460                // Shrinking a rectangle on the start side means pushing its
1461                // start point on the positive direction.
1462                i: self.start.i + other.inline_start,
1463                b: self.start.b + other.block_start,
1464                debug_writing_mode: self.debug_writing_mode,
1465            },
1466            size: LogicalSize {
1467                inline: self.size.inline - other.inline_start_end(),
1468                block: self.size.block - other.block_start_end(),
1469                debug_writing_mode: self.debug_writing_mode,
1470            },
1471            debug_writing_mode: self.debug_writing_mode,
1472        }
1473    }
1474}
1475
1476#[derive(Clone, Copy, Debug, PartialEq)]
1477#[repr(u8)]
1478pub enum LogicalAxis {
1479    Block = 0,
1480    Inline,
1481}
1482
1483impl LogicalAxis {
1484    #[inline]
1485    pub fn to_physical(self, wm: WritingMode) -> PhysicalAxis {
1486        if wm.is_horizontal() == (self == Self::Inline) {
1487            PhysicalAxis::Horizontal
1488        } else {
1489            PhysicalAxis::Vertical
1490        }
1491    }
1492}
1493
1494#[derive(Clone, Copy, Debug, PartialEq)]
1495#[repr(u8)]
1496pub enum LogicalSide {
1497    BlockStart = 0,
1498    BlockEnd,
1499    InlineStart,
1500    InlineEnd,
1501}
1502
1503impl LogicalSide {
1504    fn is_block(self) -> bool {
1505        matches!(self, Self::BlockStart | Self::BlockEnd)
1506    }
1507
1508    #[inline]
1509    pub fn to_physical(self, wm: WritingMode) -> PhysicalSide {
1510        // Block mapping depends only on vertical+vertical-lr
1511        static BLOCK_MAPPING: [[PhysicalSide; 2]; 4] = [
1512            [PhysicalSide::Top, PhysicalSide::Bottom], // horizontal-tb
1513            [PhysicalSide::Right, PhysicalSide::Left], // vertical-rl
1514            [PhysicalSide::Bottom, PhysicalSide::Top], // (horizontal-bt)
1515            [PhysicalSide::Left, PhysicalSide::Right], // vertical-lr
1516        ];
1517
1518        if self.is_block() {
1519            let vertical = wm.is_vertical();
1520            let lr = wm.is_vertical_lr();
1521            let index = (vertical as usize) | ((lr as usize) << 1);
1522            return BLOCK_MAPPING[index][self as usize];
1523        }
1524
1525        // start = 0, end = 1
1526        let edge = self as usize - 2;
1527        // Inline axis sides depend on all three of writing-mode, text-orientation and direction,
1528        // which are encoded in the VERTICAL, INLINE_REVERSED, VERTICAL_LR and LINE_INVERTED bits.
1529        //
1530        //   bit 0 = the VERTICAL value
1531        //   bit 1 = the INLINE_REVERSED value
1532        //   bit 2 = the VERTICAL_LR value
1533        //   bit 3 = the LINE_INVERTED value
1534        //
1535        // Note that not all of these combinations can actually be specified via CSS: there is no
1536        // horizontal-bt writing-mode, and no text-orientation value that produces "inverted"
1537        // text. (The former 'sideways-left' value, no longer in the spec, would have produced
1538        // this in vertical-rl mode.)
1539        static INLINE_MAPPING: [[PhysicalSide; 2]; 16] = [
1540            [PhysicalSide::Left, PhysicalSide::Right], // horizontal-tb               ltr
1541            [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-rl                 ltr
1542            [PhysicalSide::Right, PhysicalSide::Left], // horizontal-tb               rtl
1543            [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-rl                 rtl
1544            [PhysicalSide::Right, PhysicalSide::Left], // (horizontal-bt)  (inverted) ltr
1545            [PhysicalSide::Top, PhysicalSide::Bottom], // sideways-lr                 rtl
1546            [PhysicalSide::Left, PhysicalSide::Right], // (horizontal-bt)  (inverted) rtl
1547            [PhysicalSide::Bottom, PhysicalSide::Top], // sideways-lr                 ltr
1548            [PhysicalSide::Left, PhysicalSide::Right], // horizontal-tb    (inverted) rtl
1549            [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-rl      (inverted) rtl
1550            [PhysicalSide::Right, PhysicalSide::Left], // horizontal-tb    (inverted) ltr
1551            [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-rl      (inverted) ltr
1552            [PhysicalSide::Left, PhysicalSide::Right], // (horizontal-bt)             ltr
1553            [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-lr                 ltr
1554            [PhysicalSide::Right, PhysicalSide::Left], // (horizontal-bt)             rtl
1555            [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-lr                 rtl
1556        ];
1557
1558        debug_assert!(
1559            WritingMode::VERTICAL.bits() == 0x01
1560                && WritingMode::INLINE_REVERSED.bits() == 0x02
1561                && WritingMode::VERTICAL_LR.bits() == 0x04
1562                && WritingMode::LINE_INVERTED.bits() == 0x08
1563        );
1564        let index = (wm.bits() & 0xF) as usize;
1565        INLINE_MAPPING[index][edge]
1566    }
1567}
1568
1569#[derive(Clone, Copy, Debug, PartialEq)]
1570#[repr(u8)]
1571pub enum LogicalCorner {
1572    StartStart = 0,
1573    StartEnd,
1574    EndStart,
1575    EndEnd,
1576}
1577
1578impl LogicalCorner {
1579    #[inline]
1580    pub fn to_physical(self, wm: WritingMode) -> PhysicalCorner {
1581        static CORNER_TO_SIDES: [[LogicalSide; 2]; 4] = [
1582            [LogicalSide::BlockStart, LogicalSide::InlineStart],
1583            [LogicalSide::BlockStart, LogicalSide::InlineEnd],
1584            [LogicalSide::BlockEnd, LogicalSide::InlineStart],
1585            [LogicalSide::BlockEnd, LogicalSide::InlineEnd],
1586        ];
1587
1588        let [block, inline] = CORNER_TO_SIDES[self as usize];
1589        let block = block.to_physical(wm);
1590        let inline = inline.to_physical(wm);
1591        PhysicalCorner::from_sides(block, inline)
1592    }
1593}
1594
1595#[derive(Clone, Copy, Debug, PartialEq)]
1596#[repr(u8)]
1597pub enum PhysicalAxis {
1598    Vertical = 0,
1599    Horizontal,
1600}
1601
1602#[derive(Clone, Copy, Debug, PartialEq)]
1603#[repr(u8)]
1604pub enum PhysicalSide {
1605    Top = 0,
1606    Right,
1607    Bottom,
1608    Left,
1609}
1610
1611impl PhysicalSide {
1612    fn orthogonal_to(self, other: Self) -> bool {
1613        matches!(self, Self::Top | Self::Bottom) != matches!(other, Self::Top | Self::Bottom)
1614    }
1615}
1616
1617#[derive(Clone, Copy, Debug, PartialEq)]
1618#[repr(u8)]
1619pub enum PhysicalCorner {
1620    TopLeft = 0,
1621    TopRight,
1622    BottomRight,
1623    BottomLeft,
1624}
1625
1626impl PhysicalCorner {
1627    fn from_sides(a: PhysicalSide, b: PhysicalSide) -> Self {
1628        debug_assert!(a.orthogonal_to(b), "Sides should be orthogonal");
1629        // Only some of these are possible, since we expect only orthogonal values. If the two
1630        // sides were to be parallel, we fall back to returning TopLeft.
1631        const IMPOSSIBLE: PhysicalCorner = PhysicalCorner::TopLeft;
1632        static SIDES_TO_CORNER: [[PhysicalCorner; 4]; 4] = [
1633            [
1634                IMPOSSIBLE,
1635                PhysicalCorner::TopRight,
1636                IMPOSSIBLE,
1637                PhysicalCorner::TopLeft,
1638            ],
1639            [
1640                PhysicalCorner::TopRight,
1641                IMPOSSIBLE,
1642                PhysicalCorner::BottomRight,
1643                IMPOSSIBLE,
1644            ],
1645            [
1646                IMPOSSIBLE,
1647                PhysicalCorner::BottomRight,
1648                IMPOSSIBLE,
1649                PhysicalCorner::BottomLeft,
1650            ],
1651            [
1652                PhysicalCorner::TopLeft,
1653                IMPOSSIBLE,
1654                PhysicalCorner::BottomLeft,
1655                IMPOSSIBLE,
1656            ],
1657        ];
1658        SIDES_TO_CORNER[a as usize][b as usize]
1659    }
1660}