Skip to main content

style/
logical_geometry.rs

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