1use 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#[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#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Serialize)]
61#[repr(C)]
62pub struct WritingMode(u8);
63bitflags!(
64 impl WritingMode: u8 {
65 const VERTICAL = 1 << 0;
68 const INLINE_REVERSED = 1 << 1;
75 const VERTICAL_LR = 1 << 2;
80 const LINE_INVERTED = 1 << 3;
85 const RTL = 1 << 4;
87 const VERTICAL_SIDEWAYS = 1 << 5;
95 const TEXT_SIDEWAYS = 1 << 6;
103 const UPRIGHT = 1 << 7;
111 const WRITING_MODE_HORIZONTAL_TB = 0;
115 const WRITING_MODE_VERTICAL_RL = WritingMode::VERTICAL.bits();
117 const WRITING_MODE_VERTICAL_LR = WritingMode::VERTICAL.bits() |
119 WritingMode::VERTICAL_LR.bits() |
120 WritingMode::LINE_INVERTED.bits();
121 const WRITING_MODE_SIDEWAYS_RL = WritingMode::VERTICAL.bits() |
123 WritingMode::VERTICAL_SIDEWAYS.bits();
124 const WRITING_MODE_SIDEWAYS_LR = WritingMode::VERTICAL.bits() |
126 WritingMode::VERTICAL_LR.bits() |
127 WritingMode::VERTICAL_SIDEWAYS.bits();
128 }
129);
130
131impl WritingMode {
132 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 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 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 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 #[inline]
238 pub fn is_vertical_lr(&self) -> bool {
239 self.intersects(WritingMode::VERTICAL_LR)
240 }
241
242 #[inline]
244 pub fn is_inline_tb(&self) -> bool {
245 !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 #[inline]
271 pub fn line_left_is_inline_start(&self) -> bool {
272 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 #[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 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#[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#[derive(Clone, Copy, Debug, PartialEq)]
482#[cfg_attr(feature = "servo", derive(Serialize))]
483pub enum Direction {
484 Inline,
485 Block,
486}
487
488#[derive(Clone, Copy, Eq, PartialEq)]
490#[cfg_attr(feature = "servo", derive(Serialize))]
491pub struct LogicalSize<T> {
492 pub inline: T, pub block: T, 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
507impl<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#[derive(Clone, Copy, Eq, PartialEq)]
633#[cfg_attr(feature = "servo", derive(Serialize))]
634pub struct LogicalPoint<T> {
635 pub i: T,
637 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
652impl<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 #[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#[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#[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 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 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 static BLOCK_MAPPING: [[PhysicalSide; 2]; 4] = [
1537 [PhysicalSide::Top, PhysicalSide::Bottom], [PhysicalSide::Right, PhysicalSide::Left], [PhysicalSide::Bottom, PhysicalSide::Top], [PhysicalSide::Left, PhysicalSide::Right], ];
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 let edge = self as usize - 2;
1552 static INLINE_MAPPING: [[PhysicalSide; 2]; 16] = [
1565 [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], ];
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 pub fn parallel_to(self, other: Self) -> bool {
1639 !self.orthogonal_to(other)
1640 }
1641
1642 pub fn orthogonal_to(self, other: Self) -> bool {
1644 matches!(self, Self::Top | Self::Bottom) != matches!(other, Self::Top | Self::Bottom)
1645 }
1646
1647 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 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}