tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

logical_geometry.rs (53261B)


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