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,
520            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,
664            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 if mode.is_bidi_ltr() {
714            self.i
715        } else {
716            container_size.width - self.i
717        }
718    }
719
720    #[inline]
721    pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D<T>) {
722        self.debug_writing_mode.check(mode);
723        if mode.is_vertical() {
724            self.b = if mode.is_vertical_lr() {
725                x
726            } else {
727                container_size.width - x
728            }
729        } else {
730            self.i = if mode.is_bidi_ltr() {
731                x
732            } else {
733                container_size.width - x
734            }
735        }
736    }
737
738    #[inline]
739    pub fn y(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
740        self.debug_writing_mode.check(mode);
741        if mode.is_vertical() {
742            if mode.is_inline_tb() {
743                self.i
744            } else {
745                container_size.height - self.i
746            }
747        } else {
748            self.b
749        }
750    }
751
752    #[inline]
753    pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D<T>) {
754        self.debug_writing_mode.check(mode);
755        if mode.is_vertical() {
756            self.i = if mode.is_inline_tb() {
757                y
758            } else {
759                container_size.height - y
760            }
761        } else {
762            self.b = y
763        }
764    }
765
766    #[inline]
767    pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> {
768        self.debug_writing_mode.check(mode);
769        if mode.is_vertical() {
770            Point2D::new(
771                if mode.is_vertical_lr() {
772                    self.b
773                } else {
774                    container_size.width - self.b
775                },
776                if mode.is_inline_tb() {
777                    self.i
778                } else {
779                    container_size.height - self.i
780                },
781            )
782        } else {
783            Point2D::new(
784                if mode.is_bidi_ltr() {
785                    self.i
786                } else {
787                    container_size.width - self.i
788                },
789                self.b,
790            )
791        }
792    }
793
794    #[inline]
795    pub fn convert(
796        &self,
797        mode_from: WritingMode,
798        mode_to: WritingMode,
799        container_size: Size2D<T>,
800    ) -> LogicalPoint<T> {
801        if mode_from == mode_to {
802            self.debug_writing_mode.check(mode_from);
803            *self
804        } else {
805            LogicalPoint::from_physical(
806                mode_to,
807                self.to_physical(mode_from, container_size),
808                container_size,
809            )
810        }
811    }
812}
813
814impl<T: Copy + Add<T, Output = T>> LogicalPoint<T> {
815    /// This doesn’t really makes sense,
816    /// but happens when dealing with multiple origins.
817    #[inline]
818    pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> {
819        self.debug_writing_mode
820            .check_debug(other.debug_writing_mode);
821        LogicalPoint {
822            debug_writing_mode: self.debug_writing_mode,
823            i: self.i + other.i,
824            b: self.b + other.b,
825        }
826    }
827}
828
829impl<T: Copy + Add<T, Output = T>> Add<LogicalSize<T>> for LogicalPoint<T> {
830    type Output = LogicalPoint<T>;
831
832    #[inline]
833    fn add(self, other: LogicalSize<T>) -> LogicalPoint<T> {
834        self.debug_writing_mode
835            .check_debug(other.debug_writing_mode);
836        LogicalPoint {
837            debug_writing_mode: self.debug_writing_mode,
838            i: self.i + other.inline,
839            b: self.b + other.block,
840        }
841    }
842}
843
844impl<T: Copy + Sub<T, Output = T>> Sub<LogicalSize<T>> for LogicalPoint<T> {
845    type Output = LogicalPoint<T>;
846
847    #[inline]
848    fn sub(self, other: LogicalSize<T>) -> LogicalPoint<T> {
849        self.debug_writing_mode
850            .check_debug(other.debug_writing_mode);
851        LogicalPoint {
852            debug_writing_mode: self.debug_writing_mode,
853            i: self.i - other.inline,
854            b: self.b - other.block,
855        }
856    }
857}
858
859/// A "margin" in flow-relative dimensions
860/// Represents the four sides of the margins, borders, or padding of a CSS box,
861/// or a combination of those.
862/// A positive "margin" can be added to a rectangle to obtain a bigger rectangle.
863#[derive(Clone, Copy, Eq, PartialEq, Serialize)]
864pub struct LogicalMargin<T> {
865    pub block_start: T,
866    pub inline_end: T,
867    pub block_end: T,
868    pub inline_start: T,
869    debug_writing_mode: DebugWritingMode,
870}
871
872impl<T: Debug> Debug for LogicalMargin<T> {
873    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
874        let writing_mode_string = if cfg!(debug_assertions) {
875            format!("{:?}, ", self.debug_writing_mode)
876        } else {
877            "".to_owned()
878        };
879
880        write!(
881            formatter,
882            "LogicalMargin({}i:{:?}..{:?} b:{:?}..{:?})",
883            writing_mode_string,
884            self.inline_start,
885            self.inline_end,
886            self.block_start,
887            self.block_end
888        )
889    }
890}
891
892impl<T: Zero> LogicalMargin<T> {
893    #[inline]
894    pub fn zero(mode: WritingMode) -> LogicalMargin<T> {
895        LogicalMargin {
896            block_start: Zero::zero(),
897            inline_end: Zero::zero(),
898            block_end: Zero::zero(),
899            inline_start: Zero::zero(),
900            debug_writing_mode: DebugWritingMode::new(mode),
901        }
902    }
903}
904
905impl<T> LogicalMargin<T> {
906    #[inline]
907    pub fn new(
908        mode: WritingMode,
909        block_start: T,
910        inline_end: T,
911        block_end: T,
912        inline_start: T,
913    ) -> LogicalMargin<T> {
914        LogicalMargin {
915            block_start,
916            inline_end,
917            block_end,
918            inline_start,
919            debug_writing_mode: DebugWritingMode::new(mode),
920        }
921    }
922
923    #[inline]
924    pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> {
925        let block_start;
926        let inline_end;
927        let block_end;
928        let inline_start;
929        if mode.is_vertical() {
930            if mode.is_vertical_lr() {
931                block_start = offsets.left;
932                block_end = offsets.right;
933            } else {
934                block_start = offsets.right;
935                block_end = offsets.left;
936            }
937            if mode.is_inline_tb() {
938                inline_start = offsets.top;
939                inline_end = offsets.bottom;
940            } else {
941                inline_start = offsets.bottom;
942                inline_end = offsets.top;
943            }
944        } else {
945            block_start = offsets.top;
946            block_end = offsets.bottom;
947            if mode.is_bidi_ltr() {
948                inline_start = offsets.left;
949                inline_end = offsets.right;
950            } else {
951                inline_start = offsets.right;
952                inline_end = offsets.left;
953            }
954        }
955        LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start)
956    }
957}
958
959impl<T: Clone> LogicalMargin<T> {
960    #[inline]
961    pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> {
962        LogicalMargin::new(mode, value.clone(), value.clone(), value.clone(), value)
963    }
964
965    #[inline]
966    pub fn top(&self, mode: WritingMode) -> T {
967        self.debug_writing_mode.check(mode);
968        if mode.is_vertical() {
969            if mode.is_inline_tb() {
970                self.inline_start.clone()
971            } else {
972                self.inline_end.clone()
973            }
974        } else {
975            self.block_start.clone()
976        }
977    }
978
979    #[inline]
980    pub fn set_top(&mut self, mode: WritingMode, top: T) {
981        self.debug_writing_mode.check(mode);
982        if mode.is_vertical() {
983            if mode.is_inline_tb() {
984                self.inline_start = top
985            } else {
986                self.inline_end = top
987            }
988        } else {
989            self.block_start = top
990        }
991    }
992
993    #[inline]
994    pub fn right(&self, mode: WritingMode) -> T {
995        self.debug_writing_mode.check(mode);
996        if mode.is_vertical() {
997            if mode.is_vertical_lr() {
998                self.block_end.clone()
999            } else {
1000                self.block_start.clone()
1001            }
1002        } else if mode.is_bidi_ltr() {
1003            self.inline_end.clone()
1004        } else {
1005            self.inline_start.clone()
1006        }
1007    }
1008
1009    #[inline]
1010    pub fn set_right(&mut self, mode: WritingMode, right: T) {
1011        self.debug_writing_mode.check(mode);
1012        if mode.is_vertical() {
1013            if mode.is_vertical_lr() {
1014                self.block_end = right
1015            } else {
1016                self.block_start = right
1017            }
1018        } else if mode.is_bidi_ltr() {
1019            self.inline_end = right
1020        } else {
1021            self.inline_start = right
1022        }
1023    }
1024
1025    #[inline]
1026    pub fn bottom(&self, mode: WritingMode) -> T {
1027        self.debug_writing_mode.check(mode);
1028        if mode.is_vertical() {
1029            if mode.is_inline_tb() {
1030                self.inline_end.clone()
1031            } else {
1032                self.inline_start.clone()
1033            }
1034        } else {
1035            self.block_end.clone()
1036        }
1037    }
1038
1039    #[inline]
1040    pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) {
1041        self.debug_writing_mode.check(mode);
1042        if mode.is_vertical() {
1043            if mode.is_inline_tb() {
1044                self.inline_end = bottom
1045            } else {
1046                self.inline_start = bottom
1047            }
1048        } else {
1049            self.block_end = bottom
1050        }
1051    }
1052
1053    #[inline]
1054    pub fn left(&self, mode: WritingMode) -> T {
1055        self.debug_writing_mode.check(mode);
1056        if mode.is_vertical() {
1057            if mode.is_vertical_lr() {
1058                self.block_start.clone()
1059            } else {
1060                self.block_end.clone()
1061            }
1062        } else if mode.is_bidi_ltr() {
1063            self.inline_start.clone()
1064        } else {
1065            self.inline_end.clone()
1066        }
1067    }
1068
1069    #[inline]
1070    pub fn set_left(&mut self, mode: WritingMode, left: T) {
1071        self.debug_writing_mode.check(mode);
1072        if mode.is_vertical() {
1073            if mode.is_vertical_lr() {
1074                self.block_start = left
1075            } else {
1076                self.block_end = left
1077            }
1078        } else if mode.is_bidi_ltr() {
1079            self.inline_start = left
1080        } else {
1081            self.inline_end = left
1082        }
1083    }
1084
1085    #[inline]
1086    pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
1087        self.debug_writing_mode.check(mode);
1088        let top;
1089        let right;
1090        let bottom;
1091        let left;
1092        if mode.is_vertical() {
1093            if mode.is_vertical_lr() {
1094                left = self.block_start.clone();
1095                right = self.block_end.clone();
1096            } else {
1097                right = self.block_start.clone();
1098                left = self.block_end.clone();
1099            }
1100            if mode.is_inline_tb() {
1101                top = self.inline_start.clone();
1102                bottom = self.inline_end.clone();
1103            } else {
1104                bottom = self.inline_start.clone();
1105                top = self.inline_end.clone();
1106            }
1107        } else {
1108            top = self.block_start.clone();
1109            bottom = self.block_end.clone();
1110            if mode.is_bidi_ltr() {
1111                left = self.inline_start.clone();
1112                right = self.inline_end.clone();
1113            } else {
1114                right = self.inline_start.clone();
1115                left = self.inline_end.clone();
1116            }
1117        }
1118        SideOffsets2D::new(top, right, bottom, left)
1119    }
1120
1121    #[inline]
1122    pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
1123        if mode_from == mode_to {
1124            self.debug_writing_mode.check(mode_from);
1125            self.clone()
1126        } else {
1127            LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
1128        }
1129    }
1130}
1131
1132impl<T: PartialEq + Zero> LogicalMargin<T> {
1133    #[inline]
1134    pub fn is_zero(&self) -> bool {
1135        self.block_start == Zero::zero()
1136            && self.inline_end == Zero::zero()
1137            && self.block_end == Zero::zero()
1138            && self.inline_start == Zero::zero()
1139    }
1140}
1141
1142impl<T: Copy + Add<T, Output = T>> LogicalMargin<T> {
1143    #[inline]
1144    pub fn inline_start_end(&self) -> T {
1145        self.inline_start + self.inline_end
1146    }
1147
1148    #[inline]
1149    pub fn block_start_end(&self) -> T {
1150        self.block_start + self.block_end
1151    }
1152
1153    #[inline]
1154    pub fn start_end(&self, direction: Direction) -> T {
1155        match direction {
1156            Direction::Inline => self.inline_start + self.inline_end,
1157            Direction::Block => self.block_start + self.block_end,
1158        }
1159    }
1160
1161    #[inline]
1162    pub fn top_bottom(&self, mode: WritingMode) -> T {
1163        self.debug_writing_mode.check(mode);
1164        if mode.is_vertical() {
1165            self.inline_start_end()
1166        } else {
1167            self.block_start_end()
1168        }
1169    }
1170
1171    #[inline]
1172    pub fn left_right(&self, mode: WritingMode) -> T {
1173        self.debug_writing_mode.check(mode);
1174        if mode.is_vertical() {
1175            self.block_start_end()
1176        } else {
1177            self.inline_start_end()
1178        }
1179    }
1180}
1181
1182impl<T: Add<T, Output = T>> Add for LogicalMargin<T> {
1183    type Output = LogicalMargin<T>;
1184
1185    #[inline]
1186    fn add(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1187        self.debug_writing_mode
1188            .check_debug(other.debug_writing_mode);
1189        LogicalMargin {
1190            debug_writing_mode: self.debug_writing_mode,
1191            block_start: self.block_start + other.block_start,
1192            inline_end: self.inline_end + other.inline_end,
1193            block_end: self.block_end + other.block_end,
1194            inline_start: self.inline_start + other.inline_start,
1195        }
1196    }
1197}
1198
1199impl<T: Sub<T, Output = T>> Sub for LogicalMargin<T> {
1200    type Output = LogicalMargin<T>;
1201
1202    #[inline]
1203    fn sub(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1204        self.debug_writing_mode
1205            .check_debug(other.debug_writing_mode);
1206        LogicalMargin {
1207            debug_writing_mode: self.debug_writing_mode,
1208            block_start: self.block_start - other.block_start,
1209            inline_end: self.inline_end - other.inline_end,
1210            block_end: self.block_end - other.block_end,
1211            inline_start: self.inline_start - other.inline_start,
1212        }
1213    }
1214}
1215
1216/// A rectangle in flow-relative dimensions
1217#[derive(Clone, Copy, Eq, PartialEq, Serialize)]
1218pub struct LogicalRect<T> {
1219    pub start: LogicalPoint<T>,
1220    pub size: LogicalSize<T>,
1221    debug_writing_mode: DebugWritingMode,
1222}
1223
1224impl<T: Debug> Debug for LogicalRect<T> {
1225    fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
1226        let writing_mode_string = if cfg!(debug_assertions) {
1227            format!("{:?}, ", self.debug_writing_mode)
1228        } else {
1229            "".to_owned()
1230        };
1231
1232        write!(
1233            formatter,
1234            "LogicalRect({}i{:?}×b{:?}, @ (i{:?},b{:?}))",
1235            writing_mode_string, self.size.inline, self.size.block, self.start.i, self.start.b
1236        )
1237    }
1238}
1239
1240impl<T: Zero> LogicalRect<T> {
1241    #[inline]
1242    pub fn zero(mode: WritingMode) -> LogicalRect<T> {
1243        LogicalRect {
1244            start: LogicalPoint::zero(mode),
1245            size: LogicalSize::zero(mode),
1246            debug_writing_mode: DebugWritingMode::new(mode),
1247        }
1248    }
1249}
1250
1251impl<T: Copy> LogicalRect<T> {
1252    #[inline]
1253    pub fn new(
1254        mode: WritingMode,
1255        inline_start: T,
1256        block_start: T,
1257        inline: T,
1258        block: T,
1259    ) -> LogicalRect<T> {
1260        LogicalRect {
1261            start: LogicalPoint::new(mode, inline_start, block_start),
1262            size: LogicalSize::new(mode, inline, block),
1263            debug_writing_mode: DebugWritingMode::new(mode),
1264        }
1265    }
1266
1267    #[inline]
1268    pub fn from_point_size(
1269        mode: WritingMode,
1270        start: LogicalPoint<T>,
1271        size: LogicalSize<T>,
1272    ) -> LogicalRect<T> {
1273        start.debug_writing_mode.check(mode);
1274        size.debug_writing_mode.check(mode);
1275        LogicalRect {
1276            start,
1277            size,
1278            debug_writing_mode: DebugWritingMode::new(mode),
1279        }
1280    }
1281}
1282
1283impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1284    #[inline]
1285    pub fn from_physical(
1286        mode: WritingMode,
1287        rect: Rect<T>,
1288        container_size: Size2D<T>,
1289    ) -> LogicalRect<T> {
1290        let inline_start;
1291        let block_start;
1292        let inline;
1293        let block;
1294        if mode.is_vertical() {
1295            inline = rect.size.height;
1296            block = rect.size.width;
1297            if mode.is_vertical_lr() {
1298                block_start = rect.origin.x;
1299            } else {
1300                block_start = container_size.width - (rect.origin.x + rect.size.width);
1301            }
1302            if mode.is_inline_tb() {
1303                inline_start = rect.origin.y;
1304            } else {
1305                inline_start = container_size.height - (rect.origin.y + rect.size.height);
1306            }
1307        } else {
1308            inline = rect.size.width;
1309            block = rect.size.height;
1310            block_start = rect.origin.y;
1311            if mode.is_bidi_ltr() {
1312                inline_start = rect.origin.x;
1313            } else {
1314                inline_start = container_size.width - (rect.origin.x + rect.size.width);
1315            }
1316        }
1317        LogicalRect {
1318            start: LogicalPoint::new(mode, inline_start, block_start),
1319            size: LogicalSize::new(mode, inline, block),
1320            debug_writing_mode: DebugWritingMode::new(mode),
1321        }
1322    }
1323
1324    #[inline]
1325    pub fn inline_end(&self) -> T {
1326        self.start.i + self.size.inline
1327    }
1328
1329    #[inline]
1330    pub fn block_end(&self) -> T {
1331        self.start.b + self.size.block
1332    }
1333
1334    #[inline]
1335    pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Rect<T> {
1336        self.debug_writing_mode.check(mode);
1337        let x;
1338        let y;
1339        let width;
1340        let height;
1341        if mode.is_vertical() {
1342            width = self.size.block;
1343            height = self.size.inline;
1344            if mode.is_vertical_lr() {
1345                x = self.start.b;
1346            } else {
1347                x = container_size.width - self.block_end();
1348            }
1349            if mode.is_inline_tb() {
1350                y = self.start.i;
1351            } else {
1352                y = container_size.height - self.inline_end();
1353            }
1354        } else {
1355            width = self.size.inline;
1356            height = self.size.block;
1357            y = self.start.b;
1358            if mode.is_bidi_ltr() {
1359                x = self.start.i;
1360            } else {
1361                x = container_size.width - self.inline_end();
1362            }
1363        }
1364        Rect {
1365            origin: Point2D::new(x, y),
1366            size: Size2D::new(width, height),
1367        }
1368    }
1369
1370    #[inline]
1371    pub fn convert(
1372        &self,
1373        mode_from: WritingMode,
1374        mode_to: WritingMode,
1375        container_size: Size2D<T>,
1376    ) -> LogicalRect<T> {
1377        if mode_from == mode_to {
1378            self.debug_writing_mode.check(mode_from);
1379            *self
1380        } else {
1381            LogicalRect::from_physical(
1382                mode_to,
1383                self.to_physical(mode_from, container_size),
1384                container_size,
1385            )
1386        }
1387    }
1388
1389    pub fn translate_by_size(&self, offset: LogicalSize<T>) -> LogicalRect<T> {
1390        LogicalRect {
1391            start: self.start + offset,
1392            ..*self
1393        }
1394    }
1395
1396    pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> {
1397        LogicalRect {
1398            start: self.start
1399                + LogicalSize {
1400                    inline: offset.i,
1401                    block: offset.b,
1402                    debug_writing_mode: offset.debug_writing_mode,
1403                },
1404            size: self.size,
1405            debug_writing_mode: self.debug_writing_mode,
1406        }
1407    }
1408}
1409
1410impl<T: Copy + Ord + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1411    #[inline]
1412    pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> {
1413        self.debug_writing_mode
1414            .check_debug(other.debug_writing_mode);
1415
1416        let inline_start = min(self.start.i, other.start.i);
1417        let block_start = min(self.start.b, other.start.b);
1418        LogicalRect {
1419            start: LogicalPoint {
1420                i: inline_start,
1421                b: block_start,
1422                debug_writing_mode: self.debug_writing_mode,
1423            },
1424            size: LogicalSize {
1425                inline: max(self.inline_end(), other.inline_end()) - inline_start,
1426                block: max(self.block_end(), other.block_end()) - block_start,
1427                debug_writing_mode: self.debug_writing_mode,
1428            },
1429            debug_writing_mode: self.debug_writing_mode,
1430        }
1431    }
1432}
1433
1434impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Add<LogicalMargin<T>> for LogicalRect<T> {
1435    type Output = LogicalRect<T>;
1436
1437    #[inline]
1438    fn add(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1439        self.debug_writing_mode
1440            .check_debug(other.debug_writing_mode);
1441        LogicalRect {
1442            start: LogicalPoint {
1443                // Growing a rectangle on the start side means pushing its
1444                // start point on the negative direction.
1445                i: self.start.i - other.inline_start,
1446                b: self.start.b - other.block_start,
1447                debug_writing_mode: self.debug_writing_mode,
1448            },
1449            size: LogicalSize {
1450                inline: self.size.inline + other.inline_start_end(),
1451                block: self.size.block + other.block_start_end(),
1452                debug_writing_mode: self.debug_writing_mode,
1453            },
1454            debug_writing_mode: self.debug_writing_mode,
1455        }
1456    }
1457}
1458
1459impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Sub<LogicalMargin<T>> for LogicalRect<T> {
1460    type Output = LogicalRect<T>;
1461
1462    #[inline]
1463    fn sub(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1464        self.debug_writing_mode
1465            .check_debug(other.debug_writing_mode);
1466        LogicalRect {
1467            start: LogicalPoint {
1468                // Shrinking a rectangle on the start side means pushing its
1469                // start point on the positive direction.
1470                i: self.start.i + other.inline_start,
1471                b: self.start.b + other.block_start,
1472                debug_writing_mode: self.debug_writing_mode,
1473            },
1474            size: LogicalSize {
1475                inline: self.size.inline - other.inline_start_end(),
1476                block: self.size.block - other.block_start_end(),
1477                debug_writing_mode: self.debug_writing_mode,
1478            },
1479            debug_writing_mode: self.debug_writing_mode,
1480        }
1481    }
1482}
1483
1484#[derive(Clone, Copy, Debug, PartialEq)]
1485#[repr(u8)]
1486pub enum LogicalAxis {
1487    Block = 0,
1488    Inline,
1489}
1490
1491impl LogicalAxis {
1492    #[inline]
1493    pub fn to_physical(self, wm: WritingMode) -> PhysicalAxis {
1494        if wm.is_horizontal() == (self == Self::Inline) {
1495            PhysicalAxis::Horizontal
1496        } else {
1497            PhysicalAxis::Vertical
1498        }
1499    }
1500}
1501
1502#[derive(Clone, Copy, Debug, PartialEq)]
1503#[repr(u8)]
1504pub enum LogicalSide {
1505    BlockStart = 0,
1506    BlockEnd,
1507    InlineStart,
1508    InlineEnd,
1509}
1510
1511impl LogicalSide {
1512    fn is_block(self) -> bool {
1513        matches!(self, Self::BlockStart | Self::BlockEnd)
1514    }
1515
1516    #[inline]
1517    pub fn to_physical(self, wm: WritingMode) -> PhysicalSide {
1518        // Block mapping depends only on vertical+vertical-lr
1519        static BLOCK_MAPPING: [[PhysicalSide; 2]; 4] = [
1520            [PhysicalSide::Top, PhysicalSide::Bottom], // horizontal-tb
1521            [PhysicalSide::Right, PhysicalSide::Left], // vertical-rl
1522            [PhysicalSide::Bottom, PhysicalSide::Top], // (horizontal-bt)
1523            [PhysicalSide::Left, PhysicalSide::Right], // vertical-lr
1524        ];
1525
1526        if self.is_block() {
1527            let vertical = wm.is_vertical();
1528            let lr = wm.is_vertical_lr();
1529            let index = (vertical as usize) | ((lr as usize) << 1);
1530            return BLOCK_MAPPING[index][self as usize];
1531        }
1532
1533        // start = 0, end = 1
1534        let edge = self as usize - 2;
1535        // Inline axis sides depend on all three of writing-mode, text-orientation and direction,
1536        // which are encoded in the VERTICAL, INLINE_REVERSED, VERTICAL_LR and LINE_INVERTED bits.
1537        //
1538        //   bit 0 = the VERTICAL value
1539        //   bit 1 = the INLINE_REVERSED value
1540        //   bit 2 = the VERTICAL_LR value
1541        //   bit 3 = the LINE_INVERTED value
1542        //
1543        // Note that not all of these combinations can actually be specified via CSS: there is no
1544        // horizontal-bt writing-mode, and no text-orientation value that produces "inverted"
1545        // text. (The former 'sideways-left' value, no longer in the spec, would have produced
1546        // this in vertical-rl mode.)
1547        static INLINE_MAPPING: [[PhysicalSide; 2]; 16] = [
1548            [PhysicalSide::Left, PhysicalSide::Right], // horizontal-tb               ltr
1549            [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-rl                 ltr
1550            [PhysicalSide::Right, PhysicalSide::Left], // horizontal-tb               rtl
1551            [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-rl                 rtl
1552            [PhysicalSide::Right, PhysicalSide::Left], // (horizontal-bt)  (inverted) ltr
1553            [PhysicalSide::Top, PhysicalSide::Bottom], // sideways-lr                 rtl
1554            [PhysicalSide::Left, PhysicalSide::Right], // (horizontal-bt)  (inverted) rtl
1555            [PhysicalSide::Bottom, PhysicalSide::Top], // sideways-lr                 ltr
1556            [PhysicalSide::Left, PhysicalSide::Right], // horizontal-tb    (inverted) rtl
1557            [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-rl      (inverted) rtl
1558            [PhysicalSide::Right, PhysicalSide::Left], // horizontal-tb    (inverted) ltr
1559            [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-rl      (inverted) ltr
1560            [PhysicalSide::Left, PhysicalSide::Right], // (horizontal-bt)             ltr
1561            [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-lr                 ltr
1562            [PhysicalSide::Right, PhysicalSide::Left], // (horizontal-bt)             rtl
1563            [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-lr                 rtl
1564        ];
1565
1566        debug_assert!(
1567            WritingMode::VERTICAL.bits() == 0x01
1568                && WritingMode::INLINE_REVERSED.bits() == 0x02
1569                && WritingMode::VERTICAL_LR.bits() == 0x04
1570                && WritingMode::LINE_INVERTED.bits() == 0x08
1571        );
1572        let index = (wm.bits() & 0xF) as usize;
1573        INLINE_MAPPING[index][edge]
1574    }
1575}
1576
1577#[derive(Clone, Copy, Debug, PartialEq)]
1578#[repr(u8)]
1579pub enum LogicalCorner {
1580    StartStart = 0,
1581    StartEnd,
1582    EndStart,
1583    EndEnd,
1584}
1585
1586impl LogicalCorner {
1587    #[inline]
1588    pub fn to_physical(self, wm: WritingMode) -> PhysicalCorner {
1589        static CORNER_TO_SIDES: [[LogicalSide; 2]; 4] = [
1590            [LogicalSide::BlockStart, LogicalSide::InlineStart],
1591            [LogicalSide::BlockStart, LogicalSide::InlineEnd],
1592            [LogicalSide::BlockEnd, LogicalSide::InlineStart],
1593            [LogicalSide::BlockEnd, LogicalSide::InlineEnd],
1594        ];
1595
1596        let [block, inline] = CORNER_TO_SIDES[self as usize];
1597        let block = block.to_physical(wm);
1598        let inline = inline.to_physical(wm);
1599        PhysicalCorner::from_sides(block, inline)
1600    }
1601}
1602
1603#[derive(Clone, Copy, Debug, PartialEq)]
1604#[repr(u8)]
1605pub enum PhysicalAxis {
1606    Vertical = 0,
1607    Horizontal,
1608}
1609
1610#[derive(Clone, Copy, Debug, PartialEq)]
1611#[repr(u8)]
1612pub enum PhysicalSide {
1613    Top = 0,
1614    Right,
1615    Bottom,
1616    Left,
1617}
1618
1619impl PhysicalSide {
1620    /// Returns whether one physical side is parallel to another.
1621    pub fn parallel_to(self, other: Self) -> bool {
1622        !self.orthogonal_to(other)
1623    }
1624
1625    /// Returns whether one physical side is orthogonal to another.
1626    pub fn orthogonal_to(self, other: Self) -> bool {
1627        matches!(self, Self::Top | Self::Bottom) != matches!(other, Self::Top | Self::Bottom)
1628    }
1629
1630    /// Returns the opposite side.
1631    pub fn opposite_side(self) -> Self {
1632        match self {
1633            Self::Top => Self::Bottom,
1634            Self::Right => Self::Left,
1635            Self::Bottom => Self::Top,
1636            Self::Left => Self::Right,
1637        }
1638    }
1639}
1640
1641#[derive(Clone, Copy, Debug, PartialEq)]
1642#[repr(u8)]
1643pub enum PhysicalCorner {
1644    TopLeft = 0,
1645    TopRight,
1646    BottomRight,
1647    BottomLeft,
1648}
1649
1650impl PhysicalCorner {
1651    fn from_sides(a: PhysicalSide, b: PhysicalSide) -> Self {
1652        debug_assert!(a.orthogonal_to(b), "Sides should be orthogonal");
1653        // Only some of these are possible, since we expect only orthogonal values. If the two
1654        // sides were to be parallel, we fall back to returning TopLeft.
1655        const IMPOSSIBLE: PhysicalCorner = PhysicalCorner::TopLeft;
1656        static SIDES_TO_CORNER: [[PhysicalCorner; 4]; 4] = [
1657            [
1658                IMPOSSIBLE,
1659                PhysicalCorner::TopRight,
1660                IMPOSSIBLE,
1661                PhysicalCorner::TopLeft,
1662            ],
1663            [
1664                PhysicalCorner::TopRight,
1665                IMPOSSIBLE,
1666                PhysicalCorner::BottomRight,
1667                IMPOSSIBLE,
1668            ],
1669            [
1670                IMPOSSIBLE,
1671                PhysicalCorner::BottomRight,
1672                IMPOSSIBLE,
1673                PhysicalCorner::BottomLeft,
1674            ],
1675            [
1676                PhysicalCorner::TopLeft,
1677                IMPOSSIBLE,
1678                PhysicalCorner::BottomLeft,
1679                IMPOSSIBLE,
1680            ],
1681        ];
1682        SIDES_TO_CORNER[a as usize][b as usize]
1683    }
1684}