summaryrefslogtreecommitdiffstats
path: root/servo/components/style/logical_geometry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/logical_geometry.rs')
-rw-r--r--servo/components/style/logical_geometry.rs1472
1 files changed, 1472 insertions, 0 deletions
diff --git a/servo/components/style/logical_geometry.rs b/servo/components/style/logical_geometry.rs
new file mode 100644
index 0000000000..b8a7927dbb
--- /dev/null
+++ b/servo/components/style/logical_geometry.rs
@@ -0,0 +1,1472 @@
+/* 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()
+bitflags!(
+ #[derive(Clone, Copy, Eq, MallocSizeOf, PartialEq, Serialize)]
+ #[repr(C)]
+ pub struct 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]
+ fn physical_sides_to_corner(
+ block_side: PhysicalSide,
+ inline_side: PhysicalSide,
+ ) -> PhysicalCorner {
+ match (block_side, inline_side) {
+ (PhysicalSide::Top, PhysicalSide::Left) | (PhysicalSide::Left, PhysicalSide::Top) => {
+ PhysicalCorner::TopLeft
+ },
+ (PhysicalSide::Top, PhysicalSide::Right) | (PhysicalSide::Right, PhysicalSide::Top) => {
+ PhysicalCorner::TopRight
+ },
+ (PhysicalSide::Bottom, PhysicalSide::Right) |
+ (PhysicalSide::Right, PhysicalSide::Bottom) => PhysicalCorner::BottomRight,
+ (PhysicalSide::Bottom, PhysicalSide::Left) |
+ (PhysicalSide::Left, PhysicalSide::Bottom) => PhysicalCorner::BottomLeft,
+ _ => unreachable!("block and inline sides must be orthogonal"),
+ }
+ }
+
+ #[inline]
+ pub fn start_start_physical_corner(&self) -> PhysicalCorner {
+ WritingMode::physical_sides_to_corner(
+ self.block_start_physical_side(),
+ self.inline_start_physical_side(),
+ )
+ }
+
+ #[inline]
+ pub fn start_end_physical_corner(&self) -> PhysicalCorner {
+ WritingMode::physical_sides_to_corner(
+ self.block_start_physical_side(),
+ self.inline_end_physical_side(),
+ )
+ }
+
+ #[inline]
+ pub fn end_start_physical_corner(&self) -> PhysicalCorner {
+ WritingMode::physical_sides_to_corner(
+ self.block_end_physical_side(),
+ self.inline_start_physical_side(),
+ )
+ }
+
+ #[inline]
+ pub fn end_end_physical_corner(&self) -> PhysicalCorner {
+ WritingMode::physical_sides_to_corner(
+ 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()
+ }
+ }
+}
+
+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<T> {
+ 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<T: Debug> Debug for LogicalSize<T> {
+ 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<T: Zero> LogicalSize<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalSize<T> {
+ LogicalSize {
+ inline: Zero::zero(),
+ block: Zero::zero(),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T> LogicalSize<T> {
+ #[inline]
+ pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize<T> {
+ LogicalSize {
+ inline: inline,
+ block: block,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn from_physical(mode: WritingMode, size: Size2D<T>) -> LogicalSize<T> {
+ if mode.is_vertical() {
+ LogicalSize::new(mode, size.height, size.width)
+ } else {
+ LogicalSize::new(mode, size.width, size.height)
+ }
+ }
+}
+
+impl<T: Copy> LogicalSize<T> {
+ #[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<T> {
+ 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<T> {
+ 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<T: Add<T, Output = T>> Add for LogicalSize<T> {
+ type Output = LogicalSize<T>;
+
+ #[inline]
+ fn add(self, other: LogicalSize<T>) -> LogicalSize<T> {
+ 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<T: Sub<T, Output = T>> Sub for LogicalSize<T> {
+ type Output = LogicalSize<T>;
+
+ #[inline]
+ fn sub(self, other: LogicalSize<T>) -> LogicalSize<T> {
+ 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<T> {
+ /// inline-axis coordinate
+ pub i: T,
+ /// block-axis coordinate
+ pub b: T,
+ debug_writing_mode: DebugWritingMode,
+}
+
+impl<T: Debug> Debug for LogicalPoint<T> {
+ 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<T: Zero> LogicalPoint<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalPoint<T> {
+ LogicalPoint {
+ i: Zero::zero(),
+ b: Zero::zero(),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T: Copy> LogicalPoint<T> {
+ #[inline]
+ pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint<T> {
+ LogicalPoint {
+ i: i,
+ b: b,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T: Copy + Sub<T, Output = T>> LogicalPoint<T> {
+ #[inline]
+ pub fn from_physical(
+ mode: WritingMode,
+ point: Point2D<T>,
+ container_size: Size2D<T>,
+ ) -> LogicalPoint<T> {
+ 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>) -> 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<T>) {
+ 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>) -> 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<T>) {
+ 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<T>) -> Point2D<T> {
+ 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<T>,
+ ) -> LogicalPoint<T> {
+ 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<T: Copy + Add<T, Output = T>> LogicalPoint<T> {
+ /// This doesn’t really makes sense,
+ /// but happens when dealing with multiple origins.
+ #[inline]
+ pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> {
+ 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<T: Copy + Add<T, Output = T>> Add<LogicalSize<T>> for LogicalPoint<T> {
+ type Output = LogicalPoint<T>;
+
+ #[inline]
+ fn add(self, other: LogicalSize<T>) -> LogicalPoint<T> {
+ 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<T: Copy + Sub<T, Output = T>> Sub<LogicalSize<T>> for LogicalPoint<T> {
+ type Output = LogicalPoint<T>;
+
+ #[inline]
+ fn sub(self, other: LogicalSize<T>) -> LogicalPoint<T> {
+ 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<T> {
+ pub block_start: T,
+ pub inline_end: T,
+ pub block_end: T,
+ pub inline_start: T,
+ debug_writing_mode: DebugWritingMode,
+}
+
+impl<T: Debug> Debug for LogicalMargin<T> {
+ 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<T: Zero> LogicalMargin<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalMargin<T> {
+ LogicalMargin {
+ block_start: Zero::zero(),
+ inline_end: Zero::zero(),
+ block_end: Zero::zero(),
+ inline_start: Zero::zero(),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T> LogicalMargin<T> {
+ #[inline]
+ pub fn new(
+ mode: WritingMode,
+ block_start: T,
+ inline_end: T,
+ block_end: T,
+ inline_start: T,
+ ) -> LogicalMargin<T> {
+ LogicalMargin {
+ block_start,
+ inline_end,
+ block_end,
+ inline_start,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> {
+ 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<T: Copy> LogicalMargin<T> {
+ #[inline]
+ pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> {
+ 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<T> {
+ 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<T> {
+ 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<T: PartialEq + Zero> LogicalMargin<T> {
+ #[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<T: Copy + Add<T, Output = T>> LogicalMargin<T> {
+ #[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<T: Add<T, Output = T>> Add for LogicalMargin<T> {
+ type Output = LogicalMargin<T>;
+
+ #[inline]
+ fn add(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
+ 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<T: Sub<T, Output = T>> Sub for LogicalMargin<T> {
+ type Output = LogicalMargin<T>;
+
+ #[inline]
+ fn sub(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
+ 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<T> {
+ pub start: LogicalPoint<T>,
+ pub size: LogicalSize<T>,
+ debug_writing_mode: DebugWritingMode,
+}
+
+impl<T: Debug> Debug for LogicalRect<T> {
+ 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<T: Zero> LogicalRect<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalRect<T> {
+ LogicalRect {
+ start: LogicalPoint::zero(mode),
+ size: LogicalSize::zero(mode),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T: Copy> LogicalRect<T> {
+ #[inline]
+ pub fn new(
+ mode: WritingMode,
+ inline_start: T,
+ block_start: T,
+ inline: T,
+ block: T,
+ ) -> LogicalRect<T> {
+ 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<T>,
+ size: LogicalSize<T>,
+ ) -> LogicalRect<T> {
+ start.debug_writing_mode.check(mode);
+ size.debug_writing_mode.check(mode);
+ LogicalRect {
+ start: start,
+ size: size,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
+ #[inline]
+ pub fn from_physical(
+ mode: WritingMode,
+ rect: Rect<T>,
+ container_size: Size2D<T>,
+ ) -> LogicalRect<T> {
+ 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<T>) -> Rect<T> {
+ 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<T>,
+ ) -> LogicalRect<T> {
+ 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<T>) -> LogicalRect<T> {
+ LogicalRect {
+ start: self.start + offset,
+ ..*self
+ }
+ }
+
+ pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> {
+ 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<T: Copy + Ord + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
+ #[inline]
+ pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> {
+ 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<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Add<LogicalMargin<T>> for LogicalRect<T> {
+ type Output = LogicalRect<T>;
+
+ #[inline]
+ fn add(self, other: LogicalMargin<T>) -> LogicalRect<T> {
+ 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<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Sub<LogicalMargin<T>> for LogicalRect<T> {
+ type Output = LogicalRect<T>;
+
+ #[inline]
+ fn sub(self, other: LogicalMargin<T>) -> LogicalRect<T> {
+ 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)]
+pub enum PhysicalSide {
+ Top,
+ Right,
+ Bottom,
+ Left,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum PhysicalCorner {
+ TopLeft,
+ TopRight,
+ BottomRight,
+ BottomLeft,
+}