Skip to main content

style/
logical_geometry.rs

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