mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Basic support for bidirectional text
This commit is contained in:
parent
b386d7ae44
commit
dfac8ce4a1
20 changed files with 309 additions and 107 deletions
|
@ -106,7 +106,9 @@ bitflags! {
|
||||||
#[doc="Set if we are to ignore ligatures."]
|
#[doc="Set if we are to ignore ligatures."]
|
||||||
const IGNORE_LIGATURES_SHAPING_FLAG = 0x02,
|
const IGNORE_LIGATURES_SHAPING_FLAG = 0x02,
|
||||||
#[doc="Set if we are to disable kerning."]
|
#[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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1294,7 +1294,7 @@ impl ScaledFontExtensionMethods for ScaledFont {
|
||||||
let mut azglyphs = vec!();
|
let mut azglyphs = vec!();
|
||||||
azglyphs.reserve(range.length().to_usize());
|
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) {
|
for (_i, glyph) in slice.glyphs.iter_glyphs_for_char_range(&slice.range) {
|
||||||
let glyph_advance = glyph.advance();
|
let glyph_advance = glyph.advance();
|
||||||
let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
|
let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
extern crate harfbuzz;
|
extern crate harfbuzz;
|
||||||
|
|
||||||
use font::{DISABLE_KERNING_SHAPING_FLAG, Font, FontHandleMethods, FontTableMethods, FontTableTag};
|
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 platform::font::FontTable;
|
||||||
use text::glyph::{CharIndex, GlyphStore, GlyphId, GlyphData};
|
use text::glyph::{CharIndex, GlyphStore, GlyphId, GlyphData};
|
||||||
use text::shaping::ShaperMethods;
|
use text::shaping::ShaperMethods;
|
||||||
use text::util::{float_to_fixed, fixed_to_float};
|
use text::util::{float_to_fixed, fixed_to_float};
|
||||||
|
|
||||||
use euclid::Point2D;
|
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::{RUST_hb_blob_create, RUST_hb_face_create_for_tables};
|
||||||
use harfbuzz::{hb_blob_t};
|
use harfbuzz::{hb_blob_t};
|
||||||
use harfbuzz::{hb_bool_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) {
|
fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let hb_buffer: *mut hb_buffer_t = RUST_hb_buffer_create();
|
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,
|
RUST_hb_buffer_add_utf8(hb_buffer,
|
||||||
text.as_ptr() as *const c_char,
|
text.as_ptr() as *const c_char,
|
||||||
|
|
|
@ -5,13 +5,15 @@
|
||||||
use font::{Font, FontHandleMethods, FontMetrics, IS_WHITESPACE_SHAPING_FLAG, RunMetrics};
|
use font::{Font, FontHandleMethods, FontMetrics, IS_WHITESPACE_SHAPING_FLAG, RunMetrics};
|
||||||
use font::{ShapingOptions};
|
use font::{ShapingOptions};
|
||||||
use platform::font_template::FontTemplateData;
|
use platform::font_template::FontTemplateData;
|
||||||
|
use text::glyph::{CharIndex, GlyphStore};
|
||||||
|
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::range::Range;
|
use util::range::Range;
|
||||||
use util::vec::{Comparator, FullBinarySearchMethods};
|
use util::vec::{Comparator, FullBinarySearchMethods};
|
||||||
|
|
||||||
use std::cmp::{Ordering, max};
|
use std::cmp::{Ordering, max};
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use text::glyph::{CharIndex, GlyphStore};
|
|
||||||
|
|
||||||
/// A single "paragraph" of text in one font size and style.
|
/// A single "paragraph" of text in one font size and style.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
@ -23,6 +25,7 @@ pub struct TextRun {
|
||||||
pub font_metrics: FontMetrics,
|
pub font_metrics: FontMetrics,
|
||||||
/// The glyph runs that make up this text run.
|
/// The glyph runs that make up this text run.
|
||||||
pub glyphs: Arc<Vec<GlyphRun>>,
|
pub glyphs: Arc<Vec<GlyphRun>>,
|
||||||
|
pub bidi_level: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single series of glyphs within a text run.
|
/// A single series of glyphs within a text run.
|
||||||
|
@ -35,8 +38,10 @@ pub struct GlyphRun {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NaturalWordSliceIterator<'a> {
|
pub struct NaturalWordSliceIterator<'a> {
|
||||||
glyph_iter: Iter<'a, GlyphRun>,
|
glyphs: &'a [GlyphRun],
|
||||||
|
index: usize,
|
||||||
range: Range<CharIndex>,
|
range: Range<CharIndex>,
|
||||||
|
reverse: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CharIndexComparator;
|
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) due to the inefficient rt failures messing up inline heuristics, I think.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn next(&mut self) -> Option<TextRunSlice<'a>> {
|
fn next(&mut self) -> Option<TextRunSlice<'a>> {
|
||||||
let slice_glyphs = self.glyph_iter.next();
|
let slice_glyphs;
|
||||||
if slice_glyphs.is_none() {
|
if self.reverse {
|
||||||
return None;
|
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 mut char_range = self.range.intersect(&slice_glyphs.range);
|
||||||
let slice_range_begin = slice_glyphs.range.begin();
|
let slice_range_begin = slice_glyphs.range.begin();
|
||||||
|
@ -140,7 +154,7 @@ impl<'a> Iterator for CharacterSliceIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextRun {
|
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 glyphs = TextRun::break_and_shape(font, &text, options);
|
||||||
let run = TextRun {
|
let run = TextRun {
|
||||||
text: Arc::new(text),
|
text: Arc::new(text),
|
||||||
|
@ -148,6 +162,7 @@ impl<'a> TextRun {
|
||||||
font_template: font.handle.template(),
|
font_template: font.handle.template(),
|
||||||
actual_pt_size: font.actual_pt_size,
|
actual_pt_size: font.actual_pt_size,
|
||||||
glyphs: Arc::new(glyphs),
|
glyphs: Arc::new(glyphs),
|
||||||
|
bidi_level: bidi_level,
|
||||||
};
|
};
|
||||||
return run;
|
return run;
|
||||||
}
|
}
|
||||||
|
@ -279,8 +294,36 @@ impl<'a> TextRun {
|
||||||
Some(index) => index,
|
Some(index) => index,
|
||||||
};
|
};
|
||||||
NaturalWordSliceIterator {
|
NaturalWordSliceIterator {
|
||||||
glyph_iter: self.glyphs[index..].iter(),
|
glyphs: &self.glyphs[..],
|
||||||
|
index: index,
|
||||||
range: *range,
|
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<CharIndex>)
|
||||||
|
-> 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,4 +73,4 @@ string_cache_plugin = "0.1"
|
||||||
euclid = "0.1"
|
euclid = "0.1"
|
||||||
serde = "0.4"
|
serde = "0.4"
|
||||||
serde_macros = "0.4"
|
serde_macros = "0.4"
|
||||||
|
unicode-bidi = "0.2"
|
||||||
|
|
|
@ -28,10 +28,11 @@ use std::collections::VecDeque;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Arc;
|
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::{display, overflow_x, position, text_align, text_justify};
|
||||||
use style::computed_values::{text_overflow, vertical_align, white_space};
|
use style::computed_values::{text_overflow, vertical_align, white_space};
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
|
use unicode_bidi;
|
||||||
use util::geometry::{Au, MAX_AU, ZERO_RECT};
|
use util::geometry::{Au, MAX_AU, ZERO_RECT};
|
||||||
use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
|
use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
|
||||||
use util::range::{Range, RangeIndex};
|
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
|
/// 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
|
/// 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.
|
/// the green zone can be taller and wider than the line itself.
|
||||||
#[derive(RustcEncodable, Debug, Copy, Clone)]
|
#[derive(RustcEncodable, Debug, Clone)]
|
||||||
pub struct Line {
|
pub struct Line {
|
||||||
/// A range of line indices that describe line breaks.
|
/// A range of line indices that describe line breaks.
|
||||||
///
|
///
|
||||||
|
@ -95,6 +96,11 @@ pub struct Line {
|
||||||
/// | 'I like' | 'truffles,' | '<img> yes' | 'I do.' |
|
/// | 'I like' | 'truffles,' | '<img> yes' | 'I do.' |
|
||||||
pub range: Range<FragmentIndex>,
|
pub range: Range<FragmentIndex>,
|
||||||
|
|
||||||
|
/// 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<Vec<(Range<FragmentIndex>, u8)>>,
|
||||||
|
|
||||||
/// The bounds are the exact position and extents of the line with respect
|
/// The bounds are the exact position and extents of the line with respect
|
||||||
/// to the parent box.
|
/// to the parent box.
|
||||||
///
|
///
|
||||||
|
@ -153,6 +159,23 @@ pub struct Line {
|
||||||
pub inline_metrics: InlineMetrics,
|
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! {
|
int_range_index! {
|
||||||
#[derive(RustcEncodable)]
|
#[derive(RustcEncodable)]
|
||||||
#[doc = "The index of a fragment in a flattened vector of DOM elements."]
|
#[doc = "The index of a fragment in a flattened vector of DOM elements."]
|
||||||
|
@ -202,14 +225,9 @@ impl LineBreaker {
|
||||||
LineBreaker {
|
LineBreaker {
|
||||||
new_fragments: Vec::new(),
|
new_fragments: Vec::new(),
|
||||||
work_list: VecDeque::new(),
|
work_list: VecDeque::new(),
|
||||||
pending_line: Line {
|
pending_line: Line::new(float_context.writing_mode,
|
||||||
range: Range::empty(),
|
minimum_block_size_above_baseline,
|
||||||
bounds: LogicalRect::zero(float_context.writing_mode),
|
minimum_depth_below_baseline),
|
||||||
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),
|
|
||||||
},
|
|
||||||
floats: float_context,
|
floats: float_context,
|
||||||
lines: Vec::new(),
|
lines: Vec::new(),
|
||||||
cur_b: Au(0),
|
cur_b: Au(0),
|
||||||
|
@ -228,18 +246,10 @@ impl LineBreaker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reinitializes the pending line to blank data.
|
/// Reinitializes the pending line to blank data.
|
||||||
fn reset_line(&mut self) {
|
fn reset_line(&mut self) -> Line {
|
||||||
self.pending_line.range.reset(FragmentIndex(0), FragmentIndex(0));
|
mem::replace(&mut self.pending_line, Line::new(self.floats.writing_mode,
|
||||||
self.pending_line.bounds = LogicalRect::new(self.floats.writing_mode,
|
self.minimum_block_size_above_baseline,
|
||||||
Au(0),
|
self.minimum_depth_below_baseline))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reflows fragments for the given inline flow.
|
/// Reflows fragments for the given inline flow.
|
||||||
|
@ -260,10 +270,40 @@ impl LineBreaker {
|
||||||
// Do the reflow.
|
// Do the reflow.
|
||||||
self.reflow_fragments(old_fragment_iter, flow, layout_context);
|
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<u8> = 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.
|
// Place the fragments back into the flow.
|
||||||
old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]);
|
old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]);
|
||||||
flow.fragments = old_fragments;
|
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.
|
/// 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) {
|
fn flush_current_line(&mut self) {
|
||||||
debug!("LineBreaker: flushing line {}: {:?}", self.lines.len(), self.pending_line);
|
debug!("LineBreaker: flushing line {}: {:?}", self.lines.len(), self.pending_line);
|
||||||
self.strip_trailing_whitespace_from_pending_line_if_necessary();
|
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.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
|
||||||
self.reset_line();
|
self.reset_line();
|
||||||
}
|
}
|
||||||
|
@ -612,7 +652,7 @@ impl LineBreaker {
|
||||||
line_flush_mode: LineFlushMode) {
|
line_flush_mode: LineFlushMode) {
|
||||||
let indentation = self.indentation_for_pending_fragment();
|
let indentation = self.indentation_for_pending_fragment();
|
||||||
if self.pending_line_is_empty() {
|
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),
|
self.pending_line.range.reset(FragmentIndex(self.new_fragments.len() as isize),
|
||||||
FragmentIndex(0));
|
FragmentIndex(0));
|
||||||
}
|
}
|
||||||
|
@ -922,18 +962,47 @@ impl InlineFlow {
|
||||||
text_align::T::left | text_align::T::right => unreachable!()
|
text_align::T::left | text_align::T::right => unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
for fragment_index in line.range.each_index() {
|
// Lay out the fragments in visual order.
|
||||||
let fragment = fragments.get_mut(fragment_index.to_usize());
|
let run_count = match line.visual_runs {
|
||||||
inline_start_position_for_fragment = inline_start_position_for_fragment +
|
Some(ref runs) => runs.len(),
|
||||||
fragment.margin.inline_start;
|
None => 1
|
||||||
fragment.border_box = LogicalRect::new(fragment.style.writing_mode,
|
};
|
||||||
inline_start_position_for_fragment,
|
for run_idx in 0..run_count {
|
||||||
fragment.border_box.start.b,
|
let (range, level) = match line.visual_runs {
|
||||||
fragment.border_box.size.inline,
|
Some(ref runs) if is_ltr => runs[run_idx],
|
||||||
fragment.border_box.size.block);
|
Some(ref runs) => runs[run_count - run_idx - 1], // reverse order for RTL runs
|
||||||
fragment.update_late_computed_inline_position_if_necessary();
|
None => (line.range, 0)
|
||||||
inline_start_position_for_fragment = inline_start_position_for_fragment +
|
};
|
||||||
fragment.border_box.size.inline + fragment.margin.inline_end;
|
// 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);
|
self.minimum_depth_below_baseline);
|
||||||
scanner.scan_for_lines(self, layout_context);
|
scanner.scan_for_lines(self, layout_context);
|
||||||
|
|
||||||
|
|
||||||
// Now, go through each line and lay out the fragments inside.
|
// Now, go through each line and lay out the fragments inside.
|
||||||
let mut line_distance_from_flow_block_start = Au(0);
|
let mut line_distance_from_flow_block_start = Au(0);
|
||||||
let line_count = self.lines.len();
|
let line_count = self.lines.len();
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#![feature(plugin)]
|
#![feature(plugin)]
|
||||||
#![feature(raw)]
|
#![feature(raw)]
|
||||||
#![feature(slice_chars)]
|
#![feature(slice_chars)]
|
||||||
|
#![feature(step_by)]
|
||||||
#![feature(str_char)]
|
#![feature(str_char)]
|
||||||
#![feature(unsafe_no_drop_flag)]
|
#![feature(unsafe_no_drop_flag)]
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ extern crate serde;
|
||||||
extern crate smallvec;
|
extern crate smallvec;
|
||||||
extern crate string_cache;
|
extern crate string_cache;
|
||||||
extern crate style;
|
extern crate style;
|
||||||
|
extern crate unicode_bidi;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
|
|
||||||
// Listed first because of macro definitions
|
// Listed first because of macro definitions
|
||||||
|
|
|
@ -10,7 +10,7 @@ use fragment::{Fragment, SpecificFragmentInfo, ScannedTextFragmentInfo, Unscanne
|
||||||
use inline::InlineFragments;
|
use inline::InlineFragments;
|
||||||
|
|
||||||
use gfx::font::{DISABLE_KERNING_SHAPING_FLAG, FontMetrics, IGNORE_LIGATURES_SHAPING_FLAG};
|
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::font_context::FontContext;
|
||||||
use gfx::text::glyph::CharIndex;
|
use gfx::text::glyph::CharIndex;
|
||||||
use gfx::text::text_run::TextRun;
|
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::computed_values::{white_space};
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style::properties::style_structs::Font as FontStyle;
|
use style::properties::style_structs::Font as FontStyle;
|
||||||
|
use unicode_bidi::{is_rtl, process_text};
|
||||||
use util::geometry::Au;
|
use util::geometry::Au;
|
||||||
use util::linked_list::split_off_head;
|
use util::linked_list::split_off_head;
|
||||||
use util::logical_geometry::{LogicalSize, WritingMode};
|
use util::logical_geometry::{LogicalSize, WritingMode};
|
||||||
use util::range::{Range, RangeIndex};
|
use util::range::{Range, RangeIndex};
|
||||||
|
|
||||||
|
/// Returns the concatenated text of a list of unscanned text fragments.
|
||||||
|
fn text(fragments: &LinkedList<Fragment>) -> 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.
|
/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
|
||||||
pub struct TextRunScanner {
|
pub struct TextRunScanner {
|
||||||
pub clump: LinkedList<Fragment>,
|
pub clump: LinkedList<Fragment>,
|
||||||
|
@ -45,11 +73,26 @@ impl TextRunScanner {
|
||||||
mut fragments: LinkedList<Fragment>)
|
mut fragments: LinkedList<Fragment>)
|
||||||
-> InlineFragments {
|
-> InlineFragments {
|
||||||
debug!("TextRunScanner: scanning {} fragments for text runs...", fragments.len());
|
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
|
// 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.
|
// performance-critical spot, but this may overestimate and allocate too much memory.
|
||||||
let mut new_fragments = Vec::with_capacity(fragments.len());
|
let mut new_fragments = Vec::with_capacity(fragments.len());
|
||||||
let mut last_whitespace = true;
|
let mut last_whitespace = true;
|
||||||
|
let mut paragraph_bytes_processed = 0;
|
||||||
|
|
||||||
while !fragments.is_empty() {
|
while !fragments.is_empty() {
|
||||||
// Create a clump.
|
// Create a clump.
|
||||||
split_first_fragment_at_newline_if_necessary(&mut fragments);
|
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.
|
// Flush that clump to the list of fragments we're building up.
|
||||||
last_whitespace = self.flush_clump_to_list(font_context,
|
last_whitespace = self.flush_clump_to_list(font_context,
|
||||||
&mut new_fragments,
|
&mut new_fragments,
|
||||||
|
&mut paragraph_bytes_processed,
|
||||||
|
bidi_levels,
|
||||||
last_whitespace);
|
last_whitespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +129,8 @@ impl TextRunScanner {
|
||||||
fn flush_clump_to_list(&mut self,
|
fn flush_clump_to_list(&mut self,
|
||||||
font_context: &mut FontContext,
|
font_context: &mut FontContext,
|
||||||
out_fragments: &mut Vec<Fragment>,
|
out_fragments: &mut Vec<Fragment>,
|
||||||
|
paragraph_bytes_processed: &mut usize,
|
||||||
|
bidi_levels: Option<&[u8]>,
|
||||||
mut last_whitespace: bool)
|
mut last_whitespace: bool)
|
||||||
-> bool {
|
-> bool {
|
||||||
debug!("TextRunScanner: flushing {} fragments in range", self.clump.len());
|
debug!("TextRunScanner: flushing {} fragments in range", self.clump.len());
|
||||||
|
@ -137,47 +184,51 @@ impl TextRunScanner {
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut start_position, mut end_position) = (0, 0);
|
let (mut start_position, mut end_position) = (0, 0);
|
||||||
|
|
||||||
for character in text.chars() {
|
for character in text.chars() {
|
||||||
// Search for the first font in this font group that contains a glyph for this
|
// Search for the first font in this font group that contains a glyph for this
|
||||||
// character.
|
// character.
|
||||||
for font_index in 0..fontgroup.fonts.len() {
|
let mut font_index = 0;
|
||||||
if font_index < fontgroup.fonts.len() - 1 &&
|
while font_index < fontgroup.fonts.len() - 1 {
|
||||||
fontgroup.fonts
|
if fontgroup.fonts.get(font_index).unwrap().borrow()
|
||||||
.get(font_index)
|
.glyph_index(character)
|
||||||
.unwrap()
|
.is_some() {
|
||||||
.borrow()
|
break
|
||||||
.glyph_index(character)
|
|
||||||
.is_none() {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
font_index += 1;
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
// 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)
|
// FIXME(https://github.com/rust-lang/rust/issues/23338)
|
||||||
run_info_list.into_iter().map(|run_info| {
|
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();
|
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::<Vec<_>>()
|
}).collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -364,6 +419,8 @@ struct RunInfo {
|
||||||
font_index: usize,
|
font_index: usize,
|
||||||
/// A cached copy of the number of Unicode characters in the text run.
|
/// A cached copy of the number of Unicode characters in the text run.
|
||||||
character_length: usize,
|
character_length: usize,
|
||||||
|
/// The bidirection embedding level of this text run.
|
||||||
|
bidi_level: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunInfo {
|
impl RunInfo {
|
||||||
|
@ -372,6 +429,7 @@ impl RunInfo {
|
||||||
text: String::new(),
|
text: String::new(),
|
||||||
font_index: 0,
|
font_index: 0,
|
||||||
character_length: 0,
|
character_length: 0,
|
||||||
|
bidi_level: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
components/servo/Cargo.lock
generated
9
components/servo/Cargo.lock
generated
|
@ -729,6 +729,7 @@ dependencies = [
|
||||||
"string_cache 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"string_cache_plugin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"style 0.0.1",
|
"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)",
|
"url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
]
|
]
|
||||||
|
@ -1401,6 +1402,14 @@ name = "unicase"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "0.2.36"
|
version = "0.2.36"
|
||||||
|
|
|
@ -85,6 +85,14 @@ impl WritingMode {
|
||||||
(true, false) => PhysicalSide::Left,
|
(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 {
|
impl fmt::Display for WritingMode {
|
||||||
|
|
9
ports/cef/Cargo.lock
generated
9
ports/cef/Cargo.lock
generated
|
@ -721,6 +721,7 @@ dependencies = [
|
||||||
"string_cache 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"string_cache_plugin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"style 0.0.1",
|
"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)",
|
"url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
]
|
]
|
||||||
|
@ -1384,6 +1385,14 @@ name = "unicase"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "0.2.36"
|
version = "0.2.36"
|
||||||
|
|
9
ports/gonk/Cargo.lock
generated
9
ports/gonk/Cargo.lock
generated
|
@ -655,6 +655,7 @@ dependencies = [
|
||||||
"string_cache 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"string_cache_plugin 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"style 0.0.1",
|
"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)",
|
"url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"util 0.0.1",
|
"util 0.0.1",
|
||||||
]
|
]
|
||||||
|
@ -1282,6 +1283,14 @@ name = "unicase"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "0.2.36"
|
version = "0.2.36"
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[bidi-box-model-002.htm]
|
|
||||||
type: reftest
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[bidi-box-model-004.htm]
|
|
||||||
type: reftest
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +0,0 @@
|
||||||
[bidi-box-model-005.htm]
|
|
||||||
type: reftest
|
|
||||||
expected: FAIL
|
|
|
@ -1,3 +1,3 @@
|
||||||
[bidi-breaking-003.htm]
|
[bidi-breaking-003.htm]
|
||||||
type: reftest
|
type: reftest
|
||||||
expected: FAIL
|
expected: TIMEOUT
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
[bidi-glyph-mirroring-002.htm]
|
[bidi-glyph-mirroring-002.htm]
|
||||||
type: reftest
|
type: reftest
|
||||||
expected: FAIL
|
expected:
|
||||||
|
if os == "mac": FAIL
|
||||||
|
PASS
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[bidi-box-model-001.htm]
|
[first-letter-dynamic-002.htm]
|
||||||
type: reftest
|
type: reftest
|
||||||
expected: FAIL
|
expected: FAIL
|
|
@ -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
|
|
|
@ -1,3 +1,3 @@
|
||||||
[bidi-breaking-002.htm]
|
[bdo-override.html]
|
||||||
type: reftest
|
type: reftest
|
||||||
expected: FAIL
|
expected: FAIL
|
Loading…
Add table
Add a link
Reference in a new issue