mirror of
https://github.com/servo/servo.git
synced 2025-06-15 11:54:28 +00:00
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>
272 lines
9.3 KiB
Rust
272 lines
9.3 KiB
Rust
/* 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/. */
|
|
|
|
//! <https://drafts.csswg.org/css-flexbox/#box-model>
|
|
|
|
use serde::Serialize;
|
|
use style::properties::longhands::flex_direction::computed_value::T as FlexDirection;
|
|
|
|
use crate::geom::{LogicalRect, LogicalSides, LogicalVec2};
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
pub(super) struct FlexRelativeVec2<T> {
|
|
pub main: T,
|
|
pub cross: T,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(super) struct FlexRelativeSides<T> {
|
|
pub cross_start: T,
|
|
pub main_start: T,
|
|
pub cross_end: T,
|
|
pub main_end: T,
|
|
}
|
|
|
|
pub(super) struct FlexRelativeRect<T> {
|
|
pub start_corner: FlexRelativeVec2<T>,
|
|
pub size: FlexRelativeVec2<T>,
|
|
}
|
|
|
|
impl<T> std::ops::Add for FlexRelativeVec2<T>
|
|
where
|
|
T: std::ops::Add,
|
|
{
|
|
type Output = FlexRelativeVec2<T::Output>;
|
|
fn add(self, rhs: Self) -> Self::Output {
|
|
FlexRelativeVec2 {
|
|
main: self.main + rhs.main,
|
|
cross: self.cross + rhs.cross,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> std::ops::Sub for FlexRelativeVec2<T>
|
|
where
|
|
T: std::ops::Sub,
|
|
{
|
|
type Output = FlexRelativeVec2<T::Output>;
|
|
fn sub(self, rhs: Self) -> Self::Output {
|
|
FlexRelativeVec2 {
|
|
main: self.main - rhs.main,
|
|
cross: self.cross - rhs.cross,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> FlexRelativeSides<T> {
|
|
pub fn sum_by_axis(self) -> FlexRelativeVec2<T::Output>
|
|
where
|
|
T: std::ops::Add,
|
|
{
|
|
FlexRelativeVec2 {
|
|
main: self.main_start + self.main_end,
|
|
cross: self.cross_start + self.cross_end,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One of the two bits set by the `flex-direction` property
|
|
/// (The other is "forward" v.s. reverse.)
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
|
pub(super) enum FlexAxis {
|
|
/// The main axis is the inline axis of the container (not necessarily of flex items!),
|
|
/// cross is block.
|
|
Row,
|
|
/// The main axis is the block axis, cross is inline.
|
|
Column,
|
|
}
|
|
|
|
/// Which flow-relative sides map to the main-start and cross-start sides, respectively.
|
|
/// See <https://drafts.csswg.org/css-flexbox/#box-model>
|
|
#[derive(Clone, Copy, Debug, Serialize)]
|
|
pub(super) enum MainStartCrossStart {
|
|
InlineStartBlockStart,
|
|
InlineStartBlockEnd,
|
|
BlockStartInlineStart,
|
|
BlockStartInlineEnd,
|
|
InlineEndBlockStart,
|
|
InlineEndBlockEnd,
|
|
BlockEndInlineStart,
|
|
BlockEndInlineEnd,
|
|
}
|
|
|
|
impl FlexAxis {
|
|
pub fn from(flex_direction: FlexDirection) -> Self {
|
|
match flex_direction {
|
|
FlexDirection::Row | FlexDirection::RowReverse => FlexAxis::Row,
|
|
FlexDirection::Column | FlexDirection::ColumnReverse => FlexAxis::Column,
|
|
}
|
|
}
|
|
|
|
pub fn vec2_to_flex_relative<T>(self, flow_relative: LogicalVec2<T>) -> FlexRelativeVec2<T> {
|
|
let LogicalVec2 { inline, block } = flow_relative;
|
|
match self {
|
|
FlexAxis::Row => FlexRelativeVec2 {
|
|
main: inline,
|
|
cross: block,
|
|
},
|
|
FlexAxis::Column => FlexRelativeVec2 {
|
|
main: block,
|
|
cross: inline,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn vec2_to_flow_relative<T>(self, flex_relative: FlexRelativeVec2<T>) -> LogicalVec2<T> {
|
|
let FlexRelativeVec2 { main, cross } = flex_relative;
|
|
match self {
|
|
FlexAxis::Row => LogicalVec2 {
|
|
inline: main,
|
|
block: cross,
|
|
},
|
|
FlexAxis::Column => LogicalVec2 {
|
|
block: main,
|
|
inline: cross,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! sides_mapping_methods {
|
|
(
|
|
$(
|
|
$variant: path => {
|
|
$( $flex_relative_side: ident <=> $flow_relative_side: ident, )+
|
|
},
|
|
)+
|
|
) => {
|
|
pub fn sides_to_flex_relative<T>(self, flow_relative: LogicalSides<T>) -> FlexRelativeSides<T> {
|
|
match self {
|
|
$(
|
|
$variant => FlexRelativeSides {
|
|
$( $flex_relative_side: flow_relative.$flow_relative_side, )+
|
|
},
|
|
)+
|
|
}
|
|
}
|
|
|
|
pub fn sides_to_flow_relative<T>(self, flex_relative: FlexRelativeSides<T>) -> LogicalSides<T> {
|
|
match self {
|
|
$(
|
|
$variant => LogicalSides {
|
|
$( $flow_relative_side: flex_relative.$flex_relative_side, )+
|
|
},
|
|
)+
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MainStartCrossStart {
|
|
pub fn from(flex_direction: FlexDirection, flex_wrap_reverse: bool) -> Self {
|
|
match (flex_direction, flex_wrap_reverse) {
|
|
// See definition of each keyword in
|
|
// https://drafts.csswg.org/css-flexbox/#flex-direction-property and
|
|
// https://drafts.csswg.org/css-flexbox/#flex-wrap-property,
|
|
// or the tables (though they map to physical rather than flow-relative) at
|
|
// https://drafts.csswg.org/css-flexbox/#axis-mapping
|
|
(FlexDirection::Row, true) => MainStartCrossStart::InlineStartBlockEnd,
|
|
(FlexDirection::Row, false) => MainStartCrossStart::InlineStartBlockStart,
|
|
(FlexDirection::Column, true) => MainStartCrossStart::BlockStartInlineEnd,
|
|
(FlexDirection::Column, false) => MainStartCrossStart::BlockStartInlineStart,
|
|
(FlexDirection::RowReverse, true) => MainStartCrossStart::InlineEndBlockEnd,
|
|
(FlexDirection::RowReverse, false) => MainStartCrossStart::InlineEndBlockStart,
|
|
(FlexDirection::ColumnReverse, true) => MainStartCrossStart::BlockEndInlineEnd,
|
|
(FlexDirection::ColumnReverse, false) => MainStartCrossStart::BlockEndInlineStart,
|
|
}
|
|
}
|
|
|
|
sides_mapping_methods! {
|
|
MainStartCrossStart::InlineStartBlockStart => {
|
|
main_start <=> inline_start,
|
|
cross_start <=> block_start,
|
|
main_end <=> inline_end,
|
|
cross_end <=> block_end,
|
|
},
|
|
MainStartCrossStart::InlineStartBlockEnd => {
|
|
main_start <=> inline_start,
|
|
cross_start <=> block_end,
|
|
main_end <=> inline_end,
|
|
cross_end <=> block_start,
|
|
},
|
|
MainStartCrossStart::BlockStartInlineStart => {
|
|
main_start <=> block_start,
|
|
cross_start <=> inline_start,
|
|
main_end <=> block_end,
|
|
cross_end <=> inline_end,
|
|
},
|
|
MainStartCrossStart::BlockStartInlineEnd => {
|
|
main_start <=> block_start,
|
|
cross_start <=> inline_end,
|
|
main_end <=> block_end,
|
|
cross_end <=> inline_start,
|
|
},
|
|
MainStartCrossStart::InlineEndBlockStart => {
|
|
main_start <=> inline_end,
|
|
cross_start <=> block_start,
|
|
main_end <=> inline_start,
|
|
cross_end <=> block_end,
|
|
},
|
|
MainStartCrossStart::InlineEndBlockEnd => {
|
|
main_start <=> inline_end,
|
|
cross_start <=> block_end,
|
|
main_end <=> inline_start,
|
|
cross_end <=> block_start,
|
|
},
|
|
MainStartCrossStart::BlockEndInlineStart => {
|
|
main_start <=> block_end,
|
|
cross_start <=> inline_start,
|
|
main_end <=> block_start,
|
|
cross_end <=> inline_end,
|
|
},
|
|
MainStartCrossStart::BlockEndInlineEnd => {
|
|
main_start <=> block_end,
|
|
cross_start <=> inline_end,
|
|
main_end <=> block_start,
|
|
cross_end <=> inline_start,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// The start corner coordinates in both the input rectangle and output rectangle
|
|
/// are relative to some “base rectangle” whose size is passed here.
|
|
pub(super) fn rect_to_flow_relative<T>(
|
|
flex_axis: FlexAxis,
|
|
main_start_cross_start_sides_are: MainStartCrossStart,
|
|
base_rect_size: FlexRelativeVec2<T>,
|
|
rect: FlexRelativeRect<T>,
|
|
) -> LogicalRect<T>
|
|
where
|
|
T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T>,
|
|
{
|
|
// First, convert from (start corner, size) to offsets from the edges of the base rectangle
|
|
|
|
let end_corner_position = rect.start_corner + rect.size;
|
|
let end_corner_offsets = base_rect_size - end_corner_position;
|
|
// No-ops, but hopefully clarifies to human readers:
|
|
let start_corner_position = rect.start_corner;
|
|
let start_corner_offsets = start_corner_position;
|
|
|
|
// Then, convert to flow-relative using methods above
|
|
let flow_relative_offsets =
|
|
main_start_cross_start_sides_are.sides_to_flow_relative(FlexRelativeSides {
|
|
main_start: start_corner_offsets.main,
|
|
cross_start: start_corner_offsets.cross,
|
|
main_end: end_corner_offsets.main,
|
|
cross_end: end_corner_offsets.cross,
|
|
});
|
|
let flow_relative_base_rect_size = flex_axis.vec2_to_flow_relative(base_rect_size);
|
|
|
|
// Finally, convert back to (start corner, size)
|
|
let start_corner = LogicalVec2 {
|
|
inline: flow_relative_offsets.inline_start,
|
|
block: flow_relative_offsets.block_start,
|
|
};
|
|
let end_corner_position = LogicalVec2 {
|
|
inline: flow_relative_base_rect_size.inline - flow_relative_offsets.inline_end,
|
|
block: flow_relative_base_rect_size.block - flow_relative_offsets.block_end,
|
|
};
|
|
let size = end_corner_position - start_corner;
|
|
LogicalRect { start_corner, size }
|
|
}
|