style/
logical_geometry.rs

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