/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Geometry in flow-relative space. use crate::properties::style_structs; use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D}; use euclid::num::Zero; use std::cmp::{max, min}; use std::fmt::{self, Debug, Error, Formatter}; use std::ops::{Add, Sub}; use unicode_bidi as bidi; pub enum BlockFlowDirection { TopToBottom, RightToLeft, LeftToRight, } pub enum InlineBaseDirection { LeftToRight, RightToLeft, } // TODO: improve the readability of the WritingMode serialization, refer to the Debug:fmt() #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Serialize)] #[repr(C)] pub struct WritingMode(u8); bitflags!( impl WritingMode: u8 { /// A vertical writing mode; writing-mode is vertical-rl, /// vertical-lr, sideways-lr, or sideways-rl. const VERTICAL = 1 << 0; /// The inline flow direction is reversed against the physical /// direction (i.e. right-to-left or bottom-to-top); writing-mode is /// sideways-lr or direction is rtl (but not both). /// /// (This bit can be derived from the others, but we store it for /// convenience.) const INLINE_REVERSED = 1 << 1; /// A vertical writing mode whose block progression direction is left- /// to-right; writing-mode is vertical-lr or sideways-lr. /// /// Never set without VERTICAL. const VERTICAL_LR = 1 << 2; /// The line-over/line-under sides are inverted with respect to the /// block-start/block-end edge; writing-mode is vertical-lr. /// /// Never set without VERTICAL and VERTICAL_LR. const LINE_INVERTED = 1 << 3; /// direction is rtl. const RTL = 1 << 4; /// All text within a vertical writing mode is displayed sideways /// and runs top-to-bottom or bottom-to-top; set in these cases: /// /// * writing-mode: sideways-rl; /// * writing-mode: sideways-lr; /// /// Never set without VERTICAL. const VERTICAL_SIDEWAYS = 1 << 5; /// Similar to VERTICAL_SIDEWAYS, but is set via text-orientation; /// set in these cases: /// /// * writing-mode: vertical-rl; text-orientation: sideways; /// * writing-mode: vertical-lr; text-orientation: sideways; /// /// Never set without VERTICAL. const TEXT_SIDEWAYS = 1 << 6; /// Horizontal text within a vertical writing mode is displayed with each /// glyph upright; set in these cases: /// /// * writing-mode: vertical-rl; text-orientation: upright; /// * writing-mode: vertical-lr: text-orientation: upright; /// /// Never set without VERTICAL. const UPRIGHT = 1 << 7; } ); impl WritingMode { /// Return a WritingMode bitflags from the relevant CSS properties. pub fn new(inheritedbox_style: &style_structs::InheritedBox) -> Self { use crate::properties::longhands::direction::computed_value::T as Direction; use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode; let mut flags = WritingMode::empty(); let direction = inheritedbox_style.clone_direction(); let writing_mode = inheritedbox_style.clone_writing_mode(); match direction { Direction::Ltr => {}, Direction::Rtl => { flags.insert(WritingMode::RTL); }, } match writing_mode { SpecifiedWritingMode::HorizontalTb => { if direction == Direction::Rtl { flags.insert(WritingMode::INLINE_REVERSED); } }, SpecifiedWritingMode::VerticalRl => { flags.insert(WritingMode::VERTICAL); if direction == Direction::Rtl { flags.insert(WritingMode::INLINE_REVERSED); } }, SpecifiedWritingMode::VerticalLr => { flags.insert(WritingMode::VERTICAL); flags.insert(WritingMode::VERTICAL_LR); flags.insert(WritingMode::LINE_INVERTED); if direction == Direction::Rtl { flags.insert(WritingMode::INLINE_REVERSED); } }, #[cfg(feature = "gecko")] SpecifiedWritingMode::SidewaysRl => { flags.insert(WritingMode::VERTICAL); flags.insert(WritingMode::VERTICAL_SIDEWAYS); if direction == Direction::Rtl { flags.insert(WritingMode::INLINE_REVERSED); } }, #[cfg(feature = "gecko")] SpecifiedWritingMode::SidewaysLr => { flags.insert(WritingMode::VERTICAL); flags.insert(WritingMode::VERTICAL_LR); flags.insert(WritingMode::VERTICAL_SIDEWAYS); if direction == Direction::Ltr { flags.insert(WritingMode::INLINE_REVERSED); } }, } #[cfg(feature = "gecko")] { use crate::properties::longhands::text_orientation::computed_value::T as TextOrientation; // text-orientation only has an effect for vertical-rl and // vertical-lr values of writing-mode. match writing_mode { SpecifiedWritingMode::VerticalRl | SpecifiedWritingMode::VerticalLr => { match inheritedbox_style.clone_text_orientation() { TextOrientation::Mixed => {}, TextOrientation::Upright => { flags.insert(WritingMode::UPRIGHT); // https://drafts.csswg.org/css-writing-modes-3/#valdef-text-orientation-upright: // // > This value causes the used value of direction // > to be ltr, and for the purposes of bidi // > reordering, causes all characters to be treated // > as strong LTR. flags.remove(WritingMode::RTL); flags.remove(WritingMode::INLINE_REVERSED); }, TextOrientation::Sideways => { flags.insert(WritingMode::TEXT_SIDEWAYS); }, } }, _ => {}, } } flags } /// Returns the `horizontal-tb` value. pub fn horizontal_tb() -> Self { Self::empty() } #[inline] pub fn is_vertical(&self) -> bool { self.intersects(WritingMode::VERTICAL) } #[inline] pub fn is_horizontal(&self) -> bool { !self.is_vertical() } /// Assuming .is_vertical(), does the block direction go left to right? #[inline] pub fn is_vertical_lr(&self) -> bool { self.intersects(WritingMode::VERTICAL_LR) } /// Assuming .is_vertical(), does the inline direction go top to bottom? #[inline] pub fn is_inline_tb(&self) -> bool { // https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical !self.intersects(WritingMode::INLINE_REVERSED) } #[inline] pub fn is_bidi_ltr(&self) -> bool { !self.intersects(WritingMode::RTL) } #[inline] pub fn is_sideways(&self) -> bool { self.intersects(WritingMode::VERTICAL_SIDEWAYS | WritingMode::TEXT_SIDEWAYS) } #[inline] pub fn is_upright(&self) -> bool { self.intersects(WritingMode::UPRIGHT) } /// https://drafts.csswg.org/css-writing-modes/#logical-to-physical /// /// | Return | line-left is… | line-right is… | /// |---------|---------------|----------------| /// | `true` | inline-start | inline-end | /// | `false` | inline-end | inline-start | #[inline] pub fn line_left_is_inline_start(&self) -> bool { // https://drafts.csswg.org/css-writing-modes/#inline-start // “For boxes with a used direction value of ltr, this means the line-left side. // For boxes with a used direction value of rtl, this means the line-right side.” self.is_bidi_ltr() } #[inline] pub fn inline_start_physical_side(&self) -> PhysicalSide { match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) { (false, _, true) => PhysicalSide::Left, (false, _, false) => PhysicalSide::Right, (true, true, _) => PhysicalSide::Top, (true, false, _) => PhysicalSide::Bottom, } } #[inline] pub fn inline_end_physical_side(&self) -> PhysicalSide { match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) { (false, _, true) => PhysicalSide::Right, (false, _, false) => PhysicalSide::Left, (true, true, _) => PhysicalSide::Bottom, (true, false, _) => PhysicalSide::Top, } } #[inline] pub fn block_start_physical_side(&self) -> PhysicalSide { match (self.is_vertical(), self.is_vertical_lr()) { (false, _) => PhysicalSide::Top, (true, true) => PhysicalSide::Left, (true, false) => PhysicalSide::Right, } } #[inline] pub fn block_end_physical_side(&self) -> PhysicalSide { match (self.is_vertical(), self.is_vertical_lr()) { (false, _) => PhysicalSide::Bottom, (true, true) => PhysicalSide::Right, (true, false) => PhysicalSide::Left, } } #[inline] pub fn start_start_physical_corner(&self) -> PhysicalCorner { PhysicalCorner::from_sides( self.block_start_physical_side(), self.inline_start_physical_side(), ) } #[inline] pub fn start_end_physical_corner(&self) -> PhysicalCorner { PhysicalCorner::from_sides( self.block_start_physical_side(), self.inline_end_physical_side(), ) } #[inline] pub fn end_start_physical_corner(&self) -> PhysicalCorner { PhysicalCorner::from_sides( self.block_end_physical_side(), self.inline_start_physical_side(), ) } #[inline] pub fn end_end_physical_corner(&self) -> PhysicalCorner { PhysicalCorner::from_sides( self.block_end_physical_side(), self.inline_end_physical_side(), ) } #[inline] pub fn block_flow_direction(&self) -> BlockFlowDirection { match (self.is_vertical(), self.is_vertical_lr()) { (false, _) => BlockFlowDirection::TopToBottom, (true, true) => BlockFlowDirection::LeftToRight, (true, false) => BlockFlowDirection::RightToLeft, } } #[inline] pub fn inline_base_direction(&self) -> InlineBaseDirection { if self.intersects(WritingMode::RTL) { InlineBaseDirection::RightToLeft } else { InlineBaseDirection::LeftToRight } } #[inline] /// The default bidirectional embedding level for this writing mode. /// /// Returns bidi level 0 if the mode is LTR, or 1 otherwise. pub fn to_bidi_level(&self) -> bidi::Level { if self.is_bidi_ltr() { bidi::Level::ltr() } else { bidi::Level::rtl() } } #[inline] /// Is the text layout vertical? pub fn is_text_vertical(&self) -> bool { self.is_vertical() && !self.is_sideways() } } impl fmt::Display for WritingMode { fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { if self.is_vertical() { write!(formatter, "V")?; if self.is_vertical_lr() { write!(formatter, " LR")?; } else { write!(formatter, " RL")?; } if self.is_sideways() { write!(formatter, " Sideways")?; } if self.intersects(WritingMode::LINE_INVERTED) { write!(formatter, " Inverted")?; } } else { write!(formatter, "H")?; } if self.is_bidi_ltr() { write!(formatter, " LTR") } else { write!(formatter, " RTL") } } } /// Wherever logical geometry is used, the writing mode is known based on context: /// every method takes a `mode` parameter. /// However, this context is easy to get wrong. /// In debug builds only, logical geometry objects store their writing mode /// (in addition to taking it as a parameter to methods) and check it. /// In non-debug builds, make this storage zero-size and the checks no-ops. #[cfg(not(debug_assertions))] #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "servo", derive(Serialize))] struct DebugWritingMode; #[cfg(debug_assertions)] #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "servo", derive(Serialize))] struct DebugWritingMode { mode: WritingMode, } #[cfg(not(debug_assertions))] impl DebugWritingMode { #[inline] fn check(&self, _other: WritingMode) {} #[inline] fn check_debug(&self, _other: DebugWritingMode) {} #[inline] fn new(_mode: WritingMode) -> DebugWritingMode { DebugWritingMode } } #[cfg(debug_assertions)] impl DebugWritingMode { #[inline] fn check(&self, other: WritingMode) { assert_eq!(self.mode, other) } #[inline] fn check_debug(&self, other: DebugWritingMode) { assert_eq!(self.mode, other.mode) } #[inline] fn new(mode: WritingMode) -> DebugWritingMode { DebugWritingMode { mode } } } impl Debug for DebugWritingMode { #[cfg(not(debug_assertions))] fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { write!(formatter, "?") } #[cfg(debug_assertions)] fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { write!(formatter, "{}", self.mode) } } // Used to specify the logical direction. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(Serialize))] pub enum Direction { Inline, Block, } /// A 2D size in flow-relative dimensions #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "servo", derive(Serialize))] pub struct LogicalSize { pub inline: T, // inline-size, a.k.a. logical width, a.k.a. measure pub block: T, // block-size, a.k.a. logical height, a.k.a. extent debug_writing_mode: DebugWritingMode, } impl Debug for LogicalSize { fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { write!( formatter, "LogicalSize({:?}, i{:?}×b{:?})", self.debug_writing_mode, self.inline, self.block ) } } // Can not implement the Zero trait: its zero() method does not have the `mode` parameter. impl LogicalSize { #[inline] pub fn zero(mode: WritingMode) -> LogicalSize { LogicalSize { inline: Zero::zero(), block: Zero::zero(), debug_writing_mode: DebugWritingMode::new(mode), } } } impl LogicalSize { #[inline] pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize { LogicalSize { inline: inline, block: block, debug_writing_mode: DebugWritingMode::new(mode), } } #[inline] pub fn from_physical(mode: WritingMode, size: Size2D) -> LogicalSize { if mode.is_vertical() { LogicalSize::new(mode, size.height, size.width) } else { LogicalSize::new(mode, size.width, size.height) } } } impl LogicalSize { #[inline] pub fn width(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.block } else { self.inline } } #[inline] pub fn set_width(&mut self, mode: WritingMode, width: T) { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.block = width } else { self.inline = width } } #[inline] pub fn height(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.inline } else { self.block } } #[inline] pub fn set_height(&mut self, mode: WritingMode, height: T) { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.inline = height } else { self.block = height } } #[inline] pub fn to_physical(&self, mode: WritingMode) -> Size2D { self.debug_writing_mode.check(mode); if mode.is_vertical() { Size2D::new(self.block, self.inline) } else { Size2D::new(self.inline, self.block) } } #[inline] pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize { if mode_from == mode_to { self.debug_writing_mode.check(mode_from); *self } else { LogicalSize::from_physical(mode_to, self.to_physical(mode_from)) } } } impl> Add for LogicalSize { type Output = LogicalSize; #[inline] fn add(self, other: LogicalSize) -> LogicalSize { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalSize { debug_writing_mode: self.debug_writing_mode, inline: self.inline + other.inline, block: self.block + other.block, } } } impl> Sub for LogicalSize { type Output = LogicalSize; #[inline] fn sub(self, other: LogicalSize) -> LogicalSize { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalSize { debug_writing_mode: self.debug_writing_mode, inline: self.inline - other.inline, block: self.block - other.block, } } } /// A 2D point in flow-relative dimensions #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "servo", derive(Serialize))] pub struct LogicalPoint { /// inline-axis coordinate pub i: T, /// block-axis coordinate pub b: T, debug_writing_mode: DebugWritingMode, } impl Debug for LogicalPoint { fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { write!( formatter, "LogicalPoint({:?} (i{:?}, b{:?}))", self.debug_writing_mode, self.i, self.b ) } } // Can not implement the Zero trait: its zero() method does not have the `mode` parameter. impl LogicalPoint { #[inline] pub fn zero(mode: WritingMode) -> LogicalPoint { LogicalPoint { i: Zero::zero(), b: Zero::zero(), debug_writing_mode: DebugWritingMode::new(mode), } } } impl LogicalPoint { #[inline] pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint { LogicalPoint { i: i, b: b, debug_writing_mode: DebugWritingMode::new(mode), } } } impl> LogicalPoint { #[inline] pub fn from_physical( mode: WritingMode, point: Point2D, container_size: Size2D, ) -> LogicalPoint { if mode.is_vertical() { LogicalPoint { i: if mode.is_inline_tb() { point.y } else { container_size.height - point.y }, b: if mode.is_vertical_lr() { point.x } else { container_size.width - point.x }, debug_writing_mode: DebugWritingMode::new(mode), } } else { LogicalPoint { i: if mode.is_bidi_ltr() { point.x } else { container_size.width - point.x }, b: point.y, debug_writing_mode: DebugWritingMode::new(mode), } } } #[inline] pub fn x(&self, mode: WritingMode, container_size: Size2D) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_vertical_lr() { self.b } else { container_size.width - self.b } } else { if mode.is_bidi_ltr() { self.i } else { container_size.width - self.i } } } #[inline] pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D) { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.b = if mode.is_vertical_lr() { x } else { container_size.width - x } } else { self.i = if mode.is_bidi_ltr() { x } else { container_size.width - x } } } #[inline] pub fn y(&self, mode: WritingMode, container_size: Size2D) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_inline_tb() { self.i } else { container_size.height - self.i } } else { self.b } } #[inline] pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D) { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.i = if mode.is_inline_tb() { y } else { container_size.height - y } } else { self.b = y } } #[inline] pub fn to_physical(&self, mode: WritingMode, container_size: Size2D) -> Point2D { self.debug_writing_mode.check(mode); if mode.is_vertical() { Point2D::new( if mode.is_vertical_lr() { self.b } else { container_size.width - self.b }, if mode.is_inline_tb() { self.i } else { container_size.height - self.i }, ) } else { Point2D::new( if mode.is_bidi_ltr() { self.i } else { container_size.width - self.i }, self.b, ) } } #[inline] pub fn convert( &self, mode_from: WritingMode, mode_to: WritingMode, container_size: Size2D, ) -> LogicalPoint { if mode_from == mode_to { self.debug_writing_mode.check(mode_from); *self } else { LogicalPoint::from_physical( mode_to, self.to_physical(mode_from, container_size), container_size, ) } } } impl> LogicalPoint { /// This doesn’t really makes sense, /// but happens when dealing with multiple origins. #[inline] pub fn add_point(&self, other: &LogicalPoint) -> LogicalPoint { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalPoint { debug_writing_mode: self.debug_writing_mode, i: self.i + other.i, b: self.b + other.b, } } } impl> Add> for LogicalPoint { type Output = LogicalPoint; #[inline] fn add(self, other: LogicalSize) -> LogicalPoint { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalPoint { debug_writing_mode: self.debug_writing_mode, i: self.i + other.inline, b: self.b + other.block, } } } impl> Sub> for LogicalPoint { type Output = LogicalPoint; #[inline] fn sub(self, other: LogicalSize) -> LogicalPoint { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalPoint { debug_writing_mode: self.debug_writing_mode, i: self.i - other.inline, b: self.b - other.block, } } } /// A "margin" in flow-relative dimensions /// Represents the four sides of the margins, borders, or padding of a CSS box, /// or a combination of those. /// A positive "margin" can be added to a rectangle to obtain a bigger rectangle. #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "servo", derive(Serialize))] pub struct LogicalMargin { pub block_start: T, pub inline_end: T, pub block_end: T, pub inline_start: T, debug_writing_mode: DebugWritingMode, } impl Debug for LogicalMargin { fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { let writing_mode_string = if cfg!(debug_assertions) { format!("{:?}, ", self.debug_writing_mode) } else { "".to_owned() }; write!( formatter, "LogicalMargin({}i:{:?}..{:?} b:{:?}..{:?})", writing_mode_string, self.inline_start, self.inline_end, self.block_start, self.block_end ) } } impl LogicalMargin { #[inline] pub fn zero(mode: WritingMode) -> LogicalMargin { LogicalMargin { block_start: Zero::zero(), inline_end: Zero::zero(), block_end: Zero::zero(), inline_start: Zero::zero(), debug_writing_mode: DebugWritingMode::new(mode), } } } impl LogicalMargin { #[inline] pub fn new( mode: WritingMode, block_start: T, inline_end: T, block_end: T, inline_start: T, ) -> LogicalMargin { LogicalMargin { block_start, inline_end, block_end, inline_start, debug_writing_mode: DebugWritingMode::new(mode), } } #[inline] pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D) -> LogicalMargin { let block_start; let inline_end; let block_end; let inline_start; if mode.is_vertical() { if mode.is_vertical_lr() { block_start = offsets.left; block_end = offsets.right; } else { block_start = offsets.right; block_end = offsets.left; } if mode.is_inline_tb() { inline_start = offsets.top; inline_end = offsets.bottom; } else { inline_start = offsets.bottom; inline_end = offsets.top; } } else { block_start = offsets.top; block_end = offsets.bottom; if mode.is_bidi_ltr() { inline_start = offsets.left; inline_end = offsets.right; } else { inline_start = offsets.right; inline_end = offsets.left; } } LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start) } } impl LogicalMargin { #[inline] pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin { LogicalMargin::new(mode, value, value, value, value) } #[inline] pub fn top(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_inline_tb() { self.inline_start } else { self.inline_end } } else { self.block_start } } #[inline] pub fn set_top(&mut self, mode: WritingMode, top: T) { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_inline_tb() { self.inline_start = top } else { self.inline_end = top } } else { self.block_start = top } } #[inline] pub fn right(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_vertical_lr() { self.block_end } else { self.block_start } } else { if mode.is_bidi_ltr() { self.inline_end } else { self.inline_start } } } #[inline] pub fn set_right(&mut self, mode: WritingMode, right: T) { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_vertical_lr() { self.block_end = right } else { self.block_start = right } } else { if mode.is_bidi_ltr() { self.inline_end = right } else { self.inline_start = right } } } #[inline] pub fn bottom(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_inline_tb() { self.inline_end } else { self.inline_start } } else { self.block_end } } #[inline] pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_inline_tb() { self.inline_end = bottom } else { self.inline_start = bottom } } else { self.block_end = bottom } } #[inline] pub fn left(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_vertical_lr() { self.block_start } else { self.block_end } } else { if mode.is_bidi_ltr() { self.inline_start } else { self.inline_end } } } #[inline] pub fn set_left(&mut self, mode: WritingMode, left: T) { self.debug_writing_mode.check(mode); if mode.is_vertical() { if mode.is_vertical_lr() { self.block_start = left } else { self.block_end = left } } else { if mode.is_bidi_ltr() { self.inline_start = left } else { self.inline_end = left } } } #[inline] pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D { self.debug_writing_mode.check(mode); let top; let right; let bottom; let left; if mode.is_vertical() { if mode.is_vertical_lr() { left = self.block_start; right = self.block_end; } else { right = self.block_start; left = self.block_end; } if mode.is_inline_tb() { top = self.inline_start; bottom = self.inline_end; } else { bottom = self.inline_start; top = self.inline_end; } } else { top = self.block_start; bottom = self.block_end; if mode.is_bidi_ltr() { left = self.inline_start; right = self.inline_end; } else { right = self.inline_start; left = self.inline_end; } } SideOffsets2D::new(top, right, bottom, left) } #[inline] pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin { if mode_from == mode_to { self.debug_writing_mode.check(mode_from); *self } else { LogicalMargin::from_physical(mode_to, self.to_physical(mode_from)) } } } impl LogicalMargin { #[inline] pub fn is_zero(&self) -> bool { self.block_start == Zero::zero() && self.inline_end == Zero::zero() && self.block_end == Zero::zero() && self.inline_start == Zero::zero() } } impl> LogicalMargin { #[inline] pub fn inline_start_end(&self) -> T { self.inline_start + self.inline_end } #[inline] pub fn block_start_end(&self) -> T { self.block_start + self.block_end } #[inline] pub fn start_end(&self, direction: Direction) -> T { match direction { Direction::Inline => self.inline_start + self.inline_end, Direction::Block => self.block_start + self.block_end, } } #[inline] pub fn top_bottom(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.inline_start_end() } else { self.block_start_end() } } #[inline] pub fn left_right(&self, mode: WritingMode) -> T { self.debug_writing_mode.check(mode); if mode.is_vertical() { self.block_start_end() } else { self.inline_start_end() } } } impl> Add for LogicalMargin { type Output = LogicalMargin; #[inline] fn add(self, other: LogicalMargin) -> LogicalMargin { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalMargin { debug_writing_mode: self.debug_writing_mode, block_start: self.block_start + other.block_start, inline_end: self.inline_end + other.inline_end, block_end: self.block_end + other.block_end, inline_start: self.inline_start + other.inline_start, } } } impl> Sub for LogicalMargin { type Output = LogicalMargin; #[inline] fn sub(self, other: LogicalMargin) -> LogicalMargin { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalMargin { debug_writing_mode: self.debug_writing_mode, block_start: self.block_start - other.block_start, inline_end: self.inline_end - other.inline_end, block_end: self.block_end - other.block_end, inline_start: self.inline_start - other.inline_start, } } } /// A rectangle in flow-relative dimensions #[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "servo", derive(Serialize))] pub struct LogicalRect { pub start: LogicalPoint, pub size: LogicalSize, debug_writing_mode: DebugWritingMode, } impl Debug for LogicalRect { fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { let writing_mode_string = if cfg!(debug_assertions) { format!("{:?}, ", self.debug_writing_mode) } else { "".to_owned() }; write!( formatter, "LogicalRect({}i{:?}×b{:?}, @ (i{:?},b{:?}))", writing_mode_string, self.size.inline, self.size.block, self.start.i, self.start.b ) } } impl LogicalRect { #[inline] pub fn zero(mode: WritingMode) -> LogicalRect { LogicalRect { start: LogicalPoint::zero(mode), size: LogicalSize::zero(mode), debug_writing_mode: DebugWritingMode::new(mode), } } } impl LogicalRect { #[inline] pub fn new( mode: WritingMode, inline_start: T, block_start: T, inline: T, block: T, ) -> LogicalRect { LogicalRect { start: LogicalPoint::new(mode, inline_start, block_start), size: LogicalSize::new(mode, inline, block), debug_writing_mode: DebugWritingMode::new(mode), } } #[inline] pub fn from_point_size( mode: WritingMode, start: LogicalPoint, size: LogicalSize, ) -> LogicalRect { start.debug_writing_mode.check(mode); size.debug_writing_mode.check(mode); LogicalRect { start: start, size: size, debug_writing_mode: DebugWritingMode::new(mode), } } } impl + Sub> LogicalRect { #[inline] pub fn from_physical( mode: WritingMode, rect: Rect, container_size: Size2D, ) -> LogicalRect { let inline_start; let block_start; let inline; let block; if mode.is_vertical() { inline = rect.size.height; block = rect.size.width; if mode.is_vertical_lr() { block_start = rect.origin.x; } else { block_start = container_size.width - (rect.origin.x + rect.size.width); } if mode.is_inline_tb() { inline_start = rect.origin.y; } else { inline_start = container_size.height - (rect.origin.y + rect.size.height); } } else { inline = rect.size.width; block = rect.size.height; block_start = rect.origin.y; if mode.is_bidi_ltr() { inline_start = rect.origin.x; } else { inline_start = container_size.width - (rect.origin.x + rect.size.width); } } LogicalRect { start: LogicalPoint::new(mode, inline_start, block_start), size: LogicalSize::new(mode, inline, block), debug_writing_mode: DebugWritingMode::new(mode), } } #[inline] pub fn inline_end(&self) -> T { self.start.i + self.size.inline } #[inline] pub fn block_end(&self) -> T { self.start.b + self.size.block } #[inline] pub fn to_physical(&self, mode: WritingMode, container_size: Size2D) -> Rect { self.debug_writing_mode.check(mode); let x; let y; let width; let height; if mode.is_vertical() { width = self.size.block; height = self.size.inline; if mode.is_vertical_lr() { x = self.start.b; } else { x = container_size.width - self.block_end(); } if mode.is_inline_tb() { y = self.start.i; } else { y = container_size.height - self.inline_end(); } } else { width = self.size.inline; height = self.size.block; y = self.start.b; if mode.is_bidi_ltr() { x = self.start.i; } else { x = container_size.width - self.inline_end(); } } Rect { origin: Point2D::new(x, y), size: Size2D::new(width, height), } } #[inline] pub fn convert( &self, mode_from: WritingMode, mode_to: WritingMode, container_size: Size2D, ) -> LogicalRect { if mode_from == mode_to { self.debug_writing_mode.check(mode_from); *self } else { LogicalRect::from_physical( mode_to, self.to_physical(mode_from, container_size), container_size, ) } } pub fn translate_by_size(&self, offset: LogicalSize) -> LogicalRect { LogicalRect { start: self.start + offset, ..*self } } pub fn translate(&self, offset: &LogicalPoint) -> LogicalRect { LogicalRect { start: self.start + LogicalSize { inline: offset.i, block: offset.b, debug_writing_mode: offset.debug_writing_mode, }, size: self.size, debug_writing_mode: self.debug_writing_mode, } } } impl + Sub> LogicalRect { #[inline] pub fn union(&self, other: &LogicalRect) -> LogicalRect { self.debug_writing_mode .check_debug(other.debug_writing_mode); let inline_start = min(self.start.i, other.start.i); let block_start = min(self.start.b, other.start.b); LogicalRect { start: LogicalPoint { i: inline_start, b: block_start, debug_writing_mode: self.debug_writing_mode, }, size: LogicalSize { inline: max(self.inline_end(), other.inline_end()) - inline_start, block: max(self.block_end(), other.block_end()) - block_start, debug_writing_mode: self.debug_writing_mode, }, debug_writing_mode: self.debug_writing_mode, } } } impl + Sub> Add> for LogicalRect { type Output = LogicalRect; #[inline] fn add(self, other: LogicalMargin) -> LogicalRect { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalRect { start: LogicalPoint { // Growing a rectangle on the start side means pushing its // start point on the negative direction. i: self.start.i - other.inline_start, b: self.start.b - other.block_start, debug_writing_mode: self.debug_writing_mode, }, size: LogicalSize { inline: self.size.inline + other.inline_start_end(), block: self.size.block + other.block_start_end(), debug_writing_mode: self.debug_writing_mode, }, debug_writing_mode: self.debug_writing_mode, } } } impl + Sub> Sub> for LogicalRect { type Output = LogicalRect; #[inline] fn sub(self, other: LogicalMargin) -> LogicalRect { self.debug_writing_mode .check_debug(other.debug_writing_mode); LogicalRect { start: LogicalPoint { // Shrinking a rectangle on the start side means pushing its // start point on the positive direction. i: self.start.i + other.inline_start, b: self.start.b + other.block_start, debug_writing_mode: self.debug_writing_mode, }, size: LogicalSize { inline: self.size.inline - other.inline_start_end(), block: self.size.block - other.block_start_end(), debug_writing_mode: self.debug_writing_mode, }, debug_writing_mode: self.debug_writing_mode, } } } #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] pub enum LogicalAxis { Block = 0, Inline, } impl LogicalAxis { #[inline] pub fn to_physical(self, wm: WritingMode) -> PhysicalAxis { if wm.is_horizontal() == (self == Self::Inline) { PhysicalAxis::Horizontal } else { PhysicalAxis::Vertical } } } #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] pub enum LogicalSide { BlockStart = 0, BlockEnd, InlineStart, InlineEnd, } impl LogicalSide { fn is_block(self) -> bool { matches!(self, Self::BlockStart | Self::BlockEnd) } #[inline] pub fn to_physical(self, wm: WritingMode) -> PhysicalSide { // Block mapping depends only on vertical+vertical-lr static BLOCK_MAPPING: [[PhysicalSide; 2]; 4] = [ [PhysicalSide::Top, PhysicalSide::Bottom], // horizontal-tb [PhysicalSide::Right, PhysicalSide::Left], // vertical-rl [PhysicalSide::Bottom, PhysicalSide::Top], // (horizontal-bt) [PhysicalSide::Left, PhysicalSide::Right], // vertical-lr ]; if self.is_block() { let vertical = wm.is_vertical(); let lr = wm.is_vertical_lr(); let index = (vertical as usize) | ((lr as usize) << 1); return BLOCK_MAPPING[index][self as usize]; } // start = 0, end = 1 let edge = self as usize - 2; // Inline axis sides depend on all three of writing-mode, text-orientation and direction, // which are encoded in the VERTICAL, INLINE_REVERSED, VERTICAL_LR and LINE_INVERTED bits. // // bit 0 = the VERTICAL value // bit 1 = the INLINE_REVERSED value // bit 2 = the VERTICAL_LR value // bit 3 = the LINE_INVERTED value // // Note that not all of these combinations can actually be specified via CSS: there is no // horizontal-bt writing-mode, and no text-orientation value that produces "inverted" // text. (The former 'sideways-left' value, no longer in the spec, would have produced // this in vertical-rl mode.) static INLINE_MAPPING: [[PhysicalSide; 2]; 16] = [ [PhysicalSide::Left, PhysicalSide::Right], // horizontal-tb ltr [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-rl ltr [PhysicalSide::Right, PhysicalSide::Left], // horizontal-tb rtl [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-rl rtl [PhysicalSide::Right, PhysicalSide::Left], // (horizontal-bt) (inverted) ltr [PhysicalSide::Top, PhysicalSide::Bottom], // sideways-lr rtl [PhysicalSide::Left, PhysicalSide::Right], // (horizontal-bt) (inverted) rtl [PhysicalSide::Bottom, PhysicalSide::Top], // sideways-lr ltr [PhysicalSide::Left, PhysicalSide::Right], // horizontal-tb (inverted) rtl [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-rl (inverted) rtl [PhysicalSide::Right, PhysicalSide::Left], // horizontal-tb (inverted) ltr [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-rl (inverted) ltr [PhysicalSide::Left, PhysicalSide::Right], // (horizontal-bt) ltr [PhysicalSide::Top, PhysicalSide::Bottom], // vertical-lr ltr [PhysicalSide::Right, PhysicalSide::Left], // (horizontal-bt) rtl [PhysicalSide::Bottom, PhysicalSide::Top], // vertical-lr rtl ]; debug_assert!( WritingMode::VERTICAL.bits() == 0x01 && WritingMode::INLINE_REVERSED.bits() == 0x02 && WritingMode::VERTICAL_LR.bits() == 0x04 && WritingMode::LINE_INVERTED.bits() == 0x08 ); let index = (wm.bits() & 0xF) as usize; INLINE_MAPPING[index][edge] } } #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] pub enum LogicalCorner { StartStart = 0, StartEnd, EndStart, EndEnd, } impl LogicalCorner { #[inline] pub fn to_physical(self, wm: WritingMode) -> PhysicalCorner { static CORNER_TO_SIDES: [[LogicalSide; 2]; 4] = [ [LogicalSide::BlockStart, LogicalSide::InlineStart], [LogicalSide::BlockStart, LogicalSide::InlineEnd], [LogicalSide::BlockEnd, LogicalSide::InlineStart], [LogicalSide::BlockEnd, LogicalSide::InlineEnd], ]; let [block, inline] = CORNER_TO_SIDES[self as usize]; let block = block.to_physical(wm); let inline = inline.to_physical(wm); PhysicalCorner::from_sides(block, inline) } } #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] pub enum PhysicalAxis { Vertical = 0, Horizontal, } #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] pub enum PhysicalSide { Top = 0, Right, Bottom, Left, } impl PhysicalSide { fn orthogonal_to(self, other: Self) -> bool { matches!(self, Self::Top | Self::Bottom) != matches!(other, Self::Top | Self::Bottom) } } #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] pub enum PhysicalCorner { TopLeft = 0, TopRight, BottomRight, BottomLeft, } impl PhysicalCorner { fn from_sides(a: PhysicalSide, b: PhysicalSide) -> Self { debug_assert!(a.orthogonal_to(b), "Sides should be orthogonal"); // Only some of these are possible, since we expect only orthogonal values. If the two // sides were to be parallel, we fall back to returning TopLeft. const IMPOSSIBLE: PhysicalCorner = PhysicalCorner::TopLeft; static SIDES_TO_CORNER: [[PhysicalCorner; 4]; 4] = [ [ IMPOSSIBLE, PhysicalCorner::TopRight, IMPOSSIBLE, PhysicalCorner::TopLeft, ], [ PhysicalCorner::TopRight, IMPOSSIBLE, PhysicalCorner::BottomRight, IMPOSSIBLE, ], [ IMPOSSIBLE, PhysicalCorner::BottomRight, IMPOSSIBLE, PhysicalCorner::BottomLeft, ], [ PhysicalCorner::TopLeft, IMPOSSIBLE, PhysicalCorner::BottomLeft, IMPOSSIBLE, ], ]; SIDES_TO_CORNER[a as usize][b as usize] } }