Fix insertion point layout for text runs with multiple fragments

Before this fix, if a TextRun containing an insertion point was split into
multiple ScannedText fragments, text layout would draw an insertion point
inside of each of the fragments.  This patch records the insertion point
position at most once per TextRun, and copies it only into the appropriate
ScannedText fragment.
This commit is contained in:
Matt Brubeck 2016-04-01 16:56:35 -07:00
parent bcaa62e1bd
commit 831243af7c

View file

@ -170,6 +170,8 @@ impl TextRunScanner {
// First, transform/compress text of all the nodes.
let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new());
let mut insertion_point = None;
for (fragment_index, in_fragment) in self.clump.iter().enumerate() {
let mut mapping = RunMapping::new(&run_info_list[..], &run_info, fragment_index);
let text;
@ -181,8 +183,12 @@ impl TextRunScanner {
}
_ => panic!("Expected an unscanned text fragment!"),
};
let insertion_point = match selection {
Some(range) if range.is_empty() => Some(range.begin()),
insertion_point = match selection {
Some(range) if range.is_empty() => {
// `range` is the range within the current fragment. To get the range
// within the text run, offset it by the length of the preceding fragments.
Some(range.begin() + CharIndex(run_info.character_length as isize))
}
_ => None
};
@ -233,7 +239,6 @@ impl TextRunScanner {
mapping.flush(&mut mappings,
&mut run_info,
&**text,
insertion_point,
compression,
text_transform,
&mut last_whitespace,
@ -241,7 +246,7 @@ impl TextRunScanner {
end_position);
if run_info.text.len() > 0 {
if flush_run {
run_info_list.push(run_info);
run_info.flush(&mut run_info_list, &mut insertion_point);
run_info = RunInfo::new();
}
mapping = RunMapping::new(&run_info_list[..],
@ -263,7 +268,6 @@ impl TextRunScanner {
mapping.flush(&mut mappings,
&mut run_info,
&**text,
insertion_point,
compression,
text_transform,
&mut last_whitespace,
@ -272,7 +276,7 @@ impl TextRunScanner {
}
// Push the final run info.
run_info_list.push(run_info);
run_info.flush(&mut run_info_list, &mut insertion_point);
// Per CSS 2.1 § 16.4, "when the resultant space between two characters is not the same
// as the default space, user agents should not use ligatures." This ensures that, for
@ -346,11 +350,18 @@ impl TextRunScanner {
if requires_line_break_afterward_if_wrapping_on_newlines {
flags.insert(REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES);
}
let insertion_point = if mapping.contains_insertion_point(scanned_run.insertion_point) {
scanned_run.insertion_point
} else {
None
};
let mut new_text_fragment_info = box ScannedTextFragmentInfo::new(
scanned_run.run,
mapping.char_range,
text_size,
scanned_run.insertion_point,
insertion_point,
flags);
let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range);
@ -504,6 +515,26 @@ impl RunInfo {
script: Script::Common,
}
}
/// Finish processing this RunInfo and add it to the "done" list.
///
/// * `insertion_point`: The position of the insertion point, in characters relative to the start
/// of this text run.
fn flush(mut self,
list: &mut Vec<RunInfo>,
insertion_point: &mut Option<CharIndex>) {
if let Some(idx) = *insertion_point {
let char_len = CharIndex(self.character_length as isize);
if idx <= char_len {
// The insertion point is in this text run.
self.insertion_point = insertion_point.take()
} else {
// Continue looking for the insertion point in the next text run.
*insertion_point = Some(idx - char_len)
}
}
list.push(self);
}
}
/// A mapping from a portion of an unscanned text fragment to the text run we're going to create
@ -544,7 +575,6 @@ impl RunMapping {
mappings: &mut Vec<RunMapping>,
run_info: &mut RunInfo,
text: &str,
insertion_point: Option<CharIndex>,
compression: CompressionMode,
text_transform: text_transform::T,
last_whitespace: &mut bool,
@ -568,12 +598,6 @@ impl RunMapping {
*last_whitespace,
is_first_run);
// Record the position of the insertion point if necessary.
if let Some(insertion_point) = insertion_point {
run_info.insertion_point =
Some(CharIndex(run_info.character_length as isize + insertion_point.0))
}
run_info.character_length = run_info.character_length + character_count;
*start_position = end_position;
@ -587,6 +611,18 @@ impl RunMapping {
self.char_range.extend_by(CharIndex(character_count as isize));
mappings.push(self)
}
/// Is the insertion point for this text run within this mapping?
///
/// NOTE: We treat the range as inclusive at both ends, since the insertion point can lie
/// before the first character *or* after the last character, and should be drawn even if the
/// text is empty.
fn contains_insertion_point(&self, insertion_point: Option<CharIndex>) -> bool {
match insertion_point {
None => false,
Some(idx) => self.char_range.begin() <= idx && idx <= self.char_range.end()
}
}
}