Skip to main content

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