Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(719)

Unified Diff: third_party/WebKit/Source/core/editing/iterators/TextIteratorTextNodeHandler.cpp

Issue 2909023003: Move TextIteratorTextNodeHandler to dedicated files (Closed)
Patch Set: Fix copyright Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/WebKit/Source/core/editing/iterators/TextIteratorTextNodeHandler.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/WebKit/Source/core/editing/iterators/TextIteratorTextNodeHandler.cpp
diff --git a/third_party/WebKit/Source/core/editing/iterators/TextIteratorTextNodeHandler.cpp b/third_party/WebKit/Source/core/editing/iterators/TextIteratorTextNodeHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4c6c9dd547204191a7d94d5e319019650815d184
--- /dev/null
+++ b/third_party/WebKit/Source/core/editing/iterators/TextIteratorTextNodeHandler.cpp
@@ -0,0 +1,446 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/editing/iterators/TextIteratorTextNodeHandler.h"
+
+#include <algorithm>
+#include "core/dom/FirstLetterPseudoElement.h"
+#include "core/editing/iterators/TextIteratorTextState.h"
+#include "core/layout/LayoutTextFragment.h"
+#include "core/layout/line/InlineTextBox.h"
+#include "core/layout/line/RootInlineBox.h"
+
+namespace blink {
+
+TextIteratorTextNodeHandler::TextIteratorTextNodeHandler(
+ const TextIteratorBehavior& behavior,
+ TextIteratorTextState* text_state)
+ : behavior_(behavior), text_state_(*text_state) {}
+
+void TextIteratorTextNodeHandler::Initialize(Node* start_container,
+ int start_offset,
+ Node* end_container,
+ int end_offset) {
+ // This function should be called only once.
+ DCHECK(!start_container_);
+ DCHECK_EQ(start_offset_, 0);
+ DCHECK(!end_container_);
+ DCHECK_EQ(end_offset_, 0);
+
+ start_container_ = start_container;
+ start_offset_ = start_offset;
+ end_container_ = end_container;
+ end_offset_ = end_offset;
+}
+
+bool TextIteratorTextNodeHandler::HandleRemainingTextRuns() {
+ if (ShouldProceedToRemainingText())
+ ProceedToRemainingText();
+ // Handle remembered text box
+ if (text_box_) {
+ HandleTextBox();
+ return text_state_.PositionNode();
+ }
+ // Handle remembered pre-formatted text node.
+ if (!needs_handle_pre_formatted_text_node_)
+ return false;
+ HandlePreFormattedTextNode();
+ return text_state_.PositionNode();
+}
+
+bool TextIteratorTextNodeHandler::ShouldHandleFirstLetter(
+ const LayoutText& layout_text) const {
+ if (handled_first_letter_)
+ return false;
+ if (!layout_text.IsTextFragment())
+ return false;
+ const LayoutTextFragment& text_fragment = ToLayoutTextFragment(layout_text);
+ return offset_ < static_cast<int>(text_fragment.TextStartOffset());
+}
+
+static bool HasVisibleTextNode(LayoutText* layout_object) {
+ if (layout_object->Style()->Visibility() == EVisibility::kVisible)
+ return true;
+
+ if (!layout_object->IsTextFragment())
+ return false;
+
+ LayoutTextFragment* fragment = ToLayoutTextFragment(layout_object);
+ if (!fragment->IsRemainingTextLayoutObject())
+ return false;
+
+ DCHECK(fragment->GetFirstLetterPseudoElement());
+ LayoutObject* pseudo_element_layout_object =
+ fragment->GetFirstLetterPseudoElement()->GetLayoutObject();
+ return pseudo_element_layout_object &&
+ pseudo_element_layout_object->Style()->Visibility() ==
+ EVisibility::kVisible;
+}
+
+void TextIteratorTextNodeHandler::HandlePreFormattedTextNode() {
+ // TODO(xiaochengh): Get rid of repeated computation of these fields.
+ LayoutText* const layout_object = text_node_->GetLayoutObject();
+ const String str = layout_object->GetText();
+
+ needs_handle_pre_formatted_text_node_ = false;
+
+ if (last_text_node_ended_with_collapsed_space_ &&
+ HasVisibleTextNode(layout_object)) {
+ if (!behavior_.CollapseTrailingSpace() ||
+ (offset_ > 0 && str[offset_ - 1] == ' ')) {
+ SpliceBuffer(kSpaceCharacter, text_node_, 0, offset_, offset_);
+ needs_handle_pre_formatted_text_node_ = true;
+ return;
+ }
+ }
+ if (ShouldHandleFirstLetter(*layout_object)) {
+ HandleTextNodeFirstLetter(ToLayoutTextFragment(layout_object));
+ if (first_letter_text_) {
+ const String first_letter = first_letter_text_->GetText();
+ const unsigned run_start = offset_;
+ const bool stops_in_first_letter =
+ text_node_ == end_container_ &&
+ end_offset_ <= static_cast<int>(first_letter.length());
+ const unsigned run_end =
+ stops_in_first_letter ? end_offset_ : first_letter.length();
+ EmitText(text_node_, first_letter_text_, run_start, run_end);
+ first_letter_text_ = nullptr;
+ text_box_ = 0;
+ offset_ = run_end;
+ if (!stops_in_first_letter)
+ needs_handle_pre_formatted_text_node_ = true;
+ return;
+ }
+ // We are here only if the DOM and/or layout trees are broken.
+ // For robustness, we should stop processing this node.
+ NOTREACHED();
+ return;
+ }
+ if (layout_object->Style()->Visibility() != EVisibility::kVisible &&
+ !IgnoresStyleVisibility())
+ return;
+ DCHECK_GE(static_cast<unsigned>(offset_), layout_object->TextStartOffset());
+ const unsigned run_start = offset_ - layout_object->TextStartOffset();
+ const unsigned str_length = str.length();
+ const unsigned end = (text_node_ == end_container_)
+ ? end_offset_ - layout_object->TextStartOffset()
+ : str_length;
+ const unsigned run_end = std::min(str_length, end);
+
+ if (run_start >= run_end)
+ return;
+
+ EmitText(text_node_, text_node_->GetLayoutObject(), run_start, run_end);
+}
+
+bool TextIteratorTextNodeHandler::HandleTextNode(Text* node) {
+ text_node_ = node;
+ offset_ = text_node_ == start_container_ ? start_offset_ : 0;
+ handled_first_letter_ = false;
+ first_letter_text_ = nullptr;
+
+ LayoutText* layout_object = text_node_->GetLayoutObject();
+ String str = layout_object->GetText();
+
+ // handle pre-formatted text
+ if (!layout_object->Style()->CollapseWhiteSpace()) {
+ HandlePreFormattedTextNode();
+ return true;
+ }
+
+ if (layout_object->FirstTextBox())
+ text_box_ = layout_object->FirstTextBox();
+
+ const bool should_handle_first_letter =
+ ShouldHandleFirstLetter(*layout_object);
+ if (should_handle_first_letter)
+ HandleTextNodeFirstLetter(ToLayoutTextFragment(layout_object));
+
+ if (!layout_object->FirstTextBox() && str.length() > 0 &&
+ !should_handle_first_letter) {
+ if (layout_object->Style()->Visibility() != EVisibility::kVisible &&
+ !IgnoresStyleVisibility())
+ return false;
+ last_text_node_ended_with_collapsed_space_ =
+ true; // entire block is collapsed space
+ return true;
+ }
+
+ if (first_letter_text_)
+ layout_object = first_letter_text_;
+
+ // Used when text boxes are out of order (Hebrew/Arabic w/ embeded LTR text)
+ if (layout_object->ContainsReversedText()) {
+ sorted_text_boxes_.clear();
+ for (InlineTextBox* text_box = layout_object->FirstTextBox(); text_box;
+ text_box = text_box->NextTextBox()) {
+ sorted_text_boxes_.push_back(text_box);
+ }
+ std::sort(sorted_text_boxes_.begin(), sorted_text_boxes_.end(),
+ InlineTextBox::CompareByStart);
+ sorted_text_boxes_position_ = 0;
+ text_box_ = sorted_text_boxes_.IsEmpty() ? 0 : sorted_text_boxes_[0];
+ }
+
+ HandleTextBox();
+ return true;
+}
+
+// Restore the collapsed space for copy & paste. See http://crbug.com/318925
+size_t TextIteratorTextNodeHandler::RestoreCollapsedTrailingSpace(
+ InlineTextBox* next_text_box,
+ size_t subrun_end) {
+ if (next_text_box || !text_box_->Root().NextRootBox() ||
+ text_box_->Root().LastChild() != text_box_)
+ return subrun_end;
+
+ const String& text = text_node_->GetLayoutObject()->GetText();
+ if (text.EndsWith(' ') == 0 || subrun_end != text.length() - 1 ||
+ text[subrun_end - 1] == ' ')
+ return subrun_end;
+
+ // If there is the leading space in the next line, we don't need to restore
+ // the trailing space.
+ // Example: <div style="width: 2em;"><b><i>foo </i></b> bar</div>
+ InlineBox* first_box_of_next_line =
+ text_box_->Root().NextRootBox()->FirstChild();
+ if (!first_box_of_next_line)
+ return subrun_end + 1;
+ Node* first_node_of_next_line =
+ first_box_of_next_line->GetLineLayoutItem().GetNode();
+ if (!first_node_of_next_line ||
+ first_node_of_next_line->nodeValue()[0] != ' ')
+ return subrun_end + 1;
+
+ return subrun_end;
+}
+
+void TextIteratorTextNodeHandler::HandleTextBox() {
+ LayoutText* layout_object =
+ first_letter_text_ ? first_letter_text_ : text_node_->GetLayoutObject();
+ const unsigned text_start_offset = layout_object->TextStartOffset();
+
+ if (layout_object->Style()->Visibility() != EVisibility::kVisible &&
+ !IgnoresStyleVisibility()) {
+ text_box_ = nullptr;
+ } else {
+ String str = layout_object->GetText();
+ // Start and end offsets in |str|, i.e., str[start..end - 1] should be
+ // emitted (after handling whitespace collapsing).
+ const unsigned start = offset_ - layout_object->TextStartOffset();
+ const unsigned end =
+ (text_node_ == end_container_)
+ ? static_cast<unsigned>(end_offset_) - text_start_offset
+ : INT_MAX;
+ while (text_box_) {
+ const unsigned text_box_start = text_box_->Start();
+ const unsigned run_start = std::max(text_box_start, start);
+
+ // Check for collapsed space at the start of this run.
+ InlineTextBox* first_text_box =
+ layout_object->ContainsReversedText()
+ ? (sorted_text_boxes_.IsEmpty() ? 0 : sorted_text_boxes_[0])
+ : layout_object->FirstTextBox();
+ const bool need_space = last_text_node_ended_with_collapsed_space_ ||
+ (text_box_ == first_text_box &&
+ text_box_start == run_start && run_start > 0);
+ if (need_space &&
+ !layout_object->Style()->IsCollapsibleWhiteSpace(
+ text_state_.LastCharacter()) &&
+ text_state_.LastCharacter()) {
+ if (run_start > 0 && str[run_start - 1] == ' ') {
+ unsigned space_run_start = run_start - 1;
+ while (space_run_start > 0 && str[space_run_start - 1] == ' ')
+ --space_run_start;
+ EmitText(text_node_, layout_object, space_run_start,
+ space_run_start + 1);
+ } else {
+ SpliceBuffer(kSpaceCharacter, text_node_, 0, run_start, run_start);
+ }
+ return;
+ }
+ const unsigned text_box_end = text_box_start + text_box_->Len();
+ const unsigned run_end = std::min(text_box_end, end);
+
+ // Determine what the next text box will be, but don't advance yet
+ InlineTextBox* next_text_box = nullptr;
+ if (layout_object->ContainsReversedText()) {
+ if (sorted_text_boxes_position_ + 1 < sorted_text_boxes_.size())
+ next_text_box = sorted_text_boxes_[sorted_text_boxes_position_ + 1];
+ } else {
+ next_text_box = text_box_->NextTextBox();
+ }
+
+ // FIXME: Based on the outcome of crbug.com/446502 it's possible we can
+ // remove this block. The reason we new it now is because BIDI and
+ // FirstLetter seem to have different ideas of where things can split.
+ // FirstLetter takes the punctuation + first letter, and BIDI will
+ // split out the punctuation and possibly reorder it.
+ if (next_text_box &&
+ !(next_text_box->GetLineLayoutItem().IsEqual(layout_object))) {
+ text_box_ = 0;
+ return;
+ }
+ DCHECK(!next_text_box ||
+ next_text_box->GetLineLayoutItem().IsEqual(layout_object));
+
+ if (run_start < run_end) {
+ // Handle either a single newline character (which becomes a space),
+ // or a run of characters that does not include a newline.
+ // This effectively translates newlines to spaces without copying the
+ // text.
+ if (str[run_start] == '\n') {
+ // We need to preserve new lines in case of PreLine.
+ // See bug crbug.com/317365.
+ if (layout_object->Style()->WhiteSpace() == EWhiteSpace::kPreLine) {
+ SpliceBuffer('\n', text_node_, 0, run_start, run_start);
+ } else {
+ SpliceBuffer(kSpaceCharacter, text_node_, 0, run_start,
+ run_start + 1);
+ }
+ offset_ = text_start_offset + run_start + 1;
+ } else {
+ size_t subrun_end = str.find('\n', run_start);
+ if (subrun_end == kNotFound || subrun_end > run_end) {
+ subrun_end = run_end;
+ subrun_end =
+ RestoreCollapsedTrailingSpace(next_text_box, subrun_end);
+ }
+
+ offset_ = text_start_offset + subrun_end;
+ EmitText(text_node_, layout_object, run_start, subrun_end);
+ }
+
+ // If we are doing a subrun that doesn't go to the end of the text box,
+ // come back again to finish handling this text box; don't advance to
+ // the next one.
+ if (static_cast<unsigned>(text_state_.PositionEndOffset()) <
+ text_box_end)
+ return;
+
+ if (behavior_.DoesNotEmitSpaceBeyondRangeEnd()) {
+ // If the subrun went to the text box end and this end is also the end
+ // of the range, do not advance to the next text box and do not
+ // generate a space, just stop.
+ if (text_box_end == end) {
+ text_box_ = nullptr;
+ return;
+ }
+ }
+
+ // Advance and return
+ const unsigned next_run_start =
+ next_text_box ? next_text_box->Start() : str.length();
+ if (next_run_start > run_end) {
+ last_text_node_ended_with_collapsed_space_ =
+ true; // collapsed space between runs or at the end
+ }
+
+ text_box_ = next_text_box;
+ if (layout_object->ContainsReversedText())
+ ++sorted_text_boxes_position_;
+ return;
+ }
+ // Advance and continue
+ text_box_ = next_text_box;
+ if (layout_object->ContainsReversedText())
+ ++sorted_text_boxes_position_;
+ }
+ }
+
+ if (ShouldProceedToRemainingText()) {
+ ProceedToRemainingText();
+ HandleTextBox();
+ }
+}
+
+bool TextIteratorTextNodeHandler::ShouldProceedToRemainingText() const {
+ if (text_box_ || !remaining_text_box_)
+ return false;
+ if (text_node_ != end_container_)
+ return true;
+ return offset_ < end_offset_;
+}
+
+void TextIteratorTextNodeHandler::ProceedToRemainingText() {
+ text_box_ = remaining_text_box_;
+ remaining_text_box_ = 0;
+ first_letter_text_ = nullptr;
+ offset_ = text_node_->GetLayoutObject()->TextStartOffset();
+}
+
+void TextIteratorTextNodeHandler::HandleTextNodeFirstLetter(
+ LayoutTextFragment* layout_object) {
+ handled_first_letter_ = true;
+
+ if (!layout_object->IsRemainingTextLayoutObject())
+ return;
+
+ FirstLetterPseudoElement* first_letter_element =
+ layout_object->GetFirstLetterPseudoElement();
+ if (!first_letter_element)
+ return;
+
+ LayoutObject* pseudo_layout_object = first_letter_element->GetLayoutObject();
+ if (pseudo_layout_object->Style()->Visibility() != EVisibility::kVisible &&
+ !IgnoresStyleVisibility())
+ return;
+
+ LayoutObject* first_letter = pseudo_layout_object->SlowFirstChild();
+
+ sorted_text_boxes_.clear();
+ remaining_text_box_ = text_box_;
+ CHECK(first_letter && first_letter->IsText());
+ first_letter_text_ = ToLayoutText(first_letter);
+ text_box_ = first_letter_text_->FirstTextBox();
+}
+
+bool TextIteratorTextNodeHandler::FixLeadingWhiteSpaceForReplacedElement(
+ Node* parent) {
+ // This is a hacky way for white space fixup in legacy layout. With LayoutNG,
+ // we can get rid of this function.
+
+ if (behavior_.CollapseTrailingSpace()) {
+ if (text_node_) {
+ String str = text_node_->GetLayoutObject()->GetText();
+ if (last_text_node_ended_with_collapsed_space_ && offset_ > 0 &&
+ str[offset_ - 1] == ' ') {
+ SpliceBuffer(kSpaceCharacter, parent, text_node_, 1, 1);
+ return true;
+ }
+ }
+ } else if (last_text_node_ended_with_collapsed_space_) {
+ SpliceBuffer(kSpaceCharacter, parent, text_node_, 1, 1);
+ return true;
+ }
+
+ return false;
+}
+
+void TextIteratorTextNodeHandler::ResetCollapsedWhiteSpaceFixup() {
+ // This is a hacky way for white space fixup in legacy layout. With LayoutNG,
+ // we can get rid of this function.
+ last_text_node_ended_with_collapsed_space_ = false;
+}
+
+void TextIteratorTextNodeHandler::SpliceBuffer(UChar c,
+ Node* text_node,
+ Node* offset_base_node,
+ int text_start_offset,
+ int text_end_offset) {
+ text_state_.SpliceBuffer(c, text_node, offset_base_node, text_start_offset,
+ text_end_offset);
+ ResetCollapsedWhiteSpaceFixup();
+}
+
+void TextIteratorTextNodeHandler::EmitText(Node* text_node,
+ LayoutText* layout_object,
+ int text_start_offset,
+ int text_end_offset) {
+ text_state_.EmitText(text_node, layout_object, text_start_offset,
+ text_end_offset);
+ ResetCollapsedWhiteSpaceFixup();
+}
+
+} // namespace blink
« no previous file with comments | « third_party/WebKit/Source/core/editing/iterators/TextIteratorTextNodeHandler.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698