From c62d8292c7ee5673cbe4039ba345b13f1148af0c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:20:22 -0700 Subject: [PATCH 1/9] gfx: Add a `Debug` impl for `GlyphStore` instances. Gecko has a `Dump()` routine in a similar spirit. --- components/gfx/text/glyph.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/components/gfx/text/glyph.rs b/components/gfx/text/glyph.rs index b2e574c8e70..45ec39ca717 100644 --- a/components/gfx/text/glyph.rs +++ b/components/gfx/text/glyph.rs @@ -8,6 +8,7 @@ use euclid::point::Point2D; use simd::u32x4; use std::cmp::{Ordering, PartialOrd}; +use std::fmt; use std::mem; use std::u16; use std::vec::Vec; @@ -23,7 +24,7 @@ use util::vec::*; /// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or /// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information /// in DetailedGlyphStore. -#[derive(Clone, Debug, Copy, Deserialize, Serialize)] +#[derive(Clone, Debug, Copy, Deserialize, Serialize, PartialEq)] struct GlyphEntry { value: u32, } @@ -72,6 +73,10 @@ impl GlyphEntry { GlyphEntry::new(glyph_count as u32) } + + fn is_initial(&self) -> bool { + *self == GlyphEntry::initial() + } } /// The id of a particular glyph within a font @@ -645,6 +650,34 @@ impl<'a> GlyphStore { } } +impl fmt::Debug for GlyphStore { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + try!(write!(formatter, "GlyphStore:\n")); + let mut detailed_buffer = self.detail_store.detail_buffer.iter(); + for entry in self.entry_buffer.iter() { + if entry.is_simple() { + try!(write!(formatter, + " simple id={:?} advance={:?}\n", + entry.id(), + entry.advance())); + continue + } + if entry.is_initial() { + continue + } + try!(write!(formatter, " complex...")); + if detailed_buffer.next().is_none() { + continue + } + try!(write!(formatter, + " detailed id={:?} advance={:?}\n", + entry.id(), + entry.advance())); + } + Ok(()) + } +} + /// An iterator over the glyphs in a character range in a `GlyphStore`. pub struct GlyphIterator<'a> { store: &'a GlyphStore, From dc26511ff36abd73c5e69a38172ef6e9abaad3ef Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:21:27 -0700 Subject: [PATCH 2/9] gfx: Properly create continuation glyph entries for ligatures. --- components/gfx/text/shaping/harfbuzz.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs index faca8c746c5..5c0f8f79750 100644 --- a/components/gfx/text/shaping/harfbuzz.rs +++ b/components/gfx/text/shaping/harfbuzz.rs @@ -332,6 +332,7 @@ impl Shaper { let char_byte_start = glyph_data.byte_offset_of_glyph(glyph_span.begin()); char_byte_span.reset(char_byte_start as usize, 0); + let mut glyph_spans_multiple_characters = false; // find a range of chars corresponding to this glyph, plus // any trailing chars that do not have associated glyphs. @@ -350,6 +351,7 @@ impl Shaper { let range = text.char_range_at(char_byte_span.end()); drop(range.ch); char_byte_span.extend_to(range.next); + glyph_spans_multiple_characters = true; } // extend glyph range to max glyph index covered by char_span, @@ -435,7 +437,7 @@ impl Shaper { covered_byte_span.extend_to(cmp::min(end, byte_max)); // fast path: 1-to-1 mapping of single char and single glyph. - if glyph_span.length() == 1 { + if glyph_span.length() == 1 && !glyph_spans_multiple_characters { // TODO(Issue #214): cluster ranges need to be computed before // shaping, and then consulted here. // for now, just pretend that every character is a cluster start. From 4a53c873f5587601c28798785df4b8f61475abf4 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:23:14 -0700 Subject: [PATCH 3/9] util: Make the number of app units per pixel a constant instead of repeating the literal value everywhere. Factoring this value out into a public constant makes it easier to create `Au` constants in other modules. --- components/util/geometry.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/components/util/geometry.rs b/components/util/geometry.rs index f25b93420cc..ea88b0d1713 100644 --- a/components/util/geometry.rs +++ b/components/util/geometry.rs @@ -56,6 +56,9 @@ pub enum ViewportPx {} #[derive(RustcEncodable, Debug, Copy, Clone)] pub enum PagePx {} +/// The number of app units in a pixel. +pub const AU_PER_PX: i32 = 60; + // In summary, the hierarchy of pixel units and the factors to convert from one to the next: // // DevicePixel @@ -202,57 +205,57 @@ impl Au { #[inline] pub fn from_px(px: i32) -> Au { - Au((px * 60) as i32) + Au((px * AU_PER_PX) as i32) } #[inline] pub fn from_page_px(px: Length) -> Au { - Au((px.get() * 60f32) as i32) + Au((px.get() * (AU_PER_PX as f32)) as i32) } /// Rounds this app unit down to the pixel towards zero and returns it. #[inline] pub fn to_px(self) -> i32 { - self.0 / 60 + self.0 / AU_PER_PX } /// Rounds this app unit down to the previous (left or top) pixel and returns it. #[inline] pub fn to_prev_px(self) -> i32 { - ((self.0 as f64) / 60f64).floor() as i32 + ((self.0 as f64) / (AU_PER_PX as f64)).floor() as i32 } /// Rounds this app unit up to the next (right or bottom) pixel and returns it. #[inline] pub fn to_next_px(self) -> i32 { - ((self.0 as f64) / 60f64).ceil() as i32 + ((self.0 as f64) / (AU_PER_PX as f64)).ceil() as i32 } #[inline] pub fn to_nearest_px(self) -> i32 { - ((self.0 as f64) / 60f64).round() as i32 + ((self.0 as f64) / (AU_PER_PX as f64)).round() as i32 } #[inline] pub fn to_f32_px(self) -> f32 { - (self.0 as f32) / 60f32 + (self.0 as f32) / (AU_PER_PX as f32) } #[inline] pub fn to_f64_px(self) -> f64 { - (self.0 as f64) / 60f64 + (self.0 as f64) / (AU_PER_PX as f64) } #[inline] pub fn to_snapped(self) -> Au { - let res = self.0 % 60i32; - return if res >= 30i32 { return Au(self.0 - res + 60i32) } + let res = self.0 % AU_PER_PX; + return if res >= 30i32 { return Au(self.0 - res + AU_PER_PX) } else { return Au(self.0 - res) }; } #[inline] pub fn from_f32_px(px: f32) -> Au { - Au((px * 60f32) as i32) + Au((px * (AU_PER_PX as f32)) as i32) } #[inline] @@ -262,7 +265,7 @@ impl Au { #[inline] pub fn from_f64_px(px: f64) -> Au { - Au((px * 60.) as i32) + Au((px * (AU_PER_PX as f64)) as i32) } } From 34d9a6091b07d7a57ede438f5467faf3fedfa6ae Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:26:01 -0700 Subject: [PATCH 4/9] script: Ask layout to redraw the selection whenever the user moves the caret in an input element. --- components/script/dom/htmlinputelement.rs | 7 +++++-- components/script/dom/htmltextareaelement.rs | 3 +++ components/script/textinput.rs | 19 ++++++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index e02d43c8278..f96c89e7dfa 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -27,9 +27,9 @@ use dom::node::{Node, NodeDamage, NodeTypeId}; use dom::node::{document_from_node, window_from_node}; use dom::virtualmethods::VirtualMethods; use msg::constellation_msg::ConstellationChan; -use textinput::KeyReaction::{TriggerDefaultAction, DispatchInput, Nothing}; +use textinput::KeyReaction::{TriggerDefaultAction, DispatchInput, Nothing, RedrawSelection}; use textinput::Lines::Single; -use textinput::TextInput; +use textinput::{TextInput, TextPoint}; use string_cache::Atom; use util::str::DOMString; @@ -599,6 +599,9 @@ impl VirtualMethods for HTMLInputElement { self.force_relayout(); event.PreventDefault(); } + RedrawSelection => { + self.force_relayout(); + } Nothing => (), } }); diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index f1682d2270b..fd1713057ee 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -350,6 +350,9 @@ impl VirtualMethods for HTMLTextAreaElement { self.force_relayout(); } + KeyReaction::RedrawSelection => { + self.force_relayout(); + } KeyReaction::Nothing => (), } }); diff --git a/components/script/textinput.rs b/components/script/textinput.rs index 8330b19f01f..0adffc77d34 100644 --- a/components/script/textinput.rs +++ b/components/script/textinput.rs @@ -49,6 +49,7 @@ pub struct TextInput { pub enum KeyReaction { TriggerDefaultAction, DispatchInput, + RedrawSelection, Nothing, } @@ -366,7 +367,7 @@ impl TextInput { match key { Key::A if is_control_key(mods) => { self.select_all(); - KeyReaction::Nothing + KeyReaction::RedrawSelection }, Key::C if is_control_key(mods) => { if let Some(text) = self.get_selection_text() { @@ -397,36 +398,36 @@ impl TextInput { } Key::Left => { self.adjust_horizontal_by_one(Direction::Backward, maybe_select); - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::Right => { self.adjust_horizontal_by_one(Direction::Forward, maybe_select); - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::Up => { self.adjust_vertical(-1, maybe_select); - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::Down => { self.adjust_vertical(1, maybe_select); - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::Enter | Key::KpEnter => self.handle_return(), Key::Home => { self.edit_point.index = 0; - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::End => { self.edit_point.index = self.current_line_length(); - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::PageUp => { self.adjust_vertical(-28, maybe_select); - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::PageDown => { self.adjust_vertical(28, maybe_select); - KeyReaction::Nothing + KeyReaction::RedrawSelection } Key::Tab => KeyReaction::TriggerDefaultAction, _ => KeyReaction::Nothing, From 357419dc8dcb20e9ffbc468fdba6e174797533b2 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:31:46 -0700 Subject: [PATCH 5/9] layout: Query and maintain the position of the insertion point throughout layout for input elements. --- components/layout/construct.rs | 61 ++++++++++++++--------- components/layout/fragment.rs | 21 ++++++-- components/layout/generated_content.rs | 2 +- components/layout/text.rs | 45 ++++++++++++++--- components/layout/wrapper.rs | 56 ++++++++++++++++++--- components/script/dom/htmlinputelement.rs | 8 +++ 6 files changed, 152 insertions(+), 41 deletions(-) diff --git a/components/layout/construct.rs b/components/layout/construct.rs index dd1d58a6acc..a0b85802a66 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -41,7 +41,7 @@ use table_rowgroup::TableRowGroupFlow; use table_wrapper::TableWrapperFlow; use text::TextRunScanner; use traversal::PostorderNodeMutTraversal; -use wrapper::{PseudoElementType, ThreadSafeLayoutNode}; +use wrapper::{PseudoElementType, TextContent, ThreadSafeLayoutNode}; use gfx::display_list::OpaqueNode; use script::dom::characterdata::CharacterDataTypeId; @@ -583,7 +583,7 @@ impl<'a> FlowConstructor<'a> { // Add whitespace results. They will be stripped out later on when // between block elements, and retained when between inline elements. let fragment_info = SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(" ".to_owned())); + UnscannedTextFragmentInfo::new(" ".to_owned(), None)); properties::modify_style_for_replaced_content(&mut whitespace_style); properties::modify_style_for_text(&mut whitespace_style); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, @@ -714,31 +714,44 @@ impl<'a> FlowConstructor<'a> { return } + let insertion_point = node.insertion_point(); let mut style = (*style).clone(); properties::modify_style_for_text(&mut style); - for content_item in text_content { - let specific = match content_item { - ContentItem::String(string) => { - let info = UnscannedTextFragmentInfo::from_text(string); - SpecificFragmentInfo::UnscannedText(info) - } - content_item => { - let content_item = box GeneratedContentInfo::ContentItem(content_item); - SpecificFragmentInfo::GeneratedContent(content_item) - } - }; - fragments.fragments - .push_back(Fragment::from_opaque_node_and_style(node.opaque(), - node.get_pseudo_element_type() - .strip(), - style.clone(), - node.restyle_damage(), - specific)) + match text_content { + TextContent::Text(string) => { + let info = UnscannedTextFragmentInfo::new(string, insertion_point); + let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info); + fragments.fragments.push_back(Fragment::from_opaque_node_and_style( + node.opaque(), + node.get_pseudo_element_type().strip(), + style.clone(), + node.restyle_damage(), + specific_fragment_info)) + } + TextContent::GeneratedContent(content_items) => { + for content_item in content_items.into_iter() { + let specific_fragment_info = match content_item { + ContentItem::String(string) => { + let info = UnscannedTextFragmentInfo::new(string, None); + SpecificFragmentInfo::UnscannedText(info) + } + content_item => { + let content_item = box GeneratedContentInfo::ContentItem(content_item); + SpecificFragmentInfo::GeneratedContent(content_item) + } + }; + fragments.fragments.push_back(Fragment::from_opaque_node_and_style( + node.opaque(), + node.get_pseudo_element_type().strip(), + style.clone(), + node.restyle_damage(), + specific_fragment_info)) + } + } } } - /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed /// to happen. @@ -851,7 +864,7 @@ impl<'a> FlowConstructor<'a> { whitespace_damage)) => { // Instantiate the whitespace fragment. let fragment_info = SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(" ".to_owned())); + UnscannedTextFragmentInfo::new(" ".to_owned(), None)); properties::modify_style_for_replaced_content(&mut whitespace_style); properties::modify_style_for_text(&mut whitespace_style); let fragment = Fragment::from_opaque_node_and_style(whitespace_node, @@ -1199,7 +1212,7 @@ impl<'a> FlowConstructor<'a> { unscanned_marker_fragments.push_back(Fragment::new( node, SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(text)))); + UnscannedTextFragmentInfo::new(text, None)))); let marker_fragments = TextRunScanner::new().scan_for_runs( &mut self.layout_context.font_context(), unscanned_marker_fragments); @@ -1793,7 +1806,7 @@ fn control_chars_to_fragment(node: &InlineFragmentNodeInfo, restyle_damage: RestyleDamage) -> Fragment { let info = SpecificFragmentInfo::UnscannedText( - UnscannedTextFragmentInfo::from_text(String::from(text))); + UnscannedTextFragmentInfo::new(String::from(text), None)); let mut style = node.style.clone(); properties::modify_style_for_text(&mut style); Fragment::from_opaque_node_and_style(node.address, diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index a7ab373afdb..f73e6013c57 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -651,6 +651,11 @@ pub struct ScannedTextFragmentInfo { /// The intrinsic size of the text fragment. pub content_size: LogicalSize, + /// The position of the insertion point in characters, if any. + /// + /// TODO(pcwalton): Make this a range. + pub insertion_point: Option, + /// The range within the above text run that this represents. pub range: Range, @@ -669,11 +674,13 @@ impl ScannedTextFragmentInfo { pub fn new(run: Arc, range: Range, content_size: LogicalSize, + insertion_point: &Option, requires_line_break_afterward_if_wrapping_on_newlines: bool) -> ScannedTextFragmentInfo { ScannedTextFragmentInfo { run: run, range: range, + insertion_point: *insertion_point, content_size: content_size, range_end_including_stripped_whitespace: range.end(), requires_line_break_afterward_if_wrapping_on_newlines: @@ -726,14 +733,20 @@ pub struct TruncationResult { pub struct UnscannedTextFragmentInfo { /// The text inside the fragment. pub text: Box, + + /// The position of the insertion point, if any. + /// + /// TODO(pcwalton): Make this a range. + pub insertion_point: Option, } impl UnscannedTextFragmentInfo { /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. #[inline] - pub fn from_text(text: String) -> UnscannedTextFragmentInfo { + pub fn new(text: String, insertion_point: Option) -> UnscannedTextFragmentInfo { UnscannedTextFragmentInfo { text: text.into_boxed_str(), + insertion_point: insertion_point, } } } @@ -843,10 +856,12 @@ impl Fragment { self.border_box.size.block); let requires_line_break_afterward_if_wrapping_on_newlines = self.requires_line_break_afterward_if_wrapping_on_newlines(); + // FIXME(pcwalton): This should modify the insertion point as necessary. let info = box ScannedTextFragmentInfo::new( text_run, split.range, size, + &None, requires_line_break_afterward_if_wrapping_on_newlines); self.transform(size, SpecificFragmentInfo::ScannedText(info)) } @@ -856,8 +871,8 @@ impl Fragment { let mut unscanned_ellipsis_fragments = LinkedList::new(); unscanned_ellipsis_fragments.push_back(self.transform( self.border_box.size, - SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text( - "…".to_owned())))); + SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new("…".to_owned(), + None)))); let ellipsis_fragments = TextRunScanner::new().scan_for_runs(&mut layout_context.font_context(), unscanned_ellipsis_fragments); debug_assert!(ellipsis_fragments.len() == 1); diff --git a/components/layout/generated_content.rs b/components/layout/generated_content.rs index 316f012b885..2e823d3bc1d 100644 --- a/components/layout/generated_content.rs +++ b/components/layout/generated_content.rs @@ -428,7 +428,7 @@ fn render_text(layout_context: &LayoutContext, string: String) -> Option { let mut fragments = LinkedList::new(); - let info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::from_text(string)); + let info = SpecificFragmentInfo::UnscannedText(UnscannedTextFragmentInfo::new(string, None)); fragments.push_back(Fragment::from_opaque_node_and_style(node, pseudo, style, diff --git a/components/layout/text.rs b/components/layout/text.rs index 856017c7f56..4657dc86e23 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -176,9 +176,12 @@ impl TextRunScanner { let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new()); for (fragment_index, in_fragment) in self.clump.iter().enumerate() { let mut mapping = RunMapping::new(&run_info_list[..], &run_info, fragment_index); - let text = match in_fragment.specific { + let text; + let insertion_point; + match in_fragment.specific { SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => { - &text_fragment_info.text + text = &text_fragment_info.text; + insertion_point = text_fragment_info.insertion_point; } _ => panic!("Expected an unscanned text fragment!"), }; @@ -208,6 +211,7 @@ impl TextRunScanner { mapping.flush(&mut mappings, &mut run_info, &**text, + insertion_point, compression, text_transform, &mut last_whitespace, @@ -239,6 +243,7 @@ impl TextRunScanner { mapping.flush(&mut mappings, &mut run_info, &**text, + insertion_point, compression, text_transform, &mut last_whitespace, @@ -275,7 +280,13 @@ impl TextRunScanner { options.flags.insert(RTL_FLAG); } let mut font = fontgroup.fonts.get(run_info.font_index).unwrap().borrow_mut(); - Arc::new(TextRun::new(&mut *font, run_info.text, &options, run_info.bidi_level)) + ScannedTextRun { + run: Arc::new(TextRun::new(&mut *font, + run_info.text, + &options, + run_info.bidi_level)), + insertion_point: run_info.insertion_point, + } }).collect::>() }; @@ -296,19 +307,20 @@ impl TextRunScanner { }; let mut mapping = mappings.next().unwrap(); - let run = runs[mapping.text_run_index].clone(); + let scanned_run = runs[mapping.text_run_index].clone(); let requires_line_break_afterward_if_wrapping_on_newlines = - run.text.char_at_reverse(mapping.byte_range.end()) == '\n'; + scanned_run.run.text.char_at_reverse(mapping.byte_range.end()) == '\n'; if requires_line_break_afterward_if_wrapping_on_newlines { mapping.char_range.extend_by(CharIndex(-1)); } let text_size = old_fragment.border_box.size; let mut new_text_fragment_info = box ScannedTextFragmentInfo::new( - run, + scanned_run.run, mapping.char_range, text_size, + &scanned_run.insertion_point, requires_line_break_afterward_if_wrapping_on_newlines); let new_metrics = new_text_fragment_info.run.metrics_for_range(&mapping.char_range); @@ -382,6 +394,7 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList { @@ -403,12 +416,14 @@ fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList, /// The index of the applicable font in the font group. font_index: usize, /// A cached copy of the number of Unicode characters in the text run. @@ -430,6 +447,7 @@ impl RunInfo { fn new() -> RunInfo { RunInfo { text: String::new(), + insertion_point: None, font_index: 0, character_length: 0, bidi_level: 0, @@ -472,6 +490,7 @@ impl RunMapping { mappings: &mut Vec, run_info: &mut RunInfo, text: &str, + insertion_point: Option, compression: CompressionMode, text_transform: text_transform::T, last_whitespace: &mut bool, @@ -489,6 +508,12 @@ impl RunMapping { old_byte_length, text_transform); + // 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; @@ -570,3 +595,9 @@ fn apply_style_transform_if_necessary(string: &mut String, } } +#[derive(Clone)] +struct ScannedTextRun { + run: Arc, + insertion_point: Option, +} + diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index d346e97a40e..8dfa9907770 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -37,6 +37,7 @@ use incremental::RestyleDamage; use opaque_node::OpaqueNodeMethods; use gfx::display_list::OpaqueNode; +use gfx::text::glyph::CharIndex; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::{PipelineId, SubpageId}; use script::dom::attr::AttrValue; @@ -904,7 +905,7 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { /// its content. Otherwise, panics. /// /// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this. - pub fn text_content(&self) -> Vec { + pub fn text_content(&self) -> TextContent { if self.pseudo != PseudoElementType::Normal { let layout_data_ref = self.borrow_layout_data(); let data = &layout_data_ref.as_ref().unwrap().data; @@ -915,8 +916,10 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { &data.after_style }; return match style.as_ref().unwrap().get_box().content { - content::T::Content(ref value) if !value.is_empty() => (*value).clone(), - _ => vec![], + content::T::Content(ref value) if !value.is_empty() => { + TextContent::GeneratedContent((*value).clone()) + } + _ => TextContent::GeneratedContent(vec![]), }; } @@ -926,22 +929,48 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { let data = unsafe { CharacterDataCast::from_layout_js(&text).data_for_layout().to_owned() }; - return vec![ContentItem::String(data)]; + return TextContent::Text(data); } let input = HTMLInputElementCast::to_layout_js(this); if let Some(input) = input { let data = unsafe { input.get_value_for_layout() }; - return vec![ContentItem::String(data)]; + return TextContent::Text(data); } let area = HTMLTextAreaElementCast::to_layout_js(this); if let Some(area) = area { let data = unsafe { area.get_value_for_layout() }; - return vec![ContentItem::String(data)]; + return TextContent::Text(data); } panic!("not text!") } + /// If the insertion point is within this node, returns it. Otherwise, returns `None`. + pub fn insertion_point(&self) -> Option { + let this = unsafe { + self.get_jsmanaged() + }; + let input = HTMLInputElementCast::to_layout_js(this); + if let Some(input) = input { + let insertion_point = unsafe { + input.get_insertion_point_for_layout() + }; + let text = unsafe { + input.get_value_for_layout() + }; + + let mut character_count = 0; + for (character_index, _) in text.char_indices() { + if character_index == insertion_point.index { + return Some(CharIndex(character_count)) + } + character_count += 1 + } + return Some(CharIndex(character_count)) + } + None + } + /// If this is an image element, returns its URL. If this is not an image element, fails. /// /// FIXME(pcwalton): Don't copy URLs. @@ -1090,3 +1119,18 @@ pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> La let (node, _) = *node; mem::transmute(node) } + +pub enum TextContent { + Text(String), + GeneratedContent(Vec), +} + +impl TextContent { + pub fn is_empty(&self) -> bool { + match *self { + TextContent::Text(_) => false, + TextContent::GeneratedContent(ref content) => content.is_empty(), + } + } +} + diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index f96c89e7dfa..51b9a3c54f0 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -144,6 +144,8 @@ pub trait LayoutHTMLInputElementHelpers { unsafe fn get_value_for_layout(self) -> String; #[allow(unsafe_code)] unsafe fn get_size_for_layout(self) -> u32; + #[allow(unsafe_code)] + unsafe fn get_insertion_point_for_layout(self) -> TextPoint; } pub trait RawLayoutHTMLInputElementHelpers { @@ -194,6 +196,12 @@ impl LayoutHTMLInputElementHelpers for LayoutJS { unsafe fn get_size_for_layout(self) -> u32 { (*self.unsafe_get()).get_size_for_layout() } + + #[allow(unrooted_must_root)] + #[allow(unsafe_code)] + unsafe fn get_insertion_point_for_layout(self) -> TextPoint { + (*self.unsafe_get()).textinput.borrow_for_layout().edit_point + } } impl RawLayoutHTMLInputElementHelpers for HTMLInputElement { From efdf435ba3b9334c15466a765257443b554aebfc Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:32:39 -0700 Subject: [PATCH 6/9] gfx: Paint the insertion point as a one-pixel wide line. --- components/layout/display_list_builder.rs | 65 ++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 9a78c0d8909..bafe3d2be10 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -34,6 +34,7 @@ use gfx::display_list::{GradientStop, ImageDisplayItem, LineDisplayItem}; use gfx::display_list::{OpaqueNode, SolidColorDisplayItem}; use gfx::display_list::{StackingContext, TextDisplayItem, TextOrientation}; use gfx::paint_task::THREAD_TINT_COLORS; +use gfx::text::glyph::CharIndex; use gfx_traits::color; use ipc_channel::ipc::{self, IpcSharedMemory}; use msg::compositor_msg::{ScrollPolicy, LayerId}; @@ -60,15 +61,19 @@ use style::values::computed::{LengthOrNone, LengthOrPercentage, LengthOrPercenta use style::values::specified::{AngleOrCorner, HorizontalDirection, VerticalDirection}; use url::Url; use util::cursor::Cursor; -use util::geometry::{Au, ZERO_POINT}; +use util::geometry::{AU_PER_PX, Au, ZERO_POINT}; use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use util::opts; +use util::range::Range; /// The fake fragment ID we use to indicate the inner display list for `overflow: scroll`. /// /// FIXME(pcwalton): This is pretty ugly. Consider modifying `LayerId` somehow. const FAKE_FRAGMENT_ID_FOR_OVERFLOW_SCROLL: u32 = 1000000; +/// The logical width of an insertion point: at the moment, a one-pixel-wide line. +const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX); + /// Whether a stacking context needs a layer or not. pub enum StackingContextLayerNecessity { Always(LayerId, ScrollPolicy), @@ -229,6 +234,14 @@ pub trait FragmentDisplayListBuilding { stacking_relative_border_box: &Rect) -> ClippingRegion; + /// Builds the display items necessary to paint the selection and/or caret for this fragment, + /// if any. + fn build_display_items_for_selection_if_necessary(&self, + display_list: &mut DisplayList, + stacking_relative_border_box: &Rect, + level: StackingLevel, + clip: &ClippingRegion); + /// Creates the text display item for one text fragment. This can be called multiple times for /// one fragment if there are text shadows. /// @@ -882,6 +895,50 @@ impl FragmentDisplayListBuilding for Fragment { (*parent_clip).clone().intersect_rect(&Rect::new(clip_origin, clip_size)) } + fn build_display_items_for_selection_if_necessary(&self, + display_list: &mut DisplayList, + stacking_relative_border_box: &Rect, + level: StackingLevel, + clip: &ClippingRegion) { + let scanned_text_fragment_info = match self.specific { + SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => { + scanned_text_fragment_info + } + _ => return, + }; + let insertion_point_index = match scanned_text_fragment_info.insertion_point { + Some(insertion_point_index) => insertion_point_index, + None => return, + }; + let range = Range::new(CharIndex(0), insertion_point_index); + let advance = scanned_text_fragment_info.run.advance_for_range(&range); + + let insertion_point_bounds; + let cursor; + if !self.style.writing_mode.is_vertical() { + insertion_point_bounds = + Rect::new(Point2D::new(stacking_relative_border_box.origin.x + advance, + stacking_relative_border_box.origin.y), + Size2D::new(INSERTION_POINT_LOGICAL_WIDTH, + stacking_relative_border_box.size.height)); + cursor = Cursor::TextCursor; + } else { + insertion_point_bounds = + Rect::new(Point2D::new(stacking_relative_border_box.origin.x, + stacking_relative_border_box.origin.y + advance), + Size2D::new(stacking_relative_border_box.size.width, + INSERTION_POINT_LOGICAL_WIDTH)); + cursor = Cursor::VerticalTextCursor; + }; + + display_list.push(DisplayItem::SolidColorClass(box SolidColorDisplayItem { + base: BaseDisplayItem::new(insertion_point_bounds, + DisplayItemMetadata::new(self.node, &*self.style, cursor), + clip.clone()), + color: self.style().get_color().color.to_gfx_color(), + }), level); + } + fn build_display_list(&mut self, display_list: &mut DisplayList, layout_context: &LayoutContext, @@ -991,6 +1048,12 @@ impl FragmentDisplayListBuilding for Fragment { &stacking_relative_border_box, &clip); } + + // Paint the selection point if necessary. + self.build_display_items_for_selection_if_necessary(display_list, + &stacking_relative_border_box, + level, + &clip); } // Create special per-fragment-type display items. From 53f47e74db007cb87544c2b32f6449b8cbf67225 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:33:08 -0700 Subject: [PATCH 7/9] gfx: Remove some useless calls to `drop` during HarfBuzz shaping. --- components/gfx/text/shaping/harfbuzz.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs index 5c0f8f79750..a2580ac1c43 100644 --- a/components/gfx/text/shaping/harfbuzz.rs +++ b/components/gfx/text/shaping/harfbuzz.rs @@ -338,7 +338,6 @@ impl Shaper { // any trailing chars that do not have associated glyphs. while char_byte_span.end() < byte_max { let range = text.char_range_at(char_byte_span.end()); - drop(range.ch); char_byte_span.extend_to(range.next); debug!("Processing char byte span: off={}, len={} for glyph idx={}", @@ -349,7 +348,6 @@ impl Shaper { debug!("Extending char byte span to include byte offset={} with no associated \ glyph", char_byte_span.end()); let range = text.char_range_at(char_byte_span.end()); - drop(range.ch); char_byte_span.extend_to(range.next); glyph_spans_multiple_characters = true; } @@ -482,7 +480,6 @@ impl Shaper { let mut i = covered_byte_span.begin(); loop { let range = text.char_range_at(i); - drop(range.ch); i = range.next; if i >= covered_byte_span.end() { break; } char_idx = char_idx + char_step; From af8fcdf073ea399053c0bce6d246a8c407ac818c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:33:33 -0700 Subject: [PATCH 8/9] gfx: Fix a minor typo. --- components/gfx/text/glyph.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/gfx/text/glyph.rs b/components/gfx/text/glyph.rs index 45ec39ca717..f5999f614f7 100644 --- a/components/gfx/text/glyph.rs +++ b/components/gfx/text/glyph.rs @@ -533,7 +533,7 @@ impl<'a> GlyphStore { assert!(i < self.char_len()); let entry = GlyphEntry::complex(cluster_start, ligature_start, 0); - debug!("adding spacer for chracter without associated glyph[idx={:?}]", i); + debug!("adding spacer for character without associated glyph[idx={:?}]", i); self.entry_buffer[i.to_usize()] = entry; } From 757e2cf4e54e4a4ac1668066d6c280ae577851ab Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 15 Sep 2015 16:34:17 -0700 Subject: [PATCH 9/9] layout: Reformat some long lines and fix some whitespace issues. --- components/gfx/text/glyph.rs | 11 +++-- components/layout/context.rs | 6 +-- components/layout/display_list_builder.rs | 2 + components/layout/layout_task.rs | 58 +++++++++++++++-------- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/components/gfx/text/glyph.rs b/components/gfx/text/glyph.rs index f5999f614f7..9ce46b79045 100644 --- a/components/gfx/text/glyph.rs +++ b/components/gfx/text/glyph.rs @@ -529,7 +529,10 @@ impl<'a> GlyphStore { } // used when a character index has no associated glyph---for example, a ligature continuation. - pub fn add_nonglyph_for_char_index(&mut self, i: CharIndex, cluster_start: bool, ligature_start: bool) { + pub fn add_nonglyph_for_char_index(&mut self, + i: CharIndex, + cluster_start: bool, + ligature_start: bool) { assert!(i < self.char_len()); let entry = GlyphEntry::complex(cluster_start, ligature_start, 0); @@ -691,8 +694,10 @@ impl<'a> GlyphIterator<'a> { #[inline(never)] fn next_glyph_range(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> { match self.glyph_range.as_mut().unwrap().next() { - Some(j) => Some((self.char_index, - GlyphInfo::Detail(self.store, self.char_index, j.get() as u16 /* ??? */))), + Some(j) => { + Some((self.char_index, + GlyphInfo::Detail(self.store, self.char_index, j.get() as u16 /* ??? */))) + } None => { // No more glyphs for current character. Try to get another. self.glyph_range = None; diff --git a/components/layout/context.rs b/components/layout/context.rs index cbea54c47be..3003c6cf56a 100644 --- a/components/layout/context.rs +++ b/components/layout/context.rs @@ -204,9 +204,8 @@ impl<'a> LayoutContext<'a> { } // Not yet requested, async mode - request image from the cache (ImageState::NotRequested, false) => { - self.shared.image_cache_task.request_image(url, - self.shared.image_cache_sender.clone(), - None); + self.shared.image_cache_task + .request_image(url, self.shared.image_cache_sender.clone(), None); None } // Image has been requested, is still pending. Return no image @@ -218,3 +217,4 @@ impl<'a> LayoutContext<'a> { } } } + diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index bafe3d2be10..c4f1d0b14c8 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -1024,6 +1024,7 @@ impl FragmentDisplayListBuilding for Fragment { &clip); } } + if !self.is_scanned_text_fragment() { self.build_display_list_for_box_shadow_if_applicable(&*self.style, display_list, @@ -2107,3 +2108,4 @@ pub enum StackingContextCreationMode { OuterScrollWrapper, InnerScrollWrapper, } + diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 921f49b8672..1a8311e0a78 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -20,8 +20,9 @@ use incremental::{LayoutDamageComputation, REFLOW, REFLOW_ENTIRE_DOCUMENT, REPAI use layout_debug; use opaque_node::OpaqueNodeMethods; use parallel::{self, WorkQueueData}; -use query::{LayoutRPCImpl, process_content_box_request, process_content_boxes_request, MarginPadding, Side}; -use query::{MarginRetrievingFragmentBorderBoxIterator, PositionProperty, PositionRetrievingFragmentBorderBoxIterator}; +use query::{LayoutRPCImpl, process_content_box_request, process_content_boxes_request}; +use query::{MarginPadding, MarginRetrievingFragmentBorderBoxIterator, PositionProperty}; +use query::{PositionRetrievingFragmentBorderBoxIterator, Side}; use sequential; use wrapper::LayoutNode; @@ -244,7 +245,9 @@ impl LayoutTaskFactory for LayoutTask { mem_profiler_chan: mem::ProfilerChan, shutdown_chan: Sender<()>) { let ConstellationChan(con_chan) = constellation_chan.clone(); - spawn_named_with_send_on_failure(format!("LayoutTask {:?}", id), task_state::LAYOUT, move || { + spawn_named_with_send_on_failure(format!("LayoutTask {:?}", id), + task_state::LAYOUT, + move || { { // Ensures layout task is destroyed before we send shutdown message let sender = chan.sender(); let layout_chan = LayoutChan(sender); @@ -561,7 +564,11 @@ impl LayoutTask { self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data) } Msg::LoadStylesheet(url, mq, pending, link_element) => { - self.handle_load_stylesheet(url, mq, pending, link_element, possibly_locked_rw_data) + self.handle_load_stylesheet(url, + mq, + pending, + link_element, + possibly_locked_rw_data) } Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data), Msg::GetRPC(response_chan) => { @@ -734,7 +741,9 @@ impl LayoutTask { Origin::Author); //TODO: mark critical subresources as blocking load as well (#5974) - self.script_chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id, url, responder)).unwrap(); + self.script_chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id, + url, + responder)).unwrap(); self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data); } @@ -923,19 +932,22 @@ impl LayoutTask { atom!("padding-right") => (MarginPadding::Padding, Side::Right), _ => unreachable!() }; - let requested_node: OpaqueNode = OpaqueNodeMethods::from_script_node(requested_node); + let requested_node: OpaqueNode = + OpaqueNodeMethods::from_script_node(requested_node); let mut iterator = MarginRetrievingFragmentBorderBoxIterator::new(requested_node, side, margin_padding, style.writing_mode); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); + sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, + &mut iterator); rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string()); }, atom!("bottom") | atom!("top") | atom!("right") | atom!("left") | atom!("width") | atom!("height") - if applies && positioned && style.get_box().display != display::computed_value::T::none => { + if applies && positioned && style.get_box().display != + display::computed_value::T::none => { let layout_data = layout_node.borrow_layout_data(); let position = layout_data.as_ref().map(|layout_data| { match layout_data.data.flow_construction_result { @@ -954,17 +966,20 @@ impl LayoutTask { atom!("height") => PositionProperty::Height, _ => unreachable!() }; - let requested_node: OpaqueNode = OpaqueNodeMethods::from_script_node(requested_node); + let requested_node: OpaqueNode = + OpaqueNodeMethods::from_script_node(requested_node); let mut iterator = PositionRetrievingFragmentBorderBoxIterator::new(requested_node, property, position); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); + sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, + &mut iterator); rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string()); }, // FIXME: implement used value computation for line-height property => { - rw_data.resolved_style_response = style.computed_value_to_string(property.as_slice()); + rw_data.resolved_style_response = + style.computed_value_to_string(property.as_slice()); } }; } @@ -1193,8 +1208,13 @@ impl LayoutTask { process_content_boxes_request(node, &mut root_flow, &mut rw_data), ReflowQueryType::NodeGeometryQuery(node) => self.process_node_geometry_request(node, &mut root_flow, &mut rw_data), - ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) => - self.process_resolved_style_request(node, pseudo, property, &mut root_flow, &mut rw_data), + ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) => { + self.process_resolved_style_request(node, + pseudo, + property, + &mut root_flow, + &mut rw_data) + } ReflowQueryType::OffsetParentQuery(node) => self.process_offset_parent_query(node, &mut root_flow, &mut rw_data), ReflowQueryType::NoQuery => {} @@ -1312,16 +1332,16 @@ impl LayoutTask { self.profiler_metadata(), self.time_profiler_chan.clone(), || { - if opts::get().nonincremental_layout || flow_ref::deref_mut(&mut root_flow) - .compute_layout_damage() - .contains(REFLOW_ENTIRE_DOCUMENT) { + if opts::get().nonincremental_layout || + flow_ref::deref_mut(&mut root_flow).compute_layout_damage() + .contains(REFLOW_ENTIRE_DOCUMENT) { flow_ref::deref_mut(&mut root_flow).reflow_entire_document() } }); - // Verification of the flow tree, which ensures that all nodes were either marked as leaves - // or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to - // memory safety but is a useful debugging tool.) + // Verification of the flow tree, which ensures that all nodes were either marked as + // leaves or as non-leaves. This becomes a no-op in release builds. (It is + // inconsequential to memory safety but is a useful debugging tool.) self.verify_flow_tree(&mut root_flow); if opts::get().trace_layout {