diff --git a/components/gfx/font.rs b/components/gfx/font.rs index fa60ba65abb..22261f1fc29 100644 --- a/components/gfx/font.rs +++ b/components/gfx/font.rs @@ -106,7 +106,9 @@ bitflags! { #[doc="Set if we are to ignore ligatures."] const IGNORE_LIGATURES_SHAPING_FLAG = 0x02, #[doc="Set if we are to disable kerning."] - const DISABLE_KERNING_SHAPING_FLAG = 0x04 + const DISABLE_KERNING_SHAPING_FLAG = 0x04, + #[doc="Text direction is right-to-left."] + const RTL_FLAG = 0x08, } } diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs index 452af8394ee..361d2af2047 100644 --- a/components/gfx/paint_context.rs +++ b/components/gfx/paint_context.rs @@ -1294,7 +1294,7 @@ impl ScaledFontExtensionMethods for ScaledFont { let mut azglyphs = vec!(); azglyphs.reserve(range.length().to_usize()); - for slice in run.natural_word_slices_in_range(range) { + for slice in run.natural_word_slices_in_visual_order(range) { for (_i, glyph) in slice.glyphs.iter_glyphs_for_char_range(&slice.range) { let glyph_advance = glyph.advance(); let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs index edf3d4a031e..74a3b4e6d25 100644 --- a/components/gfx/text/shaping/harfbuzz.rs +++ b/components/gfx/text/shaping/harfbuzz.rs @@ -5,14 +5,14 @@ extern crate harfbuzz; use font::{DISABLE_KERNING_SHAPING_FLAG, Font, FontHandleMethods, FontTableMethods, FontTableTag}; -use font::{IGNORE_LIGATURES_SHAPING_FLAG, ShapingOptions}; +use font::{IGNORE_LIGATURES_SHAPING_FLAG, RTL_FLAG, ShapingOptions}; use platform::font::FontTable; use text::glyph::{CharIndex, GlyphStore, GlyphId, GlyphData}; use text::shaping::ShaperMethods; use text::util::{float_to_fixed, fixed_to_float}; use euclid::Point2D; -use harfbuzz::{HB_MEMORY_MODE_READONLY, HB_DIRECTION_LTR}; +use harfbuzz::{HB_MEMORY_MODE_READONLY, HB_DIRECTION_LTR, HB_DIRECTION_RTL}; use harfbuzz::{RUST_hb_blob_create, RUST_hb_face_create_for_tables}; use harfbuzz::{hb_blob_t}; use harfbuzz::{hb_bool_t}; @@ -229,7 +229,11 @@ impl ShaperMethods for Shaper { fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) { unsafe { let hb_buffer: *mut hb_buffer_t = RUST_hb_buffer_create(); - RUST_hb_buffer_set_direction(hb_buffer, HB_DIRECTION_LTR); + RUST_hb_buffer_set_direction(hb_buffer, if options.flags.contains(RTL_FLAG) { + HB_DIRECTION_RTL + } else { + HB_DIRECTION_LTR + }); RUST_hb_buffer_add_utf8(hb_buffer, text.as_ptr() as *const c_char, diff --git a/components/gfx/text/text_run.rs b/components/gfx/text/text_run.rs index 4389cbc5a1c..dbdb913f25e 100644 --- a/components/gfx/text/text_run.rs +++ b/components/gfx/text/text_run.rs @@ -5,13 +5,15 @@ use font::{Font, FontHandleMethods, FontMetrics, IS_WHITESPACE_SHAPING_FLAG, RunMetrics}; use font::{ShapingOptions}; use platform::font_template::FontTemplateData; +use text::glyph::{CharIndex, GlyphStore}; + use util::geometry::Au; use util::range::Range; use util::vec::{Comparator, FullBinarySearchMethods}; + use std::cmp::{Ordering, max}; use std::slice::Iter; use std::sync::Arc; -use text::glyph::{CharIndex, GlyphStore}; /// A single "paragraph" of text in one font size and style. #[derive(Clone, Deserialize, Serialize)] @@ -23,6 +25,7 @@ pub struct TextRun { pub font_metrics: FontMetrics, /// The glyph runs that make up this text run. pub glyphs: Arc>, + pub bidi_level: u8, } /// A single series of glyphs within a text run. @@ -35,8 +38,10 @@ pub struct GlyphRun { } pub struct NaturalWordSliceIterator<'a> { - glyph_iter: Iter<'a, GlyphRun>, + glyphs: &'a [GlyphRun], + index: usize, range: Range, + reverse: bool, } struct CharIndexComparator; @@ -80,11 +85,20 @@ impl<'a> Iterator for NaturalWordSliceIterator<'a> { // inline(always) due to the inefficient rt failures messing up inline heuristics, I think. #[inline(always)] fn next(&mut self) -> Option> { - let slice_glyphs = self.glyph_iter.next(); - if slice_glyphs.is_none() { - return None; + let slice_glyphs; + if self.reverse { + if self.index == 0 { + return None; + } + self.index -= 1; + slice_glyphs = &self.glyphs[self.index]; + } else { + if self.index >= self.glyphs.len() { + return None; + } + slice_glyphs = &self.glyphs[self.index]; + self.index += 1; } - let slice_glyphs = slice_glyphs.unwrap(); let mut char_range = self.range.intersect(&slice_glyphs.range); let slice_range_begin = slice_glyphs.range.begin(); @@ -140,7 +154,7 @@ impl<'a> Iterator for CharacterSliceIterator<'a> { } impl<'a> TextRun { - pub fn new(font: &mut Font, text: String, options: &ShapingOptions) -> TextRun { + pub fn new(font: &mut Font, text: String, options: &ShapingOptions, bidi_level: u8) -> TextRun { let glyphs = TextRun::break_and_shape(font, &text, options); let run = TextRun { text: Arc::new(text), @@ -148,6 +162,7 @@ impl<'a> TextRun { font_template: font.handle.template(), actual_pt_size: font.actual_pt_size, glyphs: Arc::new(glyphs), + bidi_level: bidi_level, }; return run; } @@ -279,8 +294,36 @@ impl<'a> TextRun { Some(index) => index, }; NaturalWordSliceIterator { - glyph_iter: self.glyphs[index..].iter(), + glyphs: &self.glyphs[..], + index: index, range: *range, + reverse: false, + } + } + + /// Returns an iterator that over natural word slices in visual order (left to right or + /// right to left, depending on the bidirectional embedding level). + pub fn natural_word_slices_in_visual_order(&'a self, range: &Range) + -> NaturalWordSliceIterator<'a> { + // Iterate in reverse order if bidi level is RTL. + let reverse = self.bidi_level % 2 == 1; + + let index = if reverse { + match self.index_of_first_glyph_run_containing(range.end() - CharIndex(1)) { + Some(i) => i + 1, // In reverse mode, index points one past the next element. + None => 0 + } + } else { + match self.index_of_first_glyph_run_containing(range.begin()) { + Some(i) => i, + None => self.glyphs.len() + } + }; + NaturalWordSliceIterator { + glyphs: &self.glyphs[..], + index: index, + range: *range, + reverse: reverse, } } diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index 47bb810c7bc..8ca55095ac8 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -73,4 +73,4 @@ string_cache_plugin = "0.1" euclid = "0.1" serde = "0.4" serde_macros = "0.4" - +unicode-bidi = "0.2" diff --git a/components/layout/inline.rs b/components/layout/inline.rs index b16d075a738..e616ba02f9c 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -28,10 +28,11 @@ use std::collections::VecDeque; use std::fmt; use std::mem; use std::sync::Arc; -use std::u16; +use std::isize; use style::computed_values::{display, overflow_x, position, text_align, text_justify}; use style::computed_values::{text_overflow, vertical_align, white_space}; use style::properties::ComputedValues; +use unicode_bidi; use util::geometry::{Au, MAX_AU, ZERO_RECT}; use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; use util::range::{Range, RangeIndex}; @@ -66,7 +67,7 @@ static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34; /// with a float or a horizontal wall of the containing block. The block-start /// inline-start corner of the green zone is the same as that of the line, but /// the green zone can be taller and wider than the line itself. -#[derive(RustcEncodable, Debug, Copy, Clone)] +#[derive(RustcEncodable, Debug, Clone)] pub struct Line { /// A range of line indices that describe line breaks. /// @@ -95,6 +96,11 @@ pub struct Line { /// | 'I like' | 'truffles,' | ' yes' | 'I do.' | pub range: Range, + /// The bidirectional embedding level runs for this line, in visual order. + /// + /// Can be set to `None` if the line is 100% left-to-right. + pub visual_runs: Option, u8)>>, + /// The bounds are the exact position and extents of the line with respect /// to the parent box. /// @@ -153,6 +159,23 @@ pub struct Line { pub inline_metrics: InlineMetrics, } +impl Line { + fn new(writing_mode: WritingMode, + minimum_block_size_above_baseline: Au, + minimum_depth_below_baseline: Au) + -> Line { + Line { + range: Range::empty(), + visual_runs: None, + bounds: LogicalRect::zero(writing_mode), + green_zone: LogicalSize::zero(writing_mode), + inline_metrics: InlineMetrics::new(minimum_block_size_above_baseline, + minimum_depth_below_baseline, + minimum_block_size_above_baseline), + } + } +} + int_range_index! { #[derive(RustcEncodable)] #[doc = "The index of a fragment in a flattened vector of DOM elements."] @@ -202,14 +225,9 @@ impl LineBreaker { LineBreaker { new_fragments: Vec::new(), work_list: VecDeque::new(), - pending_line: Line { - range: Range::empty(), - bounds: LogicalRect::zero(float_context.writing_mode), - green_zone: LogicalSize::zero(float_context.writing_mode), - inline_metrics: InlineMetrics::new(minimum_block_size_above_baseline, - minimum_depth_below_baseline, - minimum_block_size_above_baseline), - }, + pending_line: Line::new(float_context.writing_mode, + minimum_block_size_above_baseline, + minimum_depth_below_baseline), floats: float_context, lines: Vec::new(), cur_b: Au(0), @@ -228,18 +246,10 @@ impl LineBreaker { } /// Reinitializes the pending line to blank data. - fn reset_line(&mut self) { - self.pending_line.range.reset(FragmentIndex(0), FragmentIndex(0)); - self.pending_line.bounds = LogicalRect::new(self.floats.writing_mode, - Au(0), - self.cur_b, - Au(0), - Au(0)); - self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode); - self.pending_line.inline_metrics = - InlineMetrics::new(self.minimum_block_size_above_baseline, - self.minimum_depth_below_baseline, - self.minimum_block_size_above_baseline) + fn reset_line(&mut self) -> Line { + mem::replace(&mut self.pending_line, Line::new(self.floats.writing_mode, + self.minimum_block_size_above_baseline, + self.minimum_depth_below_baseline)) } /// Reflows fragments for the given inline flow. @@ -260,10 +270,40 @@ impl LineBreaker { // Do the reflow. self.reflow_fragments(old_fragment_iter, flow, layout_context); + // Perform unicode bidirectional layout. + + let para_level = flow.base.writing_mode.to_bidi_level(); + + // The text within a fragment is at a single bidi embedding level (because we split + // fragments on level run boundaries during flow construction), so we can build a level + // array with just one entry per fragment. + let levels: Vec = self.new_fragments.iter().map(|fragment| match fragment.specific { + SpecificFragmentInfo::ScannedText(ref info) => info.run.bidi_level, + _ => para_level + }).collect(); + + let mut lines = mem::replace(&mut self.lines, Vec::new()); + + // If everything is LTR, don't bother with reordering. + let has_rtl = levels.iter().cloned().any(unicode_bidi::is_rtl); + + if has_rtl { + // Compute and store the visual ordering of the fragments within the line. + for line in &mut lines { + let range = line.range.begin().to_usize()..line.range.end().to_usize(); + let runs = unicode_bidi::visual_runs(range, &levels); + line.visual_runs = Some(runs.iter().map(|run| { + let start = FragmentIndex(run.start as isize); + let len = FragmentIndex(run.len() as isize); + (Range::new(start, len), levels[run.start]) + }).collect()); + } + } + // Place the fragments back into the flow. old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]); flow.fragments = old_fragments; - flow.lines = mem::replace(&mut self.lines, Vec::new()); + flow.lines = lines; } /// Reflows the given fragments, which have been plucked out of the inline flow. @@ -350,7 +390,7 @@ impl LineBreaker { fn flush_current_line(&mut self) { debug!("LineBreaker: flushing line {}: {:?}", self.lines.len(), self.pending_line); self.strip_trailing_whitespace_from_pending_line_if_necessary(); - self.lines.push(self.pending_line); + self.lines.push(self.pending_line.clone()); self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block; self.reset_line(); } @@ -612,7 +652,7 @@ impl LineBreaker { line_flush_mode: LineFlushMode) { let indentation = self.indentation_for_pending_fragment(); if self.pending_line_is_empty() { - assert!(self.new_fragments.len() <= (u16::MAX as usize)); + debug_assert!(self.new_fragments.len() <= (isize::MAX as usize)); self.pending_line.range.reset(FragmentIndex(self.new_fragments.len() as isize), FragmentIndex(0)); } @@ -922,18 +962,47 @@ impl InlineFlow { text_align::T::left | text_align::T::right => unreachable!() } - for fragment_index in line.range.each_index() { - let fragment = fragments.get_mut(fragment_index.to_usize()); - inline_start_position_for_fragment = inline_start_position_for_fragment + - fragment.margin.inline_start; - fragment.border_box = LogicalRect::new(fragment.style.writing_mode, - inline_start_position_for_fragment, - fragment.border_box.start.b, - fragment.border_box.size.inline, - fragment.border_box.size.block); - fragment.update_late_computed_inline_position_if_necessary(); - inline_start_position_for_fragment = inline_start_position_for_fragment + - fragment.border_box.size.inline + fragment.margin.inline_end; + // Lay out the fragments in visual order. + let run_count = match line.visual_runs { + Some(ref runs) => runs.len(), + None => 1 + }; + for run_idx in 0..run_count { + let (range, level) = match line.visual_runs { + Some(ref runs) if is_ltr => runs[run_idx], + Some(ref runs) => runs[run_count - run_idx - 1], // reverse order for RTL runs + None => (line.range, 0) + }; + // If the bidi embedding direction is opposite the layout direction, lay out this + // run in reverse order. + let reverse = unicode_bidi::is_ltr(level) != is_ltr; + let fragment_indices = if reverse { + (range.end().get() - 1..range.begin().get() - 1).step_by(-1) + } else { + (range.begin().get()..range.end().get()).step_by(1) + }; + + for fragment_index in fragment_indices { + let fragment = fragments.get_mut(fragment_index as usize); + inline_start_position_for_fragment = inline_start_position_for_fragment + + fragment.margin.inline_start; + + let border_start = if fragment.style.writing_mode.is_bidi_ltr() == is_ltr { + inline_start_position_for_fragment + } else { + line.green_zone.inline - inline_start_position_for_fragment + - fragment.margin.inline_end + - fragment.border_box.size.inline + }; + fragment.border_box = LogicalRect::new(fragment.style.writing_mode, + border_start, + fragment.border_box.start.b, + fragment.border_box.size.inline, + fragment.border_box.size.block); + fragment.update_late_computed_inline_position_if_necessary(); + inline_start_position_for_fragment = inline_start_position_for_fragment + + fragment.border_box.size.inline + fragment.margin.inline_end; + } } } @@ -1303,6 +1372,7 @@ impl Flow for InlineFlow { self.minimum_depth_below_baseline); scanner.scan_for_lines(self, layout_context); + // Now, go through each line and lay out the fragments inside. let mut line_distance_from_flow_block_start = Au(0); let line_count = self.lines.len(); diff --git a/components/layout/lib.rs b/components/layout/lib.rs index 9bb10cdd93c..37ef193ef54 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -14,6 +14,7 @@ #![feature(plugin)] #![feature(raw)] #![feature(slice_chars)] +#![feature(step_by)] #![feature(str_char)] #![feature(unsafe_no_drop_flag)] @@ -59,6 +60,7 @@ extern crate serde; extern crate smallvec; extern crate string_cache; extern crate style; +extern crate unicode_bidi; extern crate url; // Listed first because of macro definitions diff --git a/components/layout/text.rs b/components/layout/text.rs index 6296dc876c3..8ff70d70a07 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -10,7 +10,7 @@ use fragment::{Fragment, SpecificFragmentInfo, ScannedTextFragmentInfo, Unscanne use inline::InlineFragments; use gfx::font::{DISABLE_KERNING_SHAPING_FLAG, FontMetrics, IGNORE_LIGATURES_SHAPING_FLAG}; -use gfx::font::{RunMetrics, ShapingFlags, ShapingOptions}; +use gfx::font::{RTL_FLAG, RunMetrics, ShapingFlags, ShapingOptions}; use gfx::font_context::FontContext; use gfx::text::glyph::CharIndex; use gfx::text::text_run::TextRun; @@ -23,11 +23,39 @@ use style::computed_values::{line_height, text_orientation, text_rendering, text use style::computed_values::{white_space}; use style::properties::ComputedValues; use style::properties::style_structs::Font as FontStyle; +use unicode_bidi::{is_rtl, process_text}; use util::geometry::Au; use util::linked_list::split_off_head; use util::logical_geometry::{LogicalSize, WritingMode}; use util::range::{Range, RangeIndex}; +/// Returns the concatenated text of a list of unscanned text fragments. +fn text(fragments: &LinkedList) -> String { + // FIXME: Some of this work is later duplicated in split_first_fragment_at_newline_if_necessary + // and transform_text. This code should be refactored so that the all the scanning for + // newlines is done in a single pass. + + let mut text = String::new(); + + for fragment in fragments { + match fragment.specific { + SpecificFragmentInfo::UnscannedText(ref info) => { + match fragment.white_space() { + white_space::T::normal | white_space::T::nowrap => { + text.push_str(&info.text.replace("\n", " ")); + } + white_space::T::pre => { + text.push_str(&info.text); + } + } + } + _ => {} + } + } + text +} + + /// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s. pub struct TextRunScanner { pub clump: LinkedList, @@ -45,11 +73,26 @@ impl TextRunScanner { mut fragments: LinkedList) -> InlineFragments { debug!("TextRunScanner: scanning {} fragments for text runs...", fragments.len()); + debug_assert!(!fragments.is_empty()); + + // Calculate bidi embedding levels, so we can split bidirectional fragments for reordering. + let text = text(&fragments); + let para_level = fragments.front().unwrap().style.writing_mode.to_bidi_level(); + let bidi_info = process_text(&text, Some(para_level)); + + // Optimization: If all the text is LTR, don't bother splitting on bidi levels. + let bidi_levels = if bidi_info.levels.iter().cloned().any(is_rtl) { + Some(&bidi_info.levels[..]) + } else { + None + }; // FIXME(pcwalton): We want to be sure not to allocate multiple times, since this is a // performance-critical spot, but this may overestimate and allocate too much memory. let mut new_fragments = Vec::with_capacity(fragments.len()); let mut last_whitespace = true; + let mut paragraph_bytes_processed = 0; + while !fragments.is_empty() { // Create a clump. split_first_fragment_at_newline_if_necessary(&mut fragments); @@ -66,6 +109,8 @@ impl TextRunScanner { // Flush that clump to the list of fragments we're building up. last_whitespace = self.flush_clump_to_list(font_context, &mut new_fragments, + &mut paragraph_bytes_processed, + bidi_levels, last_whitespace); } @@ -84,6 +129,8 @@ impl TextRunScanner { fn flush_clump_to_list(&mut self, font_context: &mut FontContext, out_fragments: &mut Vec, + paragraph_bytes_processed: &mut usize, + bidi_levels: Option<&[u8]>, mut last_whitespace: bool) -> bool { debug!("TextRunScanner: flushing {} fragments in range", self.clump.len()); @@ -137,47 +184,51 @@ impl TextRunScanner { }; let (mut start_position, mut end_position) = (0, 0); + for character in text.chars() { // Search for the first font in this font group that contains a glyph for this // character. - for font_index in 0..fontgroup.fonts.len() { - if font_index < fontgroup.fonts.len() - 1 && - fontgroup.fonts - .get(font_index) - .unwrap() - .borrow() - .glyph_index(character) - .is_none() { - continue + let mut font_index = 0; + while font_index < fontgroup.fonts.len() - 1 { + if fontgroup.fonts.get(font_index).unwrap().borrow() + .glyph_index(character) + .is_some() { + break } - - // We found the font we want to use. Now, if necessary, flush the mapping - // we were building up. - if run_info.font_index != font_index { - if run_info.text.len() > 0 { - mapping.flush(&mut mappings, - &mut run_info, - &**text, - compression, - text_transform, - &mut last_whitespace, - &mut start_position, - end_position); - run_info_list.push(run_info); - run_info = RunInfo::new(); - mapping = RunMapping::new(&run_info_list[..], - &run_info, - fragment_index); - } - - run_info.font_index = font_index - } - - - // Consume this character. - end_position += character.len_utf8(); - break + font_index += 1; } + + let bidi_level = match bidi_levels { + Some(levels) => levels[*paragraph_bytes_processed], + None => 0 + }; + + // Now, if necessary, flush the mapping we were building up. + if run_info.font_index != font_index || run_info.bidi_level != bidi_level { + if end_position > start_position { + mapping.flush(&mut mappings, + &mut run_info, + &**text, + compression, + text_transform, + &mut last_whitespace, + &mut start_position, + end_position); + } + if run_info.text.len() > 0 { + run_info_list.push(run_info); + run_info = RunInfo::new(); + mapping = RunMapping::new(&run_info_list[..], + &run_info, + fragment_index); + } + run_info.font_index = font_index; + run_info.bidi_level = bidi_level; + } + + // Consume this character. + end_position += character.len_utf8(); + *paragraph_bytes_processed += character.len_utf8(); } // If the mapping is zero-length, don't flush it. @@ -220,8 +271,12 @@ impl TextRunScanner { // FIXME(https://github.com/rust-lang/rust/issues/23338) run_info_list.into_iter().map(|run_info| { + let mut options = options; + if is_rtl(run_info.bidi_level) { + options.flags.insert(RTL_FLAG); + } let mut font = fontgroup.fonts.get(run_info.font_index).unwrap().borrow_mut(); - Arc::new(box TextRun::new(&mut *font, run_info.text, &options)) + Arc::new(box TextRun::new(&mut *font, run_info.text, &options, run_info.bidi_level)) }).collect::>() }; @@ -364,6 +419,8 @@ struct RunInfo { font_index: usize, /// A cached copy of the number of Unicode characters in the text run. character_length: usize, + /// The bidirection embedding level of this text run. + bidi_level: u8, } impl RunInfo { @@ -372,6 +429,7 @@ impl RunInfo { text: String::new(), font_index: 0, character_length: 0, + bidi_level: 0, } } } diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 64d4fd6dca8..90d29e2e9e8 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -729,6 +729,7 @@ dependencies = [ "string_cache 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_plugin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", + "unicode-bidi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] @@ -1401,6 +1402,14 @@ name = "unicase" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-bidi" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "0.2.36" diff --git a/components/util/logical_geometry.rs b/components/util/logical_geometry.rs index 088cf444cab..95a3c3513f4 100644 --- a/components/util/logical_geometry.rs +++ b/components/util/logical_geometry.rs @@ -85,6 +85,14 @@ impl WritingMode { (true, false) => PhysicalSide::Left, } } + + #[inline] + /// The default bidirectional embedding level for this writing mode. + /// + /// Returns 0 if the mode is LTR, or 1 otherwise. + pub fn to_bidi_level(&self) -> u8 { + !self.is_bidi_ltr() as u8 + } } impl fmt::Display for WritingMode { diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 871779c4f80..eb55c30ac09 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -721,6 +721,7 @@ dependencies = [ "string_cache 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_plugin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", + "unicode-bidi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] @@ -1384,6 +1385,14 @@ name = "unicase" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-bidi" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "0.2.36" diff --git a/ports/gonk/Cargo.lock b/ports/gonk/Cargo.lock index eccaf1481cf..d625915614e 100644 --- a/ports/gonk/Cargo.lock +++ b/ports/gonk/Cargo.lock @@ -655,6 +655,7 @@ dependencies = [ "string_cache 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_plugin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", + "unicode-bidi 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] @@ -1282,6 +1283,14 @@ name = "unicase" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-bidi" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "url" version = "0.2.36" diff --git a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-002.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-002.htm.ini deleted file mode 100644 index 76b39f6d402..00000000000 --- a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-002.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[bidi-box-model-002.htm] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-004.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-004.htm.ini deleted file mode 100644 index 62e4f57ff19..00000000000 --- a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-004.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[bidi-box-model-004.htm] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-005.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-005.htm.ini deleted file mode 100644 index c858bb8beb7..00000000000 --- a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-005.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[bidi-box-model-005.htm] - type: reftest - expected: FAIL diff --git a/tests/wpt/metadata-css/css21_dev/html4/bidi-breaking-003.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/bidi-breaking-003.htm.ini index 824fbc26751..25bcc5153ba 100644 --- a/tests/wpt/metadata-css/css21_dev/html4/bidi-breaking-003.htm.ini +++ b/tests/wpt/metadata-css/css21_dev/html4/bidi-breaking-003.htm.ini @@ -1,3 +1,3 @@ [bidi-breaking-003.htm] type: reftest - expected: FAIL + expected: TIMEOUT diff --git a/tests/wpt/metadata-css/css21_dev/html4/bidi-glyph-mirroring-002.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/bidi-glyph-mirroring-002.htm.ini index 30812365408..db6bc057e47 100644 --- a/tests/wpt/metadata-css/css21_dev/html4/bidi-glyph-mirroring-002.htm.ini +++ b/tests/wpt/metadata-css/css21_dev/html4/bidi-glyph-mirroring-002.htm.ini @@ -1,3 +1,5 @@ [bidi-glyph-mirroring-002.htm] type: reftest - expected: FAIL + expected: + if os == "mac": FAIL + PASS diff --git a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-001.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/first-letter-dynamic-002.htm.ini similarity index 51% rename from tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-001.htm.ini rename to tests/wpt/metadata-css/css21_dev/html4/first-letter-dynamic-002.htm.ini index 2ade744d5e8..08bfcf210da 100644 --- a/tests/wpt/metadata-css/css21_dev/html4/bidi-box-model-001.htm.ini +++ b/tests/wpt/metadata-css/css21_dev/html4/first-letter-dynamic-002.htm.ini @@ -1,3 +1,3 @@ -[bidi-box-model-001.htm] +[first-letter-dynamic-002.htm] type: reftest expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001.html.ini b/tests/wpt/metadata/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001.html.ini deleted file mode 100644 index ba74e7d5738..00000000000 --- a/tests/wpt/metadata/html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[grouping-pre-reftest-001.html] - type: reftest - reftype: == - refurl: /html/semantics/grouping-content/the-pre-element/grouping-pre-reftest-001-ref.html - expected: FAIL diff --git a/tests/wpt/metadata-css/css21_dev/html4/bidi-breaking-002.htm.ini b/tests/wpt/metadata/html/semantics/text-level-semantics/the-bdo-element/bdo-override.html.ini similarity index 57% rename from tests/wpt/metadata-css/css21_dev/html4/bidi-breaking-002.htm.ini rename to tests/wpt/metadata/html/semantics/text-level-semantics/the-bdo-element/bdo-override.html.ini index 7e4296a3310..1093b89f0c6 100644 --- a/tests/wpt/metadata-css/css21_dev/html4/bidi-breaking-002.htm.ini +++ b/tests/wpt/metadata/html/semantics/text-level-semantics/the-bdo-element/bdo-override.html.ini @@ -1,3 +1,3 @@ -[bidi-breaking-002.htm] +[bdo-override.html] type: reftest expected: FAIL