diff options
author | Simon Sapin <simon.sapin@exyr.org> | 2014-05-26 21:24:38 +0100 |
---|---|---|
committer | Simon Sapin <simon.sapin@exyr.org> | 2014-07-09 20:57:21 +0100 |
commit | c4bbf54fe30d69d76f41c53ad5b12172c3bb12b1 (patch) | |
tree | b595ecc5011b7e784b6f25c2d1dcd45c09920e34 | |
parent | 94909f2d087ff0568d34b259c5fda38dfb949bc7 (diff) | |
download | servo-c4bbf54fe30d69d76f41c53ad5b12172c3bb12b1.tar.gz servo-c4bbf54fe30d69d76f41c53ad5b12172c3bb12b1.zip |
Add logical geometry primitives, for CSS Writing Modes.
-rw-r--r-- | src/components/style/properties/mod.rs.mako | 50 | ||||
-rw-r--r-- | src/components/util/logical_geometry.rs | 944 | ||||
-rw-r--r-- | src/components/util/util.rs | 1 |
3 files changed, 995 insertions, 0 deletions
diff --git a/src/components/style/properties/mod.rs.mako b/src/components/style/properties/mod.rs.mako index 3157ac8c5fd..55ec83cc038 100644 --- a/src/components/style/properties/mod.rs.mako +++ b/src/components/style/properties/mod.rs.mako @@ -1035,6 +1035,17 @@ pub mod longhands { ${single_keyword("table-layout", "auto fixed")} // CSS 2.1, Section 18 - User interface + + + // CSS Writing Modes Level 3 + // http://dev.w3.org/csswg/css-writing-modes/ + ${switch_to_style_struct("InheritedBox")} + + ${single_keyword("writing-mode", "horizontal-tb vertical-rl vertical-lr")} + + // FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support) + // FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented + ${single_keyword("text-orientation", "sideways sideways-left sideways-right")} } @@ -1565,6 +1576,9 @@ impl PropertyDeclaration { pub mod style_structs { use super::longhands; + use super::computed_values; + use servo_util::logical_geometry::WritingMode; + % for style_struct in STYLE_STRUCTS: #[deriving(PartialEq, Clone)] pub struct ${style_struct.name} { @@ -1573,6 +1587,42 @@ pub mod style_structs { % endfor } % endfor + + impl InheritedBox { + /// Return a WritingMode bitflags from the relevant CSS properties. + pub fn get_writing_mode(&self) -> WritingMode { + use servo_util::logical_geometry; + let mut flags = WritingMode::empty(); + match self.direction { + computed_values::direction::ltr => {}, + computed_values::direction::rtl => { + flags.insert(logical_geometry::FlagRTL); + }, + } + match self.writing_mode { + computed_values::writing_mode::horizontal_tb => {}, + computed_values::writing_mode::vertical_rl => { + flags.insert(logical_geometry::FlagVertical); + }, + computed_values::writing_mode::vertical_lr => { + flags.insert(logical_geometry::FlagVertical); + flags.insert(logical_geometry::FlagVerticalLR); + }, + } + match self.text_orientation { + computed_values::text_orientation::sideways_right => {}, + computed_values::text_orientation::sideways_left => { + flags.insert(logical_geometry::FlagSidewaysLeft); + }, + computed_values::text_orientation::sideways => { + if flags.intersects(logical_geometry::FlagVerticalLR) { + flags.insert(logical_geometry::FlagSidewaysLeft); + } + }, + } + flags + } + } } #[deriving(Clone)] diff --git a/src/components/util/logical_geometry.rs b/src/components/util/logical_geometry.rs new file mode 100644 index 00000000000..bd0ea74fed4 --- /dev/null +++ b/src/components/util/logical_geometry.rs @@ -0,0 +1,944 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +/// Geometry in flow-relative space. + +use geom::{Size2D, Point2D, SideOffsets2D, Rect}; +use std::fmt::{Show, Formatter, FormatError}; +use std::num::Zero; + + +bitflags!( + flags WritingMode: u8 { + static FlagRTL = 1 << 0, + static FlagVertical = 1 << 1, + static FlagVerticalLR = 1 << 2, + static FlagSidewaysLeft = 1 << 3 + } +) + +impl WritingMode { + #[inline] + pub fn is_vertical(&self) -> bool { + self.intersects(FlagVertical) + } + + /// Asuming .is_vertical(), does the block direction go left to right? + #[inline] + pub fn is_vertical_lr(&self) -> bool { + self.intersects(FlagVerticalLR) + } + + /// Asuming .is_vertical(), does the inline direction go top to bottom? + #[inline] + pub fn is_inline_tb(&self) -> bool { + !(self.intersects(FlagSidewaysLeft) ^ self.intersects(FlagRTL)) + } + + #[inline] + pub fn is_bidi_ltr(&self) -> bool { + !self.intersects(FlagRTL) + } +} + +impl Show for WritingMode { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + if self.is_vertical() { + try!(write!(formatter, "V")); + if self.is_vertical_lr() { + try!(write!(formatter, " LR")); + } else { + try!(write!(formatter, " RL")); + } + if self.intersects(FlagSidewaysLeft) { + try!(write!(formatter, " SidewaysL")); + } + } else { + try!(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(ndebug)] +#[deriving(PartialEq, Eq, Clone)] +struct DebugWritingMode; + +#[cfg(not(ndebug))] +#[deriving(PartialEq, Eq, Clone)] +struct DebugWritingMode { + mode: WritingMode +} + +#[cfg(ndebug)] +impl DebugWritingMode { + #[inline] + fn check(&self, _other: WritingMode) {} + + #[inline] + fn check_debug(&self, _other: DebugWritingMode) {} + + #[inline] + fn new(_mode: WritingMode) -> DebugWritingMode { + DebugWritingMode + } +} + +#[cfg(not(ndebug))] +impl DebugWritingMode { + #[inline] + fn check(&self, other: WritingMode) { + assert!(self.mode == other) + } + + #[inline] + fn check_debug(&self, other: DebugWritingMode) { + assert!(self.mode == other.mode) + } + + #[inline] + fn new(mode: WritingMode) -> DebugWritingMode { + DebugWritingMode { mode: mode } + } +} + +impl Show for DebugWritingMode { + #[cfg(ndebug)] + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "?") + } + + #[cfg(not(ndebug))] + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + self.mode.fmt(formatter) + } +} + + +/// A 2D size in flow-relative dimensions +#[deriving(PartialEq, Eq, Clone)] +pub struct LogicalSize<T> { + pub isize: T, // inline-size (a.k.a. logical width) + pub bsize: T, // block-size (a.k.a. logical height) + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalSize<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "LogicalSize[{}, {}, {}]", + self.debug_writing_mode, self.isize, self.bsize) + } +} + +// 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 { + isize: Zero::zero(), + bsize: Zero::zero(), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.isize.is_zero() && self.bsize.is_zero() + } +} + +impl<T: Copy> LogicalSize<T> { + #[inline] + pub fn new(mode: WritingMode, isize: T, bsize: T) -> LogicalSize<T> { + LogicalSize { + isize: isize, + bsize: bsize, + 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) + } + } + + #[inline] + pub fn width(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.bsize + } else { + self.isize + } + } + + #[inline] + pub fn set_width(&mut self, mode: WritingMode, width: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.bsize = width + } else { + self.isize = width + } + } + + #[inline] + pub fn height(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.isize + } else { + self.bsize + } + } + + #[inline] + pub fn set_height(&mut self, mode: WritingMode, height: T) { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.isize = height + } else { + self.bsize = height + } + } + + #[inline] + pub fn to_physical(&self, mode: WritingMode) -> Size2D<T> { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + Size2D { width: self.bsize, height: self.isize } + } else { + Size2D { width: self.isize, height: self.bsize } + } + } + + #[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, T>> Add<LogicalSize<T>, LogicalSize<T>> for 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, + isize: self.isize + other.isize, + bsize: self.bsize + other.bsize, + } + } +} + +impl<T: Sub<T, T>> Sub<LogicalSize<T>, LogicalSize<T>> for 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, + isize: self.isize - other.isize, + bsize: self.bsize - other.bsize, + } + } +} + + +/// A 2D point in flow-relative dimensions +#[deriving(PartialEq, Eq, Clone)] +pub struct LogicalPoint<T> { + pub i: T, /// inline-axis coordinate + pub b: T, /// block-axis coordinate + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalPoint<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "LogicalPoint[{}, {}, {}]", + 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), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.i.is_zero() && self.b.is_zero() + } +} + +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, 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 { + x: if mode.is_vertical_lr() { self.b } else { container_size.width - self.b }, + y: if mode.is_inline_tb() { self.i } else { container_size.height - self.i } + } + } else { + Point2D { + x: if mode.is_bidi_ltr() { self.i } else { container_size.width - self.i }, + y: 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: Add<T,T>> Add<LogicalSize<T>, LogicalPoint<T>> for 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.isize, + b: self.b + other.bsize, + } + } +} + +impl<T: Sub<T,T>> Sub<LogicalSize<T>, LogicalPoint<T>> for 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.isize, + b: self.b - other.bsize, + } + } +} + + +/// 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. +#[deriving(PartialEq, Eq, Clone)] +pub struct LogicalMargin<T> { + pub bstart: T, + pub iend: T, + pub bend: T, + pub istart: T, + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalMargin<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "LogicalMargin[{}, bstart: {}, iend: {}, bend: {}, istart: {}]", + self.debug_writing_mode, self.bstart, self.iend, self.bend, self.istart) + } +} + +impl<T: Zero> LogicalMargin<T> { + #[inline] + pub fn zero(mode: WritingMode) -> LogicalMargin<T> { + LogicalMargin { + bstart: Zero::zero(), + iend: Zero::zero(), + bend: Zero::zero(), + istart: Zero::zero(), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.bstart.is_zero() && + self.iend.is_zero() && + self.bend.is_zero() && + self.istart.is_zero() + } +} + +impl<T: Copy> LogicalMargin<T> { + #[inline] + pub fn new(mode: WritingMode, bstart: T, iend: T, bend: T, istart: T) -> LogicalMargin<T> { + LogicalMargin { + bstart: bstart, + iend: iend, + bend: bend, + istart: istart, + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> { + let bstart; + let iend; + let bend; + let istart; + if mode.is_vertical() { + if mode.is_vertical_lr() { + bstart = offsets.left; + bend = offsets.right; + } else { + bstart = offsets.right; + bend = offsets.left; + } + if mode.is_inline_tb() { + istart = offsets.top; + iend = offsets.bottom; + } else { + istart = offsets.bottom; + iend = offsets.top; + } + } else { + bstart = offsets.top; + bend = offsets.bottom; + if mode.is_bidi_ltr() { + istart = offsets.left; + iend = offsets.right; + } else { + istart = offsets.right; + iend = offsets.left; + } + } + LogicalMargin::new(mode, bstart, iend, bend, istart) + } + + #[inline] + pub fn top(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + if mode.is_inline_tb() { self.istart } else { self.iend } + } else { + self.bstart + } + } + + #[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.istart = top } else { self.iend = top } + } else { + self.bstart = 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.bend } else { self.bstart } + } else { + if mode.is_bidi_ltr() { self.iend } else { self.istart } + } + } + + #[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.bend = right } else { self.bstart = right } + } else { + if mode.is_bidi_ltr() { self.iend = right } else { self.istart = 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.iend } else { self.istart } + } else { + self.bend + } + } + + #[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.iend = bottom } else { self.istart = bottom } + } else { + self.bend = 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.bstart } else { self.bend } + } else { + if mode.is_bidi_ltr() { self.istart } else { self.iend } + } + } + + #[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.bstart = left } else { self.bend = left } + } else { + if mode.is_bidi_ltr() { self.istart = left } else { self.iend = 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.bstart; + right = self.bend; + } else { + right = self.bstart; + left = self.bend; + } + if mode.is_inline_tb() { + top = self.istart; + bottom = self.iend; + } else { + bottom = self.istart; + top = self.iend; + } + } else { + top = self.bstart; + bottom = self.bend; + if mode.is_bidi_ltr() { + left = self.istart; + right = self.iend; + } else { + right = self.istart; + left = self.iend; + } + } + 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: Add<T, T>> LogicalMargin<T> { + #[inline] + pub fn istart_end(&self) -> T { + self.istart + self.iend + } + + #[inline] + pub fn bstart_end(&self) -> T { + self.bstart + self.bend + } + + #[inline] + pub fn top_bottom(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.istart_end() + } else { + self.bstart_end() + } + } + + #[inline] + pub fn left_right(&self, mode: WritingMode) -> T { + self.debug_writing_mode.check(mode); + if mode.is_vertical() { + self.bstart_end() + } else { + self.istart_end() + } + } +} + +impl<T: Add<T, T>> Add<LogicalMargin<T>, LogicalMargin<T>> for 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, + bstart: self.bstart + other.bstart, + iend: self.iend + other.iend, + bend: self.bend + other.bend, + istart: self.istart + other.istart, + } + } +} + +impl<T: Sub<T, T>> Sub<LogicalMargin<T>, LogicalMargin<T>> for 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, + bstart: self.bstart - other.bstart, + iend: self.iend - other.iend, + bend: self.bend - other.bend, + istart: self.istart - other.istart, + } + } +} + + +/// A rectangle in flow-relative dimensions +#[deriving(PartialEq, Eq, Clone)] +pub struct LogicalRect<T> { + pub start: LogicalPoint<T>, + pub size: LogicalSize<T>, + debug_writing_mode: DebugWritingMode, +} + +impl<T: Show> Show for LogicalRect<T> { + fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> { + write!(formatter, "LogicalRect[{}, istart: {}, bstart: {}, isize: {}, bsize: {}]", + self.debug_writing_mode, self.start.i, self.start.b, + self.size.isize, self.size.bsize) + } +} + +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), + } + } + + #[inline] + pub fn is_zero(&self) -> bool { + self.start.is_zero() && self.size.is_zero() + } +} + +impl<T: Copy> LogicalRect<T> { + #[inline] + pub fn new(mode: WritingMode, istart: T, bstart: T, isize: T, bsize: T) -> LogicalRect<T> { + LogicalRect { + start: LogicalPoint::new(mode, istart, bstart), + size: LogicalSize::new(mode, isize, bsize), + debug_writing_mode: DebugWritingMode::new(mode), + } + } +} + +impl<T: Copy + Add<T, T> + Sub<T, T>> LogicalRect<T> { + #[inline] + pub fn from_physical(mode: WritingMode, rect: Rect<T>, container_size: Size2D<T>) + -> LogicalRect<T> { + let istart; + let bstart; + let isize; + let bsize; + if mode.is_vertical() { + isize = rect.size.height; + bsize = rect.size.width; + if mode.is_vertical_lr() { + bstart = rect.origin.x; + } else { + bstart = container_size.width - (rect.origin.x + rect.size.width); + } + if mode.is_inline_tb() { + istart = rect.origin.y; + } else { + istart = container_size.height - (rect.origin.y + rect.size.height); + } + } else { + isize = rect.size.width; + bsize = rect.size.height; + bstart = rect.origin.y; + if mode.is_bidi_ltr() { + istart = rect.origin.x; + } else { + istart = container_size.width - (rect.origin.x + rect.size.width); + } + } + LogicalRect { + start: LogicalPoint::new(mode, istart, bstart), + size: LogicalSize::new(mode, isize, bsize), + debug_writing_mode: DebugWritingMode::new(mode), + } + } + + #[inline] + pub fn iend(&self) -> T { + self.start.i + self.size.isize + } + + #[inline] + pub fn bend(&self) -> T { + self.start.b + self.size.bsize + } + + #[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.bsize; + height = self.size.isize; + if mode.is_vertical_lr() { + x = self.start.b; + } else { + x = container_size.width - self.bend(); + } + if mode.is_inline_tb() { + y = self.start.i; + } else { + y = container_size.height - self.iend(); + } + } else { + width = self.size.isize; + height = self.size.bsize; + y = self.start.b; + if mode.is_bidi_ltr() { + x = self.start.i; + } else { + x = container_size.width - self.iend(); + } + } + Rect { + origin: Point2D { x: x, y: y }, + size: Size2D { width: width, height: 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) + } + } +} + +impl<T: Add<T, T> + Sub<T, T>> Add<LogicalMargin<T>, LogicalRect<T>> for 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.istart, + b: self.start.b - other.bstart, + debug_writing_mode: self.debug_writing_mode, + }, + size: LogicalSize { + isize: self.size.isize + other.istart_end(), + bsize: self.size.bsize + other.bstart_end(), + debug_writing_mode: self.debug_writing_mode, + }, + debug_writing_mode: self.debug_writing_mode, + } + } +} + + +impl<T: Add<T, T> + Sub<T, T>> Sub<LogicalMargin<T>, LogicalRect<T>> for 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.istart, + b: self.start.b + other.bstart, + debug_writing_mode: self.debug_writing_mode, + }, + size: LogicalSize { + isize: self.size.isize - other.istart_end(), + bsize: self.size.bsize - other.bstart_end(), + debug_writing_mode: self.debug_writing_mode, + }, + debug_writing_mode: self.debug_writing_mode, + } + } +} + +#[cfg(test)] +fn modes() -> [WritingMode, ..10] { + [ + WritingMode::empty(), + FlagVertical, + FlagVertical | FlagVerticalLR, + FlagVertical | FlagVerticalLR | FlagSidewaysLeft, + FlagVertical | FlagSidewaysLeft, + FlagRTL, + FlagVertical | FlagRTL, + FlagVertical | FlagVerticalLR | FlagRTL, + FlagVertical | FlagVerticalLR | FlagSidewaysLeft | FlagRTL, + FlagVertical | FlagSidewaysLeft | FlagRTL, + ] +} + +#[test] +fn test_size_round_trip() { + let physical = Size2D(1, 2); + for &mode in modes().iter() { + let logical = LogicalSize::from_physical(mode, physical); + assert!(logical.to_physical(mode) == physical); + assert!(logical.width(mode) == 1); + assert!(logical.height(mode) == 2); + } +} + +#[test] +fn test_point_round_trip() { + let physical = Point2D(1, 2); + let container = Size2D(100, 200); + for &mode in modes().iter() { + let logical = LogicalPoint::from_physical(mode, physical, container); + assert!(logical.to_physical(mode, container) == physical); + assert!(logical.x(mode, container) == 1); + assert!(logical.y(mode, container) == 2); + } +} + +#[test] +fn test_margin_round_trip() { + let physical = SideOffsets2D::new(1, 2, 3, 4); + for &mode in modes().iter() { + let logical = LogicalMargin::from_physical(mode, physical); + assert!(logical.to_physical(mode) == physical); + assert!(logical.top(mode) == 1); + assert!(logical.right(mode) == 2); + assert!(logical.bottom(mode) == 3); + assert!(logical.left(mode) == 4); + } +} + +#[test] +fn test_rect_round_trip() { + let physical = Rect(Point2D(1, 2), Size2D(3, 4)); + let container = Size2D(100, 200); + for &mode in modes().iter() { + let logical = LogicalRect::from_physical(mode, physical, container); + assert!(logical.to_physical(mode, container) == physical); + } +} diff --git a/src/components/util/util.rs b/src/components/util/util.rs index 31f0b2295c9..1f60ce6c0d6 100644 --- a/src/components/util/util.rs +++ b/src/components/util/util.rs @@ -32,6 +32,7 @@ extern crate std_url = "url"; pub mod cache; pub mod debug_utils; pub mod geometry; +pub mod logical_geometry; pub mod memory; pub mod namespace; pub mod opts; |