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