servo/components/layout_2020/geom.rs
Martin Robinson 56280c6242
layout: Add initial support for bidirectional text (BiDi) (#33148)
This adds supports for right-to-left text assigning bidi levels to all
line items when necessary. This includes support for the `dir` attribute
as well as corresponding CSS properties like `unicode-bidi`. It only
implements right-to-left rendering for inline layout at the moment and
doesn't include support for `dir=auto`. Because of missing features,
this causes quite a few tests to start failing, as references become
incorrect due to right-to-left rendering being active in some cases,
but not others (before it didn't exist at all).

Analysis of most of the new failures:

```
- /css/css-flexbox/gap-001-rtl.html
  /css/css-flexbox/gap-004-rtl.html
 - Require implementing BiDi in Flexbox, because the start and
   end inline margins are opposite the order of items.

- /css/CSS2/bidi-text/direction-applies-to-*.xht
  /css/CSS2/bidi-text/direction-applies-to-002.xht
  /css/CSS2/bidi-text/direction-applies-to-003.xht
  /css/CSS2/bidi-text/direction-applies-to-004.xht
  - Broken due to a bug in tables, not allocating the
    right amount of width for a column.

- /css/css-lists/inline-list.html
  - This fails because we wrongly insert a soft wrap opportunity between the
    start of an inline box and its first content.

- /css/css-text/bidi/bidi-lines-001.html
  /css/css-text/bidi/bidi-lines-002.html
  /css/CSS2/text/bidi-flag-emoji.html
  - We do not fully support unicode-bidi: plaintext

- /css/css-text/text-align/text-align-end-010.html
  /css/css-text/text-align/text-align-justify-006.html
  /css/css-text/text-align/text-align-start-010.html
  /html/dom/elements/global-attributes/*
  - We do not support dir=auto yet.

- /css/css-text/white-space/tab-bidi-001.html
  - Servo doesn't support tab stops

- /css/CSS2/positioning/abspos-block-level-001.html
  /css/css-text/word-break/word-break-normal-ar-000.html
  - Do not yet support RTL layout in block

- /css/css-text/white-space/pre-wrap-018.html
  - Even in RTL contexts, spaces at the end of the line must hang and
    not be reordered

- /css/css-text/white-space/trailing-space-and-text-alignment-rtl-002.html
  - We are letting spaces hang with white-space: pre, but they shouldn't
    hang.
```

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Rakhi Sharma <atbrakhi@igalia.com>
2024-08-21 14:28:54 +00:00

567 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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/. */
use std::convert::From;
use std::fmt;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use app_units::Au;
use serde::Serialize;
use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection, WritingMode};
use style::values::computed::{CSSPixelLength, Length, LengthPercentage};
use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr;
use style::Zero;
use style_traits::CSSPixel;
use crate::ContainingBlock;
pub type PhysicalPoint<U> = euclid::Point2D<U, CSSPixel>;
pub type PhysicalSize<U> = euclid::Size2D<U, CSSPixel>;
pub type PhysicalRect<U> = euclid::Rect<U, CSSPixel>;
pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;
pub type AuOrAuto = AutoOr<Au>;
pub type LengthOrAuto = AutoOr<Length>;
pub type LengthPercentageOrAuto<'a> = AutoOr<&'a LengthPercentage>;
#[derive(Clone, Copy, Serialize)]
pub struct LogicalVec2<T> {
pub inline: T,
pub block: T,
}
#[derive(Clone, Copy, Serialize)]
pub struct LogicalRect<T> {
pub start_corner: LogicalVec2<T>,
pub size: LogicalVec2<T>,
}
#[derive(Clone, Copy, Debug, Serialize)]
pub struct LogicalSides<T> {
pub inline_start: T,
pub inline_end: T,
pub block_start: T,
pub block_end: T,
}
impl<T: fmt::Debug> fmt::Debug for LogicalVec2<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Not using f.debug_struct on purpose here, to keep {:?} output somewhat compact
f.write_str("Vec2 { i: ")?;
self.inline.fmt(f)?;
f.write_str(", b: ")?;
self.block.fmt(f)?;
f.write_str(" }")
}
}
impl<T: Clone> LogicalVec2<T> {
pub fn from_physical_size(physical_size: &PhysicalSize<T>, mode: WritingMode) -> Self {
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
let (i, b) = if mode.is_horizontal() {
(&physical_size.width, &physical_size.height)
} else {
(&physical_size.height, &physical_size.width)
};
LogicalVec2 {
inline: i.clone(),
block: b.clone(),
}
}
pub fn from_physical_point(physical_point: &PhysicalPoint<T>, mode: WritingMode) -> Self {
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
let (i, b) = if mode.is_horizontal() {
(&physical_point.x, &physical_point.y)
} else {
(&physical_point.y, &physical_point.x)
};
LogicalVec2 {
inline: i.clone(),
block: b.clone(),
}
}
pub fn map<U>(&self, f: impl Fn(&T) -> U) -> LogicalVec2<U> {
LogicalVec2 {
inline: f(&self.inline),
block: f(&self.block),
}
}
}
impl<T: Add<Output = T> + Copy> Add<LogicalVec2<T>> for LogicalVec2<T> {
type Output = LogicalVec2<T>;
fn add(self, other: Self) -> Self::Output {
LogicalVec2 {
inline: self.inline + other.inline,
block: self.block + other.block,
}
}
}
impl<T: Sub<Output = T> + Copy> Sub<LogicalVec2<T>> for LogicalVec2<T> {
type Output = LogicalVec2<T>;
fn sub(self, other: Self) -> Self::Output {
LogicalVec2 {
inline: self.inline - other.inline,
block: self.block - other.block,
}
}
}
impl<T: AddAssign<T> + Copy> AddAssign<LogicalVec2<T>> for LogicalVec2<T> {
fn add_assign(&mut self, other: LogicalVec2<T>) {
self.inline += other.inline;
self.block += other.block;
}
}
impl<T: SubAssign<T> + Copy> SubAssign<LogicalVec2<T>> for LogicalVec2<T> {
fn sub_assign(&mut self, other: LogicalVec2<T>) {
self.inline -= other.inline;
self.block -= other.block;
}
}
impl<T: Neg<Output = T> + Copy> Neg for LogicalVec2<T> {
type Output = LogicalVec2<T>;
fn neg(self) -> Self::Output {
Self {
inline: -self.inline,
block: -self.block,
}
}
}
impl<T: Zero> LogicalVec2<T> {
pub fn zero() -> Self {
Self {
inline: T::zero(),
block: T::zero(),
}
}
}
impl<T: Clone> LogicalVec2<AutoOr<T>> {
pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalVec2<T> {
self.map(|t| t.auto_is(&f))
}
}
impl LogicalVec2<LengthPercentageOrAuto<'_>> {
pub fn percentages_relative_to(
&self,
containing_block: &ContainingBlock,
) -> LogicalVec2<LengthOrAuto> {
LogicalVec2 {
inline: self
.inline
.percentage_relative_to(containing_block.inline_size.into()),
block: self.block.maybe_percentage_relative_to(
containing_block.block_size.map(|t| t.into()).non_auto(),
),
}
}
}
impl LogicalVec2<Option<&'_ LengthPercentage>> {
pub fn percentages_relative_to(
&self,
containing_block: &ContainingBlock,
) -> LogicalVec2<Option<Length>> {
LogicalVec2 {
inline: self
.inline
.map(|lp| lp.percentage_relative_to(containing_block.inline_size.into())),
block: self.block.and_then(|lp| {
lp.maybe_percentage_relative_to(
containing_block.block_size.map(|t| t.into()).non_auto(),
)
}),
}
}
}
impl<T: Zero> LogicalRect<T> {
pub fn zero() -> Self {
Self {
start_corner: LogicalVec2::zero(),
size: LogicalVec2::zero(),
}
}
}
impl fmt::Debug for LogicalRect<Au> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Rect(i{}×b{} @ (i{},b{}))",
self.size.inline.to_px(),
self.size.block.to_px(),
self.start_corner.inline.to_px(),
self.start_corner.block.to_px(),
)
}
}
impl<T: Clone> LogicalVec2<T> {
pub fn to_physical_size(&self, mode: WritingMode) -> PhysicalSize<T> {
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
let (x, y) = if mode.is_horizontal() {
(&self.inline, &self.block)
} else {
(&self.block, &self.inline)
};
PhysicalSize::new(x.clone(), y.clone())
}
pub fn to_physical_point(&self, mode: WritingMode) -> PhysicalPoint<T> {
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
let (x, y) = if mode.is_horizontal() {
(&self.inline, &self.block)
} else {
(&self.block, &self.inline)
};
PhysicalPoint::new(x.clone(), y.clone())
}
}
impl<T: Clone> LogicalSides<T> {
pub fn from_physical(sides: &PhysicalSides<T>, mode: WritingMode) -> Self {
// https://drafts.csswg.org/css-writing-modes/#logical-to-physical
let block_flow = mode.block_flow_direction();
let (bs, be) = match mode.block_flow_direction() {
BlockFlowDirection::TopToBottom => (&sides.top, &sides.bottom),
BlockFlowDirection::RightToLeft => (&sides.right, &sides.left),
BlockFlowDirection::LeftToRight => (&sides.left, &sides.right),
};
use BlockFlowDirection::TopToBottom;
let (is, ie) = match (block_flow, mode.inline_base_direction()) {
(TopToBottom, InlineBaseDirection::LeftToRight) => (&sides.left, &sides.right),
(TopToBottom, InlineBaseDirection::RightToLeft) => (&sides.right, &sides.left),
(_, InlineBaseDirection::LeftToRight) => (&sides.top, &sides.bottom),
(_, InlineBaseDirection::RightToLeft) => (&sides.bottom, &sides.top),
};
LogicalSides {
inline_start: is.clone(),
inline_end: ie.clone(),
block_start: bs.clone(),
block_end: be.clone(),
}
}
}
impl<T> LogicalSides<T> {
pub fn map<U>(&self, f: impl Fn(&T) -> U) -> LogicalSides<U> {
LogicalSides {
inline_start: f(&self.inline_start),
inline_end: f(&self.inline_end),
block_start: f(&self.block_start),
block_end: f(&self.block_end),
}
}
pub fn map_inline_and_block_axes<U>(
&self,
inline_f: impl Fn(&T) -> U,
block_f: impl Fn(&T) -> U,
) -> LogicalSides<U> {
LogicalSides {
inline_start: inline_f(&self.inline_start),
inline_end: inline_f(&self.inline_end),
block_start: block_f(&self.block_start),
block_end: block_f(&self.block_end),
}
}
pub fn inline_sum(&self) -> T::Output
where
T: Add + Copy,
{
self.inline_start + self.inline_end
}
pub fn block_sum(&self) -> T::Output
where
T: Add + Copy,
{
self.block_start + self.block_end
}
pub fn sum(&self) -> LogicalVec2<T::Output>
where
T: Add + Copy,
{
LogicalVec2 {
inline: self.inline_sum(),
block: self.block_sum(),
}
}
pub fn to_physical(&self, mode: WritingMode) -> PhysicalSides<T>
where
T: Clone,
{
let top;
let right;
let bottom;
let left;
if mode.is_vertical() {
if mode.is_vertical_lr() {
left = self.block_start.clone();
right = self.block_end.clone();
} else {
right = self.block_start.clone();
left = self.block_end.clone();
}
if mode.is_inline_tb() {
top = self.inline_start.clone();
bottom = self.inline_end.clone();
} else {
bottom = self.inline_start.clone();
top = self.inline_end.clone();
}
} else {
top = self.block_start.clone();
bottom = self.block_end.clone();
if mode.is_bidi_ltr() {
left = self.inline_start.clone();
right = self.inline_end.clone();
} else {
right = self.inline_start.clone();
left = self.inline_end.clone();
}
}
PhysicalSides::new(top, right, bottom, left)
}
}
impl<T: Copy> LogicalSides<T> {
pub fn start_offset(&self) -> LogicalVec2<T> {
LogicalVec2 {
inline: self.inline_start,
block: self.block_start,
}
}
}
impl LogicalSides<&'_ LengthPercentage> {
pub fn percentages_relative_to(&self, basis: Length) -> LogicalSides<Length> {
self.map(|s| s.percentage_relative_to(basis))
}
}
impl LogicalSides<LengthPercentageOrAuto<'_>> {
pub fn percentages_relative_to(&self, basis: Length) -> LogicalSides<LengthOrAuto> {
self.map(|s| s.percentage_relative_to(basis))
}
}
impl<T: Clone> LogicalSides<AutoOr<T>> {
pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalSides<T> {
self.map(|s| s.auto_is(&f))
}
}
impl<T: Add<Output = T> + Copy> Add<LogicalSides<T>> for LogicalSides<T> {
type Output = LogicalSides<T>;
fn add(self, other: Self) -> Self::Output {
LogicalSides {
inline_start: self.inline_start + other.inline_start,
inline_end: self.inline_end + other.inline_end,
block_start: self.block_start + other.block_start,
block_end: self.block_end + other.block_end,
}
}
}
impl<T: Sub<Output = T> + Copy> Sub<LogicalSides<T>> for LogicalSides<T> {
type Output = LogicalSides<T>;
fn sub(self, other: Self) -> Self::Output {
LogicalSides {
inline_start: self.inline_start - other.inline_start,
inline_end: self.inline_end - other.inline_end,
block_start: self.block_start - other.block_start,
block_end: self.block_end - other.block_end,
}
}
}
impl<T: Neg<Output = T> + Copy> Neg for LogicalSides<T> {
type Output = LogicalSides<T>;
fn neg(self) -> Self::Output {
Self {
inline_start: -self.inline_start,
inline_end: -self.inline_end,
block_start: -self.block_start,
block_end: -self.block_end,
}
}
}
impl<T: Zero> LogicalSides<T> {
pub(crate) fn zero() -> LogicalSides<T> {
Self {
inline_start: T::zero(),
inline_end: T::zero(),
block_start: T::zero(),
block_end: T::zero(),
}
}
}
impl From<LogicalSides<CSSPixelLength>> for LogicalSides<Au> {
fn from(value: LogicalSides<CSSPixelLength>) -> Self {
Self {
inline_start: value.inline_start.into(),
inline_end: value.inline_end.into(),
block_start: value.block_start.into(),
block_end: value.block_end.into(),
}
}
}
impl From<LogicalSides<Au>> for LogicalSides<CSSPixelLength> {
fn from(value: LogicalSides<Au>) -> Self {
Self {
inline_start: value.inline_start.into(),
inline_end: value.inline_end.into(),
block_start: value.block_start.into(),
block_end: value.block_end.into(),
}
}
}
impl<T> LogicalRect<T> {
pub fn max_inline_position(&self) -> T
where
T: Add<Output = T> + Copy,
{
self.start_corner.inline + self.size.inline
}
pub fn max_block_position(&self) -> T
where
T: Add<Output = T> + Copy,
{
self.start_corner.block + self.size.block
}
pub fn inflate(&self, sides: &LogicalSides<T>) -> Self
where
T: Add<Output = T> + Copy,
T: Sub<Output = T> + Copy,
{
Self {
start_corner: LogicalVec2 {
inline: self.start_corner.inline - sides.inline_start,
block: self.start_corner.block - sides.block_start,
},
size: LogicalVec2 {
inline: self.size.inline + sides.inline_sum(),
block: self.size.block + sides.block_sum(),
},
}
}
pub fn deflate(&self, sides: &LogicalSides<T>) -> Self
where
T: Add<Output = T> + Copy,
T: Sub<Output = T> + Copy,
{
LogicalRect {
start_corner: LogicalVec2 {
inline: self.start_corner.inline + sides.inline_start,
block: self.start_corner.block + sides.block_start,
},
size: LogicalVec2 {
inline: self.size.inline - sides.inline_sum(),
block: self.size.block - sides.block_sum(),
},
}
}
pub fn to_physical(&self, mode: WritingMode) -> PhysicalRect<T>
where
T: Copy,
{
PhysicalRect::new(
self.start_corner.to_physical_point(mode),
self.size.to_physical_size(mode),
)
}
}
impl From<LogicalVec2<CSSPixelLength>> for LogicalVec2<Au> {
fn from(value: LogicalVec2<CSSPixelLength>) -> Self {
LogicalVec2 {
inline: value.inline.into(),
block: value.block.into(),
}
}
}
impl From<LogicalVec2<Au>> for LogicalVec2<CSSPixelLength> {
fn from(value: LogicalVec2<Au>) -> Self {
LogicalVec2 {
inline: value.inline.into(),
block: value.block.into(),
}
}
}
impl From<LogicalRect<Au>> for LogicalRect<CSSPixelLength> {
fn from(value: LogicalRect<Au>) -> Self {
LogicalRect {
start_corner: value.start_corner.into(),
size: value.size.into(),
}
}
}
impl From<LogicalRect<CSSPixelLength>> for LogicalRect<Au> {
fn from(value: LogicalRect<CSSPixelLength>) -> Self {
LogicalRect {
start_corner: value.start_corner.into(),
size: value.size.into(),
}
}
}
pub(crate) trait ToLogical<Unit, LogicalType> {
fn to_logical(&self, writing_mode: WritingMode) -> LogicalType;
}
impl<Unit: Copy> ToLogical<Unit, LogicalRect<Unit>> for PhysicalRect<Unit> {
fn to_logical(&self, writing_mode: WritingMode) -> LogicalRect<Unit> {
LogicalRect {
start_corner: LogicalVec2::from_physical_size(
&PhysicalSize::new(self.origin.x, self.origin.y),
writing_mode,
),
size: LogicalVec2::from_physical_size(&self.size, writing_mode),
}
}
}
impl<Unit: Copy> ToLogical<Unit, LogicalVec2<Unit>> for PhysicalSize<Unit> {
fn to_logical(&self, writing_mode: WritingMode) -> LogicalVec2<Unit> {
LogicalVec2::from_physical_size(self, writing_mode)
}
}
impl<Unit: Copy> ToLogical<Unit, LogicalVec2<Unit>> for PhysicalPoint<Unit> {
fn to_logical(&self, writing_mode: WritingMode) -> LogicalVec2<Unit> {
LogicalVec2::from_physical_point(self, writing_mode)
}
}
impl<Unit: Copy> ToLogical<Unit, LogicalSides<Unit>> for PhysicalSides<Unit> {
fn to_logical(&self, writing_mode: WritingMode) -> LogicalSides<Unit> {
LogicalSides::from_physical(self, writing_mode)
}
}