style/
logical_geometry.rs

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