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