1use crate::properties::style_structs;
8use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D};
9use euclid::num::Zero;
10use std::cmp::{max, min};
11use std::fmt::{self, Debug, Error, Formatter};
12use std::ops::{Add, Sub};
13
14pub enum BlockFlowDirection {
15 TopToBottom,
16 RightToLeft,
17 LeftToRight,
18}
19
20pub enum InlineBaseDirection {
21 LeftToRight,
22 RightToLeft,
23}
24
25#[allow(missing_docs)]
29#[derive(
30 Clone,
31 Copy,
32 Debug,
33 Eq,
34 FromPrimitive,
35 MallocSizeOf,
36 Parse,
37 PartialEq,
38 SpecifiedValueInfo,
39 ToComputedValue,
40 ToCss,
41 ToResolvedValue,
42 ToShmem,
43 ToTyped,
44)]
45#[repr(u8)]
46pub enum WritingModeProperty {
47 #[parse(aliases = "lr,lr-tb,rl,rl-tb")]
48 HorizontalTb,
49 #[parse(aliases = "tb,tb-rl")]
50 VerticalRl,
51 VerticalLr,
52 #[cfg(feature = "gecko")]
53 SidewaysRl,
54 #[cfg(feature = "gecko")]
55 SidewaysLr,
56}
57
58#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Serialize)]
60#[repr(C)]
61pub struct WritingMode(u8);
62bitflags!(
63 impl WritingMode: u8 {
64 const VERTICAL = 1 << 0;
67 const INLINE_REVERSED = 1 << 1;
74 const VERTICAL_LR = 1 << 2;
79 const LINE_INVERTED = 1 << 3;
84 const RTL = 1 << 4;
86 const VERTICAL_SIDEWAYS = 1 << 5;
94 const TEXT_SIDEWAYS = 1 << 6;
102 const UPRIGHT = 1 << 7;
110 const WRITING_MODE_HORIZONTAL_TB = 0;
114 const WRITING_MODE_VERTICAL_RL = WritingMode::VERTICAL.bits();
116 const WRITING_MODE_VERTICAL_LR = WritingMode::VERTICAL.bits() |
118 WritingMode::VERTICAL_LR.bits() |
119 WritingMode::LINE_INVERTED.bits();
120 const WRITING_MODE_SIDEWAYS_RL = WritingMode::VERTICAL.bits() |
122 WritingMode::VERTICAL_SIDEWAYS.bits();
123 const WRITING_MODE_SIDEWAYS_LR = WritingMode::VERTICAL.bits() |
125 WritingMode::VERTICAL_LR.bits() |
126 WritingMode::VERTICAL_SIDEWAYS.bits();
127 }
128);
129
130impl WritingMode {
131 pub fn new(inheritedbox_style: &style_structs::InheritedBox) -> Self {
133 use crate::properties::longhands::direction::computed_value::T as Direction;
134
135 let mut flags = WritingMode::empty();
136
137 let direction = inheritedbox_style.clone_direction();
138 let writing_mode = inheritedbox_style.clone_writing_mode();
139
140 match direction {
141 Direction::Ltr => {},
142 Direction::Rtl => {
143 flags.insert(WritingMode::RTL);
144 },
145 }
146
147 match writing_mode {
148 WritingModeProperty::HorizontalTb => {
149 if direction == Direction::Rtl {
150 flags.insert(WritingMode::INLINE_REVERSED);
151 }
152 },
153 WritingModeProperty::VerticalRl => {
154 flags.insert(WritingMode::WRITING_MODE_VERTICAL_RL);
155 if direction == Direction::Rtl {
156 flags.insert(WritingMode::INLINE_REVERSED);
157 }
158 },
159 WritingModeProperty::VerticalLr => {
160 flags.insert(WritingMode::WRITING_MODE_VERTICAL_LR);
161 if direction == Direction::Rtl {
162 flags.insert(WritingMode::INLINE_REVERSED);
163 }
164 },
165 #[cfg(feature = "gecko")]
166 WritingModeProperty::SidewaysRl => {
167 flags.insert(WritingMode::WRITING_MODE_SIDEWAYS_RL);
168 if direction == Direction::Rtl {
169 flags.insert(WritingMode::INLINE_REVERSED);
170 }
171 },
172 #[cfg(feature = "gecko")]
173 WritingModeProperty::SidewaysLr => {
174 flags.insert(WritingMode::WRITING_MODE_SIDEWAYS_LR);
175 if direction == Direction::Ltr {
176 flags.insert(WritingMode::INLINE_REVERSED);
177 }
178 },
179 }
180
181 #[cfg(feature = "gecko")]
182 {
183 use crate::properties::longhands::text_orientation::computed_value::T as TextOrientation;
184
185 match writing_mode {
188 WritingModeProperty::VerticalRl | WritingModeProperty::VerticalLr => {
189 match inheritedbox_style.clone_text_orientation() {
190 TextOrientation::Mixed => {},
191 TextOrientation::Upright => {
192 flags.insert(WritingMode::UPRIGHT);
193
194 flags.remove(WritingMode::RTL);
201 flags.remove(WritingMode::INLINE_REVERSED);
202 },
203 TextOrientation::Sideways => {
204 flags.insert(WritingMode::TEXT_SIDEWAYS);
205 },
206 }
207 },
208 _ => {},
209 }
210 }
211
212 flags
213 }
214
215 pub fn horizontal_tb() -> Self {
217 Self::empty()
218 }
219
220 #[inline]
221 pub fn is_vertical(&self) -> bool {
222 self.intersects(WritingMode::VERTICAL)
223 }
224
225 #[inline]
226 pub fn is_horizontal(&self) -> bool {
227 !self.is_vertical()
228 }
229
230 #[inline]
232 pub fn is_vertical_lr(&self) -> bool {
233 self.intersects(WritingMode::VERTICAL_LR)
234 }
235
236 #[inline]
238 pub fn is_inline_tb(&self) -> bool {
239 !self.intersects(WritingMode::INLINE_REVERSED)
241 }
242
243 #[inline]
244 pub fn is_bidi_ltr(&self) -> bool {
245 !self.intersects(WritingMode::RTL)
246 }
247
248 #[inline]
249 pub fn is_sideways(&self) -> bool {
250 self.intersects(WritingMode::VERTICAL_SIDEWAYS | WritingMode::TEXT_SIDEWAYS)
251 }
252
253 #[inline]
254 pub fn is_upright(&self) -> bool {
255 self.intersects(WritingMode::UPRIGHT)
256 }
257
258 #[inline]
265 pub fn line_left_is_inline_start(&self) -> bool {
266 self.is_bidi_ltr()
270 }
271
272 #[inline]
273 pub fn inline_start_physical_side(&self) -> PhysicalSide {
274 match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) {
275 (false, _, true) => PhysicalSide::Left,
276 (false, _, false) => PhysicalSide::Right,
277 (true, true, _) => PhysicalSide::Top,
278 (true, false, _) => PhysicalSide::Bottom,
279 }
280 }
281
282 #[inline]
283 pub fn inline_end_physical_side(&self) -> PhysicalSide {
284 match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) {
285 (false, _, true) => PhysicalSide::Right,
286 (false, _, false) => PhysicalSide::Left,
287 (true, true, _) => PhysicalSide::Bottom,
288 (true, false, _) => PhysicalSide::Top,
289 }
290 }
291
292 #[inline]
293 pub fn block_start_physical_side(&self) -> PhysicalSide {
294 match (self.is_vertical(), self.is_vertical_lr()) {
295 (false, _) => PhysicalSide::Top,
296 (true, true) => PhysicalSide::Left,
297 (true, false) => PhysicalSide::Right,
298 }
299 }
300
301 #[inline]
302 pub fn block_end_physical_side(&self) -> PhysicalSide {
303 match (self.is_vertical(), self.is_vertical_lr()) {
304 (false, _) => PhysicalSide::Bottom,
305 (true, true) => PhysicalSide::Right,
306 (true, false) => PhysicalSide::Left,
307 }
308 }
309
310 #[inline]
313 pub fn flipped_start_side(&self, side: PhysicalSide) -> PhysicalSide {
314 let bs = self.block_start_physical_side();
315 if side == bs {
316 return self.inline_start_physical_side();
317 }
318 let be = self.block_end_physical_side();
319 if side == be {
320 return self.inline_end_physical_side();
321 }
322 if side == self.inline_start_physical_side() {
323 return bs;
324 }
325 debug_assert_eq!(side, self.inline_end_physical_side());
326 be
327 }
328
329 #[inline]
330 pub fn start_start_physical_corner(&self) -> PhysicalCorner {
331 PhysicalCorner::from_sides(
332 self.block_start_physical_side(),
333 self.inline_start_physical_side(),
334 )
335 }
336
337 #[inline]
338 pub fn start_end_physical_corner(&self) -> PhysicalCorner {
339 PhysicalCorner::from_sides(
340 self.block_start_physical_side(),
341 self.inline_end_physical_side(),
342 )
343 }
344
345 #[inline]
346 pub fn end_start_physical_corner(&self) -> PhysicalCorner {
347 PhysicalCorner::from_sides(
348 self.block_end_physical_side(),
349 self.inline_start_physical_side(),
350 )
351 }
352
353 #[inline]
354 pub fn end_end_physical_corner(&self) -> PhysicalCorner {
355 PhysicalCorner::from_sides(
356 self.block_end_physical_side(),
357 self.inline_end_physical_side(),
358 )
359 }
360
361 #[inline]
362 pub fn block_flow_direction(&self) -> BlockFlowDirection {
363 match (self.is_vertical(), self.is_vertical_lr()) {
364 (false, _) => BlockFlowDirection::TopToBottom,
365 (true, true) => BlockFlowDirection::LeftToRight,
366 (true, false) => BlockFlowDirection::RightToLeft,
367 }
368 }
369
370 #[inline]
371 pub fn inline_base_direction(&self) -> InlineBaseDirection {
372 if self.intersects(WritingMode::RTL) {
373 InlineBaseDirection::RightToLeft
374 } else {
375 InlineBaseDirection::LeftToRight
376 }
377 }
378
379 #[inline]
380 pub fn is_text_vertical(&self) -> bool {
382 self.is_vertical() && !self.is_sideways()
383 }
384}
385
386impl fmt::Display for WritingMode {
387 fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
388 if self.is_vertical() {
389 write!(formatter, "V")?;
390 if self.is_vertical_lr() {
391 write!(formatter, " LR")?;
392 } else {
393 write!(formatter, " RL")?;
394 }
395 if self.is_sideways() {
396 write!(formatter, " Sideways")?;
397 }
398 if self.intersects(WritingMode::LINE_INVERTED) {
399 write!(formatter, " Inverted")?;
400 }
401 } else {
402 write!(formatter, "H")?;
403 }
404 if self.is_bidi_ltr() {
405 write!(formatter, " LTR")
406 } else {
407 write!(formatter, " RTL")
408 }
409 }
410}
411
412#[cfg(not(debug_assertions))]
419#[derive(Clone, Copy, Eq, PartialEq)]
420#[cfg_attr(feature = "servo", derive(Serialize))]
421struct DebugWritingMode;
422
423#[cfg(debug_assertions)]
424#[derive(Clone, Copy, Eq, PartialEq)]
425#[cfg_attr(feature = "servo", derive(Serialize))]
426struct DebugWritingMode {
427 mode: WritingMode,
428}
429
430#[cfg(not(debug_assertions))]
431impl DebugWritingMode {
432 #[inline]
433 fn check(&self, _other: WritingMode) {}
434
435 #[inline]
436 fn check_debug(&self, _other: DebugWritingMode) {}
437
438 #[inline]
439 fn new(_mode: WritingMode) -> DebugWritingMode {
440 DebugWritingMode
441 }
442}
443
444#[cfg(debug_assertions)]
445impl DebugWritingMode {
446 #[inline]
447 fn check(&self, other: WritingMode) {
448 assert_eq!(self.mode, other)
449 }
450
451 #[inline]
452 fn check_debug(&self, other: DebugWritingMode) {
453 assert_eq!(self.mode, other.mode)
454 }
455
456 #[inline]
457 fn new(mode: WritingMode) -> DebugWritingMode {
458 DebugWritingMode { mode }
459 }
460}
461
462impl Debug for DebugWritingMode {
463 #[cfg(not(debug_assertions))]
464 fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
465 write!(formatter, "?")
466 }
467
468 #[cfg(debug_assertions)]
469 fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
470 write!(formatter, "{}", self.mode)
471 }
472}
473
474#[derive(Clone, Copy, Debug, PartialEq)]
476#[cfg_attr(feature = "servo", derive(Serialize))]
477pub enum Direction {
478 Inline,
479 Block,
480}
481
482#[derive(Clone, Copy, Eq, PartialEq)]
484#[cfg_attr(feature = "servo", derive(Serialize))]
485pub struct LogicalSize<T> {
486 pub inline: T, pub block: T, debug_writing_mode: DebugWritingMode,
489}
490
491impl<T: Debug> Debug for LogicalSize<T> {
492 fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
493 write!(
494 formatter,
495 "LogicalSize({:?}, i{:?}×b{:?})",
496 self.debug_writing_mode, self.inline, self.block
497 )
498 }
499}
500
501impl<T: Zero> LogicalSize<T> {
503 #[inline]
504 pub fn zero(mode: WritingMode) -> LogicalSize<T> {
505 LogicalSize {
506 inline: Zero::zero(),
507 block: Zero::zero(),
508 debug_writing_mode: DebugWritingMode::new(mode),
509 }
510 }
511}
512
513impl<T> LogicalSize<T> {
514 #[inline]
515 pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize<T> {
516 LogicalSize {
517 inline: inline,
518 block: block,
519 debug_writing_mode: DebugWritingMode::new(mode),
520 }
521 }
522
523 #[inline]
524 pub fn from_physical(mode: WritingMode, size: Size2D<T>) -> LogicalSize<T> {
525 if mode.is_vertical() {
526 LogicalSize::new(mode, size.height, size.width)
527 } else {
528 LogicalSize::new(mode, size.width, size.height)
529 }
530 }
531}
532
533impl<T: Clone> LogicalSize<T> {
534 #[inline]
535 pub fn width(&self, mode: WritingMode) -> T {
536 self.debug_writing_mode.check(mode);
537 if mode.is_vertical() {
538 self.block.clone()
539 } else {
540 self.inline.clone()
541 }
542 }
543
544 #[inline]
545 pub fn set_width(&mut self, mode: WritingMode, width: T) {
546 self.debug_writing_mode.check(mode);
547 if mode.is_vertical() {
548 self.block = width
549 } else {
550 self.inline = width
551 }
552 }
553
554 #[inline]
555 pub fn height(&self, mode: WritingMode) -> T {
556 self.debug_writing_mode.check(mode);
557 if mode.is_vertical() {
558 self.inline.clone()
559 } else {
560 self.block.clone()
561 }
562 }
563
564 #[inline]
565 pub fn set_height(&mut self, mode: WritingMode, height: T) {
566 self.debug_writing_mode.check(mode);
567 if mode.is_vertical() {
568 self.inline = height
569 } else {
570 self.block = height
571 }
572 }
573
574 #[inline]
575 pub fn to_physical(&self, mode: WritingMode) -> Size2D<T> {
576 self.debug_writing_mode.check(mode);
577 if mode.is_vertical() {
578 Size2D::new(self.block.clone(), self.inline.clone())
579 } else {
580 Size2D::new(self.inline.clone(), self.block.clone())
581 }
582 }
583
584 #[inline]
585 pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize<T> {
586 if mode_from == mode_to {
587 self.debug_writing_mode.check(mode_from);
588 self.clone()
589 } else {
590 LogicalSize::from_physical(mode_to, self.to_physical(mode_from))
591 }
592 }
593}
594
595impl<T: Add<T, Output = T>> Add for LogicalSize<T> {
596 type Output = LogicalSize<T>;
597
598 #[inline]
599 fn add(self, other: LogicalSize<T>) -> LogicalSize<T> {
600 self.debug_writing_mode
601 .check_debug(other.debug_writing_mode);
602 LogicalSize {
603 debug_writing_mode: self.debug_writing_mode,
604 inline: self.inline + other.inline,
605 block: self.block + other.block,
606 }
607 }
608}
609
610impl<T: Sub<T, Output = T>> Sub for LogicalSize<T> {
611 type Output = LogicalSize<T>;
612
613 #[inline]
614 fn sub(self, other: LogicalSize<T>) -> LogicalSize<T> {
615 self.debug_writing_mode
616 .check_debug(other.debug_writing_mode);
617 LogicalSize {
618 debug_writing_mode: self.debug_writing_mode,
619 inline: self.inline - other.inline,
620 block: self.block - other.block,
621 }
622 }
623}
624
625#[derive(Clone, Copy, Eq, PartialEq)]
627#[cfg_attr(feature = "servo", derive(Serialize))]
628pub struct LogicalPoint<T> {
629 pub i: T,
631 pub b: T,
633 debug_writing_mode: DebugWritingMode,
634}
635
636impl<T: Debug> Debug for LogicalPoint<T> {
637 fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
638 write!(
639 formatter,
640 "LogicalPoint({:?} (i{:?}, b{:?}))",
641 self.debug_writing_mode, self.i, self.b
642 )
643 }
644}
645
646impl<T: Zero> LogicalPoint<T> {
648 #[inline]
649 pub fn zero(mode: WritingMode) -> LogicalPoint<T> {
650 LogicalPoint {
651 i: Zero::zero(),
652 b: Zero::zero(),
653 debug_writing_mode: DebugWritingMode::new(mode),
654 }
655 }
656}
657
658impl<T: Copy> LogicalPoint<T> {
659 #[inline]
660 pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint<T> {
661 LogicalPoint {
662 i: i,
663 b: b,
664 debug_writing_mode: DebugWritingMode::new(mode),
665 }
666 }
667}
668
669impl<T: Copy + Sub<T, Output = T>> LogicalPoint<T> {
670 #[inline]
671 pub fn from_physical(
672 mode: WritingMode,
673 point: Point2D<T>,
674 container_size: Size2D<T>,
675 ) -> LogicalPoint<T> {
676 if mode.is_vertical() {
677 LogicalPoint {
678 i: if mode.is_inline_tb() {
679 point.y
680 } else {
681 container_size.height - point.y
682 },
683 b: if mode.is_vertical_lr() {
684 point.x
685 } else {
686 container_size.width - point.x
687 },
688 debug_writing_mode: DebugWritingMode::new(mode),
689 }
690 } else {
691 LogicalPoint {
692 i: if mode.is_bidi_ltr() {
693 point.x
694 } else {
695 container_size.width - point.x
696 },
697 b: point.y,
698 debug_writing_mode: DebugWritingMode::new(mode),
699 }
700 }
701 }
702
703 #[inline]
704 pub fn x(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
705 self.debug_writing_mode.check(mode);
706 if mode.is_vertical() {
707 if mode.is_vertical_lr() {
708 self.b
709 } else {
710 container_size.width - self.b
711 }
712 } else {
713 if mode.is_bidi_ltr() {
714 self.i
715 } else {
716 container_size.width - self.i
717 }
718 }
719 }
720
721 #[inline]
722 pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D<T>) {
723 self.debug_writing_mode.check(mode);
724 if mode.is_vertical() {
725 self.b = if mode.is_vertical_lr() {
726 x
727 } else {
728 container_size.width - x
729 }
730 } else {
731 self.i = if mode.is_bidi_ltr() {
732 x
733 } else {
734 container_size.width - x
735 }
736 }
737 }
738
739 #[inline]
740 pub fn y(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
741 self.debug_writing_mode.check(mode);
742 if mode.is_vertical() {
743 if mode.is_inline_tb() {
744 self.i
745 } else {
746 container_size.height - self.i
747 }
748 } else {
749 self.b
750 }
751 }
752
753 #[inline]
754 pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D<T>) {
755 self.debug_writing_mode.check(mode);
756 if mode.is_vertical() {
757 self.i = if mode.is_inline_tb() {
758 y
759 } else {
760 container_size.height - y
761 }
762 } else {
763 self.b = y
764 }
765 }
766
767 #[inline]
768 pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> {
769 self.debug_writing_mode.check(mode);
770 if mode.is_vertical() {
771 Point2D::new(
772 if mode.is_vertical_lr() {
773 self.b
774 } else {
775 container_size.width - self.b
776 },
777 if mode.is_inline_tb() {
778 self.i
779 } else {
780 container_size.height - self.i
781 },
782 )
783 } else {
784 Point2D::new(
785 if mode.is_bidi_ltr() {
786 self.i
787 } else {
788 container_size.width - self.i
789 },
790 self.b,
791 )
792 }
793 }
794
795 #[inline]
796 pub fn convert(
797 &self,
798 mode_from: WritingMode,
799 mode_to: WritingMode,
800 container_size: Size2D<T>,
801 ) -> LogicalPoint<T> {
802 if mode_from == mode_to {
803 self.debug_writing_mode.check(mode_from);
804 *self
805 } else {
806 LogicalPoint::from_physical(
807 mode_to,
808 self.to_physical(mode_from, container_size),
809 container_size,
810 )
811 }
812 }
813}
814
815impl<T: Copy + Add<T, Output = T>> LogicalPoint<T> {
816 #[inline]
819 pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> {
820 self.debug_writing_mode
821 .check_debug(other.debug_writing_mode);
822 LogicalPoint {
823 debug_writing_mode: self.debug_writing_mode,
824 i: self.i + other.i,
825 b: self.b + other.b,
826 }
827 }
828}
829
830impl<T: Copy + Add<T, Output = T>> Add<LogicalSize<T>> for LogicalPoint<T> {
831 type Output = LogicalPoint<T>;
832
833 #[inline]
834 fn add(self, other: LogicalSize<T>) -> LogicalPoint<T> {
835 self.debug_writing_mode
836 .check_debug(other.debug_writing_mode);
837 LogicalPoint {
838 debug_writing_mode: self.debug_writing_mode,
839 i: self.i + other.inline,
840 b: self.b + other.block,
841 }
842 }
843}
844
845impl<T: Copy + Sub<T, Output = T>> Sub<LogicalSize<T>> for LogicalPoint<T> {
846 type Output = LogicalPoint<T>;
847
848 #[inline]
849 fn sub(self, other: LogicalSize<T>) -> LogicalPoint<T> {
850 self.debug_writing_mode
851 .check_debug(other.debug_writing_mode);
852 LogicalPoint {
853 debug_writing_mode: self.debug_writing_mode,
854 i: self.i - other.inline,
855 b: self.b - other.block,
856 }
857 }
858}
859
860#[derive(Clone, Copy, Eq, PartialEq)]
865#[cfg_attr(feature = "servo", derive(Serialize))]
866pub struct LogicalMargin<T> {
867 pub block_start: T,
868 pub inline_end: T,
869 pub block_end: T,
870 pub inline_start: T,
871 debug_writing_mode: DebugWritingMode,
872}
873
874impl<T: Debug> Debug for LogicalMargin<T> {
875 fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
876 let writing_mode_string = if cfg!(debug_assertions) {
877 format!("{:?}, ", self.debug_writing_mode)
878 } else {
879 "".to_owned()
880 };
881
882 write!(
883 formatter,
884 "LogicalMargin({}i:{:?}..{:?} b:{:?}..{:?})",
885 writing_mode_string,
886 self.inline_start,
887 self.inline_end,
888 self.block_start,
889 self.block_end
890 )
891 }
892}
893
894impl<T: Zero> LogicalMargin<T> {
895 #[inline]
896 pub fn zero(mode: WritingMode) -> LogicalMargin<T> {
897 LogicalMargin {
898 block_start: Zero::zero(),
899 inline_end: Zero::zero(),
900 block_end: Zero::zero(),
901 inline_start: Zero::zero(),
902 debug_writing_mode: DebugWritingMode::new(mode),
903 }
904 }
905}
906
907impl<T> LogicalMargin<T> {
908 #[inline]
909 pub fn new(
910 mode: WritingMode,
911 block_start: T,
912 inline_end: T,
913 block_end: T,
914 inline_start: T,
915 ) -> LogicalMargin<T> {
916 LogicalMargin {
917 block_start,
918 inline_end,
919 block_end,
920 inline_start,
921 debug_writing_mode: DebugWritingMode::new(mode),
922 }
923 }
924
925 #[inline]
926 pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> {
927 let block_start;
928 let inline_end;
929 let block_end;
930 let inline_start;
931 if mode.is_vertical() {
932 if mode.is_vertical_lr() {
933 block_start = offsets.left;
934 block_end = offsets.right;
935 } else {
936 block_start = offsets.right;
937 block_end = offsets.left;
938 }
939 if mode.is_inline_tb() {
940 inline_start = offsets.top;
941 inline_end = offsets.bottom;
942 } else {
943 inline_start = offsets.bottom;
944 inline_end = offsets.top;
945 }
946 } else {
947 block_start = offsets.top;
948 block_end = offsets.bottom;
949 if mode.is_bidi_ltr() {
950 inline_start = offsets.left;
951 inline_end = offsets.right;
952 } else {
953 inline_start = offsets.right;
954 inline_end = offsets.left;
955 }
956 }
957 LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start)
958 }
959}
960
961impl<T: Clone> LogicalMargin<T> {
962 #[inline]
963 pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> {
964 LogicalMargin::new(mode, value.clone(), value.clone(), value.clone(), value)
965 }
966
967 #[inline]
968 pub fn top(&self, mode: WritingMode) -> T {
969 self.debug_writing_mode.check(mode);
970 if mode.is_vertical() {
971 if mode.is_inline_tb() {
972 self.inline_start.clone()
973 } else {
974 self.inline_end.clone()
975 }
976 } else {
977 self.block_start.clone()
978 }
979 }
980
981 #[inline]
982 pub fn set_top(&mut self, mode: WritingMode, top: T) {
983 self.debug_writing_mode.check(mode);
984 if mode.is_vertical() {
985 if mode.is_inline_tb() {
986 self.inline_start = top
987 } else {
988 self.inline_end = top
989 }
990 } else {
991 self.block_start = top
992 }
993 }
994
995 #[inline]
996 pub fn right(&self, mode: WritingMode) -> T {
997 self.debug_writing_mode.check(mode);
998 if mode.is_vertical() {
999 if mode.is_vertical_lr() {
1000 self.block_end.clone()
1001 } else {
1002 self.block_start.clone()
1003 }
1004 } else {
1005 if mode.is_bidi_ltr() {
1006 self.inline_end.clone()
1007 } else {
1008 self.inline_start.clone()
1009 }
1010 }
1011 }
1012
1013 #[inline]
1014 pub fn set_right(&mut self, mode: WritingMode, right: T) {
1015 self.debug_writing_mode.check(mode);
1016 if mode.is_vertical() {
1017 if mode.is_vertical_lr() {
1018 self.block_end = right
1019 } else {
1020 self.block_start = right
1021 }
1022 } else {
1023 if mode.is_bidi_ltr() {
1024 self.inline_end = right
1025 } else {
1026 self.inline_start = right
1027 }
1028 }
1029 }
1030
1031 #[inline]
1032 pub fn bottom(&self, mode: WritingMode) -> T {
1033 self.debug_writing_mode.check(mode);
1034 if mode.is_vertical() {
1035 if mode.is_inline_tb() {
1036 self.inline_end.clone()
1037 } else {
1038 self.inline_start.clone()
1039 }
1040 } else {
1041 self.block_end.clone()
1042 }
1043 }
1044
1045 #[inline]
1046 pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) {
1047 self.debug_writing_mode.check(mode);
1048 if mode.is_vertical() {
1049 if mode.is_inline_tb() {
1050 self.inline_end = bottom
1051 } else {
1052 self.inline_start = bottom
1053 }
1054 } else {
1055 self.block_end = bottom
1056 }
1057 }
1058
1059 #[inline]
1060 pub fn left(&self, mode: WritingMode) -> T {
1061 self.debug_writing_mode.check(mode);
1062 if mode.is_vertical() {
1063 if mode.is_vertical_lr() {
1064 self.block_start.clone()
1065 } else {
1066 self.block_end.clone()
1067 }
1068 } else {
1069 if mode.is_bidi_ltr() {
1070 self.inline_start.clone()
1071 } else {
1072 self.inline_end.clone()
1073 }
1074 }
1075 }
1076
1077 #[inline]
1078 pub fn set_left(&mut self, mode: WritingMode, left: T) {
1079 self.debug_writing_mode.check(mode);
1080 if mode.is_vertical() {
1081 if mode.is_vertical_lr() {
1082 self.block_start = left
1083 } else {
1084 self.block_end = left
1085 }
1086 } else {
1087 if mode.is_bidi_ltr() {
1088 self.inline_start = left
1089 } else {
1090 self.inline_end = left
1091 }
1092 }
1093 }
1094
1095 #[inline]
1096 pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
1097 self.debug_writing_mode.check(mode);
1098 let top;
1099 let right;
1100 let bottom;
1101 let left;
1102 if mode.is_vertical() {
1103 if mode.is_vertical_lr() {
1104 left = self.block_start.clone();
1105 right = self.block_end.clone();
1106 } else {
1107 right = self.block_start.clone();
1108 left = self.block_end.clone();
1109 }
1110 if mode.is_inline_tb() {
1111 top = self.inline_start.clone();
1112 bottom = self.inline_end.clone();
1113 } else {
1114 bottom = self.inline_start.clone();
1115 top = self.inline_end.clone();
1116 }
1117 } else {
1118 top = self.block_start.clone();
1119 bottom = self.block_end.clone();
1120 if mode.is_bidi_ltr() {
1121 left = self.inline_start.clone();
1122 right = self.inline_end.clone();
1123 } else {
1124 right = self.inline_start.clone();
1125 left = self.inline_end.clone();
1126 }
1127 }
1128 SideOffsets2D::new(top, right, bottom, left)
1129 }
1130
1131 #[inline]
1132 pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
1133 if mode_from == mode_to {
1134 self.debug_writing_mode.check(mode_from);
1135 self.clone()
1136 } else {
1137 LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
1138 }
1139 }
1140}
1141
1142impl<T: PartialEq + Zero> LogicalMargin<T> {
1143 #[inline]
1144 pub fn is_zero(&self) -> bool {
1145 self.block_start == Zero::zero()
1146 && self.inline_end == Zero::zero()
1147 && self.block_end == Zero::zero()
1148 && self.inline_start == Zero::zero()
1149 }
1150}
1151
1152impl<T: Copy + Add<T, Output = T>> LogicalMargin<T> {
1153 #[inline]
1154 pub fn inline_start_end(&self) -> T {
1155 self.inline_start + self.inline_end
1156 }
1157
1158 #[inline]
1159 pub fn block_start_end(&self) -> T {
1160 self.block_start + self.block_end
1161 }
1162
1163 #[inline]
1164 pub fn start_end(&self, direction: Direction) -> T {
1165 match direction {
1166 Direction::Inline => self.inline_start + self.inline_end,
1167 Direction::Block => self.block_start + self.block_end,
1168 }
1169 }
1170
1171 #[inline]
1172 pub fn top_bottom(&self, mode: WritingMode) -> T {
1173 self.debug_writing_mode.check(mode);
1174 if mode.is_vertical() {
1175 self.inline_start_end()
1176 } else {
1177 self.block_start_end()
1178 }
1179 }
1180
1181 #[inline]
1182 pub fn left_right(&self, mode: WritingMode) -> T {
1183 self.debug_writing_mode.check(mode);
1184 if mode.is_vertical() {
1185 self.block_start_end()
1186 } else {
1187 self.inline_start_end()
1188 }
1189 }
1190}
1191
1192impl<T: Add<T, Output = T>> Add for LogicalMargin<T> {
1193 type Output = LogicalMargin<T>;
1194
1195 #[inline]
1196 fn add(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1197 self.debug_writing_mode
1198 .check_debug(other.debug_writing_mode);
1199 LogicalMargin {
1200 debug_writing_mode: self.debug_writing_mode,
1201 block_start: self.block_start + other.block_start,
1202 inline_end: self.inline_end + other.inline_end,
1203 block_end: self.block_end + other.block_end,
1204 inline_start: self.inline_start + other.inline_start,
1205 }
1206 }
1207}
1208
1209impl<T: Sub<T, Output = T>> Sub for LogicalMargin<T> {
1210 type Output = LogicalMargin<T>;
1211
1212 #[inline]
1213 fn sub(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1214 self.debug_writing_mode
1215 .check_debug(other.debug_writing_mode);
1216 LogicalMargin {
1217 debug_writing_mode: self.debug_writing_mode,
1218 block_start: self.block_start - other.block_start,
1219 inline_end: self.inline_end - other.inline_end,
1220 block_end: self.block_end - other.block_end,
1221 inline_start: self.inline_start - other.inline_start,
1222 }
1223 }
1224}
1225
1226#[derive(Clone, Copy, Eq, PartialEq)]
1228#[cfg_attr(feature = "servo", derive(Serialize))]
1229pub struct LogicalRect<T> {
1230 pub start: LogicalPoint<T>,
1231 pub size: LogicalSize<T>,
1232 debug_writing_mode: DebugWritingMode,
1233}
1234
1235impl<T: Debug> Debug for LogicalRect<T> {
1236 fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
1237 let writing_mode_string = if cfg!(debug_assertions) {
1238 format!("{:?}, ", self.debug_writing_mode)
1239 } else {
1240 "".to_owned()
1241 };
1242
1243 write!(
1244 formatter,
1245 "LogicalRect({}i{:?}×b{:?}, @ (i{:?},b{:?}))",
1246 writing_mode_string, self.size.inline, self.size.block, self.start.i, self.start.b
1247 )
1248 }
1249}
1250
1251impl<T: Zero> LogicalRect<T> {
1252 #[inline]
1253 pub fn zero(mode: WritingMode) -> LogicalRect<T> {
1254 LogicalRect {
1255 start: LogicalPoint::zero(mode),
1256 size: LogicalSize::zero(mode),
1257 debug_writing_mode: DebugWritingMode::new(mode),
1258 }
1259 }
1260}
1261
1262impl<T: Copy> LogicalRect<T> {
1263 #[inline]
1264 pub fn new(
1265 mode: WritingMode,
1266 inline_start: T,
1267 block_start: T,
1268 inline: T,
1269 block: T,
1270 ) -> LogicalRect<T> {
1271 LogicalRect {
1272 start: LogicalPoint::new(mode, inline_start, block_start),
1273 size: LogicalSize::new(mode, inline, block),
1274 debug_writing_mode: DebugWritingMode::new(mode),
1275 }
1276 }
1277
1278 #[inline]
1279 pub fn from_point_size(
1280 mode: WritingMode,
1281 start: LogicalPoint<T>,
1282 size: LogicalSize<T>,
1283 ) -> LogicalRect<T> {
1284 start.debug_writing_mode.check(mode);
1285 size.debug_writing_mode.check(mode);
1286 LogicalRect {
1287 start: start,
1288 size: size,
1289 debug_writing_mode: DebugWritingMode::new(mode),
1290 }
1291 }
1292}
1293
1294impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1295 #[inline]
1296 pub fn from_physical(
1297 mode: WritingMode,
1298 rect: Rect<T>,
1299 container_size: Size2D<T>,
1300 ) -> LogicalRect<T> {
1301 let inline_start;
1302 let block_start;
1303 let inline;
1304 let block;
1305 if mode.is_vertical() {
1306 inline = rect.size.height;
1307 block = rect.size.width;
1308 if mode.is_vertical_lr() {
1309 block_start = rect.origin.x;
1310 } else {
1311 block_start = container_size.width - (rect.origin.x + rect.size.width);
1312 }
1313 if mode.is_inline_tb() {
1314 inline_start = rect.origin.y;
1315 } else {
1316 inline_start = container_size.height - (rect.origin.y + rect.size.height);
1317 }
1318 } else {
1319 inline = rect.size.width;
1320 block = rect.size.height;
1321 block_start = rect.origin.y;
1322 if mode.is_bidi_ltr() {
1323 inline_start = rect.origin.x;
1324 } else {
1325 inline_start = container_size.width - (rect.origin.x + rect.size.width);
1326 }
1327 }
1328 LogicalRect {
1329 start: LogicalPoint::new(mode, inline_start, block_start),
1330 size: LogicalSize::new(mode, inline, block),
1331 debug_writing_mode: DebugWritingMode::new(mode),
1332 }
1333 }
1334
1335 #[inline]
1336 pub fn inline_end(&self) -> T {
1337 self.start.i + self.size.inline
1338 }
1339
1340 #[inline]
1341 pub fn block_end(&self) -> T {
1342 self.start.b + self.size.block
1343 }
1344
1345 #[inline]
1346 pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Rect<T> {
1347 self.debug_writing_mode.check(mode);
1348 let x;
1349 let y;
1350 let width;
1351 let height;
1352 if mode.is_vertical() {
1353 width = self.size.block;
1354 height = self.size.inline;
1355 if mode.is_vertical_lr() {
1356 x = self.start.b;
1357 } else {
1358 x = container_size.width - self.block_end();
1359 }
1360 if mode.is_inline_tb() {
1361 y = self.start.i;
1362 } else {
1363 y = container_size.height - self.inline_end();
1364 }
1365 } else {
1366 width = self.size.inline;
1367 height = self.size.block;
1368 y = self.start.b;
1369 if mode.is_bidi_ltr() {
1370 x = self.start.i;
1371 } else {
1372 x = container_size.width - self.inline_end();
1373 }
1374 }
1375 Rect {
1376 origin: Point2D::new(x, y),
1377 size: Size2D::new(width, height),
1378 }
1379 }
1380
1381 #[inline]
1382 pub fn convert(
1383 &self,
1384 mode_from: WritingMode,
1385 mode_to: WritingMode,
1386 container_size: Size2D<T>,
1387 ) -> LogicalRect<T> {
1388 if mode_from == mode_to {
1389 self.debug_writing_mode.check(mode_from);
1390 *self
1391 } else {
1392 LogicalRect::from_physical(
1393 mode_to,
1394 self.to_physical(mode_from, container_size),
1395 container_size,
1396 )
1397 }
1398 }
1399
1400 pub fn translate_by_size(&self, offset: LogicalSize<T>) -> LogicalRect<T> {
1401 LogicalRect {
1402 start: self.start + offset,
1403 ..*self
1404 }
1405 }
1406
1407 pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> {
1408 LogicalRect {
1409 start: self.start
1410 + LogicalSize {
1411 inline: offset.i,
1412 block: offset.b,
1413 debug_writing_mode: offset.debug_writing_mode,
1414 },
1415 size: self.size,
1416 debug_writing_mode: self.debug_writing_mode,
1417 }
1418 }
1419}
1420
1421impl<T: Copy + Ord + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1422 #[inline]
1423 pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> {
1424 self.debug_writing_mode
1425 .check_debug(other.debug_writing_mode);
1426
1427 let inline_start = min(self.start.i, other.start.i);
1428 let block_start = min(self.start.b, other.start.b);
1429 LogicalRect {
1430 start: LogicalPoint {
1431 i: inline_start,
1432 b: block_start,
1433 debug_writing_mode: self.debug_writing_mode,
1434 },
1435 size: LogicalSize {
1436 inline: max(self.inline_end(), other.inline_end()) - inline_start,
1437 block: max(self.block_end(), other.block_end()) - block_start,
1438 debug_writing_mode: self.debug_writing_mode,
1439 },
1440 debug_writing_mode: self.debug_writing_mode,
1441 }
1442 }
1443}
1444
1445impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Add<LogicalMargin<T>> for LogicalRect<T> {
1446 type Output = LogicalRect<T>;
1447
1448 #[inline]
1449 fn add(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1450 self.debug_writing_mode
1451 .check_debug(other.debug_writing_mode);
1452 LogicalRect {
1453 start: LogicalPoint {
1454 i: self.start.i - other.inline_start,
1457 b: self.start.b - other.block_start,
1458 debug_writing_mode: self.debug_writing_mode,
1459 },
1460 size: LogicalSize {
1461 inline: self.size.inline + other.inline_start_end(),
1462 block: self.size.block + other.block_start_end(),
1463 debug_writing_mode: self.debug_writing_mode,
1464 },
1465 debug_writing_mode: self.debug_writing_mode,
1466 }
1467 }
1468}
1469
1470impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Sub<LogicalMargin<T>> for LogicalRect<T> {
1471 type Output = LogicalRect<T>;
1472
1473 #[inline]
1474 fn sub(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1475 self.debug_writing_mode
1476 .check_debug(other.debug_writing_mode);
1477 LogicalRect {
1478 start: LogicalPoint {
1479 i: self.start.i + other.inline_start,
1482 b: self.start.b + other.block_start,
1483 debug_writing_mode: self.debug_writing_mode,
1484 },
1485 size: LogicalSize {
1486 inline: self.size.inline - other.inline_start_end(),
1487 block: self.size.block - other.block_start_end(),
1488 debug_writing_mode: self.debug_writing_mode,
1489 },
1490 debug_writing_mode: self.debug_writing_mode,
1491 }
1492 }
1493}
1494
1495#[derive(Clone, Copy, Debug, PartialEq)]
1496#[repr(u8)]
1497pub enum LogicalAxis {
1498 Block = 0,
1499 Inline,
1500}
1501
1502impl LogicalAxis {
1503 #[inline]
1504 pub fn to_physical(self, wm: WritingMode) -> PhysicalAxis {
1505 if wm.is_horizontal() == (self == Self::Inline) {
1506 PhysicalAxis::Horizontal
1507 } else {
1508 PhysicalAxis::Vertical
1509 }
1510 }
1511}
1512
1513#[derive(Clone, Copy, Debug, PartialEq)]
1514#[repr(u8)]
1515pub enum LogicalSide {
1516 BlockStart = 0,
1517 BlockEnd,
1518 InlineStart,
1519 InlineEnd,
1520}
1521
1522impl LogicalSide {
1523 fn is_block(self) -> bool {
1524 matches!(self, Self::BlockStart | Self::BlockEnd)
1525 }
1526
1527 #[inline]
1528 pub fn to_physical(self, wm: WritingMode) -> PhysicalSide {
1529 static BLOCK_MAPPING: [[PhysicalSide; 2]; 4] = [
1531 [PhysicalSide::Top, PhysicalSide::Bottom], [PhysicalSide::Right, PhysicalSide::Left], [PhysicalSide::Bottom, PhysicalSide::Top], [PhysicalSide::Left, PhysicalSide::Right], ];
1536
1537 if self.is_block() {
1538 let vertical = wm.is_vertical();
1539 let lr = wm.is_vertical_lr();
1540 let index = (vertical as usize) | ((lr as usize) << 1);
1541 return BLOCK_MAPPING[index][self as usize];
1542 }
1543
1544 let edge = self as usize - 2;
1546 static INLINE_MAPPING: [[PhysicalSide; 2]; 16] = [
1559 [PhysicalSide::Left, PhysicalSide::Right], [PhysicalSide::Top, PhysicalSide::Bottom], [PhysicalSide::Right, PhysicalSide::Left], [PhysicalSide::Bottom, PhysicalSide::Top], [PhysicalSide::Right, PhysicalSide::Left], [PhysicalSide::Top, PhysicalSide::Bottom], [PhysicalSide::Left, PhysicalSide::Right], [PhysicalSide::Bottom, PhysicalSide::Top], [PhysicalSide::Left, PhysicalSide::Right], [PhysicalSide::Top, PhysicalSide::Bottom], [PhysicalSide::Right, PhysicalSide::Left], [PhysicalSide::Bottom, PhysicalSide::Top], [PhysicalSide::Left, PhysicalSide::Right], [PhysicalSide::Top, PhysicalSide::Bottom], [PhysicalSide::Right, PhysicalSide::Left], [PhysicalSide::Bottom, PhysicalSide::Top], ];
1576
1577 debug_assert!(
1578 WritingMode::VERTICAL.bits() == 0x01
1579 && WritingMode::INLINE_REVERSED.bits() == 0x02
1580 && WritingMode::VERTICAL_LR.bits() == 0x04
1581 && WritingMode::LINE_INVERTED.bits() == 0x08
1582 );
1583 let index = (wm.bits() & 0xF) as usize;
1584 INLINE_MAPPING[index][edge]
1585 }
1586}
1587
1588#[derive(Clone, Copy, Debug, PartialEq)]
1589#[repr(u8)]
1590pub enum LogicalCorner {
1591 StartStart = 0,
1592 StartEnd,
1593 EndStart,
1594 EndEnd,
1595}
1596
1597impl LogicalCorner {
1598 #[inline]
1599 pub fn to_physical(self, wm: WritingMode) -> PhysicalCorner {
1600 static CORNER_TO_SIDES: [[LogicalSide; 2]; 4] = [
1601 [LogicalSide::BlockStart, LogicalSide::InlineStart],
1602 [LogicalSide::BlockStart, LogicalSide::InlineEnd],
1603 [LogicalSide::BlockEnd, LogicalSide::InlineStart],
1604 [LogicalSide::BlockEnd, LogicalSide::InlineEnd],
1605 ];
1606
1607 let [block, inline] = CORNER_TO_SIDES[self as usize];
1608 let block = block.to_physical(wm);
1609 let inline = inline.to_physical(wm);
1610 PhysicalCorner::from_sides(block, inline)
1611 }
1612}
1613
1614#[derive(Clone, Copy, Debug, PartialEq)]
1615#[repr(u8)]
1616pub enum PhysicalAxis {
1617 Vertical = 0,
1618 Horizontal,
1619}
1620
1621#[derive(Clone, Copy, Debug, PartialEq)]
1622#[repr(u8)]
1623pub enum PhysicalSide {
1624 Top = 0,
1625 Right,
1626 Bottom,
1627 Left,
1628}
1629
1630impl PhysicalSide {
1631 pub fn parallel_to(self, other: Self) -> bool {
1633 !self.orthogonal_to(other)
1634 }
1635
1636 pub fn orthogonal_to(self, other: Self) -> bool {
1638 matches!(self, Self::Top | Self::Bottom) != matches!(other, Self::Top | Self::Bottom)
1639 }
1640
1641 pub fn opposite_side(self) -> Self {
1643 match self {
1644 Self::Top => Self::Bottom,
1645 Self::Right => Self::Left,
1646 Self::Bottom => Self::Top,
1647 Self::Left => Self::Right,
1648 }
1649 }
1650}
1651
1652#[derive(Clone, Copy, Debug, PartialEq)]
1653#[repr(u8)]
1654pub enum PhysicalCorner {
1655 TopLeft = 0,
1656 TopRight,
1657 BottomRight,
1658 BottomLeft,
1659}
1660
1661impl PhysicalCorner {
1662 fn from_sides(a: PhysicalSide, b: PhysicalSide) -> Self {
1663 debug_assert!(a.orthogonal_to(b), "Sides should be orthogonal");
1664 const IMPOSSIBLE: PhysicalCorner = PhysicalCorner::TopLeft;
1667 static SIDES_TO_CORNER: [[PhysicalCorner; 4]; 4] = [
1668 [
1669 IMPOSSIBLE,
1670 PhysicalCorner::TopRight,
1671 IMPOSSIBLE,
1672 PhysicalCorner::TopLeft,
1673 ],
1674 [
1675 PhysicalCorner::TopRight,
1676 IMPOSSIBLE,
1677 PhysicalCorner::BottomRight,
1678 IMPOSSIBLE,
1679 ],
1680 [
1681 IMPOSSIBLE,
1682 PhysicalCorner::BottomRight,
1683 IMPOSSIBLE,
1684 PhysicalCorner::BottomLeft,
1685 ],
1686 [
1687 PhysicalCorner::TopLeft,
1688 IMPOSSIBLE,
1689 PhysicalCorner::BottomLeft,
1690 IMPOSSIBLE,
1691 ],
1692 ];
1693 SIDES_TO_CORNER[a as usize][b as usize]
1694 }
1695}