logical_geometry.rs (53261B)
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 7 use crate::derives::*; 8 use crate::properties::style_structs; 9 use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D}; 10 use euclid::num::Zero; 11 use std::cmp::{max, min}; 12 use std::fmt::{self, Debug, Error, Formatter}; 13 use std::ops::{Add, Sub}; 14 15 pub enum BlockFlowDirection { 16 TopToBottom, 17 RightToLeft, 18 LeftToRight, 19 } 20 21 pub 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)] 47 pub 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)] 62 pub struct WritingMode(u8); 63 bitflags!( 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 131 impl 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 392 impl 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))] 427 struct DebugWritingMode; 428 429 #[cfg(debug_assertions)] 430 #[derive(Clone, Copy, Eq, PartialEq)] 431 #[cfg_attr(feature = "servo", derive(Serialize))] 432 struct DebugWritingMode { 433 mode: WritingMode, 434 } 435 436 #[cfg(not(debug_assertions))] 437 impl 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)] 451 impl 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 468 impl 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))] 483 pub 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))] 491 pub 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 497 impl<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. 508 impl<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 519 impl<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 539 impl<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 601 impl<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 616 impl<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))] 634 pub 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 642 impl<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. 653 impl<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 664 impl<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 675 impl<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 821 impl<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 836 impl<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 851 impl<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))] 872 pub 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 880 impl<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 900 impl<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 913 impl<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 967 impl<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 1148 impl<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 1158 impl<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 1198 impl<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 1215 impl<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))] 1235 pub struct LogicalRect<T> { 1236 pub start: LogicalPoint<T>, 1237 pub size: LogicalSize<T>, 1238 debug_writing_mode: DebugWritingMode, 1239 } 1240 1241 impl<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 1257 impl<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 1268 impl<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 1300 impl<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 1427 impl<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 1451 impl<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 1476 impl<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)] 1503 pub enum LogicalAxis { 1504 Block = 0, 1505 Inline, 1506 } 1507 1508 impl 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)] 1521 pub enum LogicalSide { 1522 BlockStart = 0, 1523 BlockEnd, 1524 InlineStart, 1525 InlineEnd, 1526 } 1527 1528 impl 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)] 1596 pub enum LogicalCorner { 1597 StartStart = 0, 1598 StartEnd, 1599 EndStart, 1600 EndEnd, 1601 } 1602 1603 impl 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)] 1622 pub enum PhysicalAxis { 1623 Vertical = 0, 1624 Horizontal, 1625 } 1626 1627 #[derive(Clone, Copy, Debug, PartialEq)] 1628 #[repr(u8)] 1629 pub enum PhysicalSide { 1630 Top = 0, 1631 Right, 1632 Bottom, 1633 Left, 1634 } 1635 1636 impl 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)] 1660 pub enum PhysicalCorner { 1661 TopLeft = 0, 1662 TopRight, 1663 BottomRight, 1664 BottomLeft, 1665 } 1666 1667 impl 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 }