mirror of
https://github.com/servo/servo.git
synced 2025-08-04 21:20:23 +01:00
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>
This commit is contained in:
parent
65bd5a3b99
commit
56280c6242
189 changed files with 547 additions and 762 deletions
|
@ -8,6 +8,7 @@ use std::char::{ToLowercase, ToUppercase};
|
|||
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
|
||||
use style::values::computed::TextDecorationLine;
|
||||
use style::values::specified::text::TextTransformCase;
|
||||
use unicode_bidi::Level;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use super::text_run::TextRun;
|
||||
|
@ -19,6 +20,7 @@ use crate::dom_traversal::NodeAndStyleInfo;
|
|||
use crate::flow::float::FloatBox;
|
||||
use crate::formatting_contexts::IndependentFormattingContext;
|
||||
use crate::positioned::AbsolutelyPositionedBox;
|
||||
use crate::style_ext::ComputedValuesExt;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct InlineFormattingContextBuilder {
|
||||
|
@ -82,6 +84,11 @@ impl InlineFormattingContextBuilder {
|
|||
!self.inline_box_stack.is_empty()
|
||||
}
|
||||
|
||||
fn push_control_character_string(&mut self, string_to_push: &str) {
|
||||
self.text_segments.push(string_to_push.to_owned());
|
||||
self.current_text_offset += string_to_push.len();
|
||||
}
|
||||
|
||||
/// Return true if this [`InlineFormattingContextBuilder`] is empty for the purposes of ignoring
|
||||
/// during box tree construction. An IFC is empty if it only contains TextRuns with
|
||||
/// completely collapsible whitespace. When that happens it can be ignored completely.
|
||||
|
@ -101,7 +108,7 @@ impl InlineFormattingContextBuilder {
|
|||
// Text content is handled by `self.has_uncollapsible_text` content above in order
|
||||
// to avoid having to iterate through the character once again.
|
||||
InlineItem::TextRun(_) => true,
|
||||
InlineItem::OutOfFlowAbsolutelyPositionedBox(_) => false,
|
||||
InlineItem::OutOfFlowAbsolutelyPositionedBox(..) => false,
|
||||
InlineItem::OutOfFlowFloatBox(_) => false,
|
||||
InlineItem::Atomic(..) => false,
|
||||
}
|
||||
|
@ -119,14 +126,13 @@ impl InlineFormattingContextBuilder {
|
|||
let inline_level_box = ArcRefCell::new(InlineItem::Atomic(
|
||||
independent_formatting_context,
|
||||
self.current_text_offset,
|
||||
Level::ltr(), /* This will be assigned later if necessary. */
|
||||
));
|
||||
self.inline_items.push(inline_level_box.clone());
|
||||
|
||||
// Push an object replacement character for this atomic, which will ensure that the line breaker
|
||||
// inserts a line breaking opportunity here.
|
||||
let string_to_push = "\u{fffc}";
|
||||
self.text_segments.push(string_to_push.to_owned());
|
||||
self.current_text_offset += string_to_push.len();
|
||||
self.push_control_character_string("\u{fffc}");
|
||||
|
||||
self.last_inline_box_ended_with_collapsible_white_space = false;
|
||||
self.on_word_boundary = true;
|
||||
|
@ -141,7 +147,9 @@ impl InlineFormattingContextBuilder {
|
|||
let absolutely_positioned_box = ArcRefCell::new(absolutely_positioned_box);
|
||||
let inline_level_box = ArcRefCell::new(InlineItem::OutOfFlowAbsolutelyPositionedBox(
|
||||
absolutely_positioned_box,
|
||||
self.current_text_offset,
|
||||
));
|
||||
|
||||
self.inline_items.push(inline_level_box.clone());
|
||||
inline_level_box
|
||||
}
|
||||
|
@ -154,6 +162,8 @@ impl InlineFormattingContextBuilder {
|
|||
}
|
||||
|
||||
pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) {
|
||||
self.push_control_character_string(inline_box.style.bidi_control_chars().0);
|
||||
|
||||
let identifier = self.inline_boxes.start_inline_box(inline_box);
|
||||
self.inline_items
|
||||
.push(ArcRefCell::new(InlineItem::StartInlineBox(identifier)));
|
||||
|
@ -164,6 +174,9 @@ impl InlineFormattingContextBuilder {
|
|||
let identifier = self.end_inline_box_internal();
|
||||
let inline_level_box = self.inline_boxes.get(&identifier);
|
||||
inline_level_box.borrow_mut().is_last_fragment = true;
|
||||
|
||||
self.push_control_character_string(inline_level_box.borrow().style.bidi_control_chars().1);
|
||||
|
||||
inline_level_box
|
||||
}
|
||||
|
||||
|
@ -261,6 +274,7 @@ impl InlineFormattingContextBuilder {
|
|||
layout_context: &LayoutContext,
|
||||
text_decoration_line: TextDecorationLine,
|
||||
has_first_formatted_line: bool,
|
||||
default_bidi_level: Level,
|
||||
) -> Option<InlineFormattingContext> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
|
@ -293,6 +307,7 @@ impl InlineFormattingContextBuilder {
|
|||
text_decoration_line,
|
||||
has_first_formatted_line,
|
||||
/* is_single_line_text_input = */ false,
|
||||
default_bidi_level,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -303,6 +318,7 @@ impl InlineFormattingContextBuilder {
|
|||
text_decoration_line: TextDecorationLine,
|
||||
has_first_formatted_line: bool,
|
||||
is_single_line_text_input: bool,
|
||||
default_bidi_level: Level,
|
||||
) -> Option<InlineFormattingContext> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
|
@ -317,6 +333,7 @@ impl InlineFormattingContextBuilder {
|
|||
text_decoration_line,
|
||||
has_first_formatted_line,
|
||||
is_single_line_text_input,
|
||||
default_bidi_level,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue