| Index: third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp b/third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e60408da1721eca99bae46a6957e4998936b1d13
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp
|
| @@ -0,0 +1,434 @@
|
| +/*
|
| + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights
|
| + * reserved.
|
| + *
|
| + * Redistribution and use in source and binary forms, with or without
|
| + * modification, are permitted provided that the following conditions
|
| + * are met:
|
| + * 1. Redistributions of source code must retain the above copyright
|
| + * notice, this list of conditions and the following disclaimer.
|
| + * 2. Redistributions in binary form must reproduce the above copyright
|
| + * notice, this list of conditions and the following disclaimer in the
|
| + * documentation and/or other materials provided with the distribution.
|
| + *
|
| + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
|
| + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
| + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
|
| + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
| + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
| + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
| + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
| + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| + */
|
| +
|
| +#include "core/editing/EditingUtilities.h"
|
| +#include "core/editing/RenderedPosition.h"
|
| +#include "core/editing/VisibleUnits.h"
|
| +#include "core/layout/line/InlineTextBox.h"
|
| +#include "core/layout/line/RootInlineBox.h"
|
| +#include "platform/text/TextBreakIterator.h"
|
| +
|
| +namespace blink {
|
| +
|
| +namespace {
|
| +
|
| +// This class holds a list of |InlineBox| in logical order.
|
| +// TDOO(editing-dev): We should utilize |CachedLogicallyOrderedLeafBoxes| class
|
| +// in |CompositeEditCommand::DeleteInsignificantText()|.
|
| +class CachedLogicallyOrderedLeafBoxes final {
|
| + public:
|
| + CachedLogicallyOrderedLeafBoxes() = default;
|
| +
|
| + const InlineTextBox* PreviousTextBox(const RootInlineBox*,
|
| + const InlineTextBox*);
|
| + const InlineTextBox* NextTextBox(const RootInlineBox*, const InlineTextBox*);
|
| +
|
| + size_t size() const { return leaf_boxes_.size(); }
|
| + const InlineBox* FirstBox() const { return leaf_boxes_[0]; }
|
| +
|
| + private:
|
| + const Vector<InlineBox*>& CollectBoxes(const RootInlineBox*);
|
| + int BoxIndexInLeaves(const InlineTextBox*) const;
|
| +
|
| + const RootInlineBox* root_inline_box_ = nullptr;
|
| + Vector<InlineBox*> leaf_boxes_;
|
| +};
|
| +
|
| +const InlineTextBox* CachedLogicallyOrderedLeafBoxes::PreviousTextBox(
|
| + const RootInlineBox* root,
|
| + const InlineTextBox* box) {
|
| + if (!root)
|
| + return nullptr;
|
| +
|
| + CollectBoxes(root);
|
| +
|
| + // If box is null, root is box's previous RootInlineBox, and previousBox is
|
| + // the last logical box in root.
|
| + int box_index = leaf_boxes_.size() - 1;
|
| + if (box)
|
| + box_index = BoxIndexInLeaves(box) - 1;
|
| +
|
| + for (int i = box_index; i >= 0; --i) {
|
| + if (leaf_boxes_[i]->IsInlineTextBox())
|
| + return ToInlineTextBox(leaf_boxes_[i]);
|
| + }
|
| +
|
| + return nullptr;
|
| +}
|
| +
|
| +const InlineTextBox* CachedLogicallyOrderedLeafBoxes::NextTextBox(
|
| + const RootInlineBox* root,
|
| + const InlineTextBox* box) {
|
| + if (!root)
|
| + return nullptr;
|
| +
|
| + CollectBoxes(root);
|
| +
|
| + // If box is null, root is box's next RootInlineBox, and nextBox is the first
|
| + // logical box in root. Otherwise, root is box's RootInlineBox, and nextBox is
|
| + // the next logical box in the same line.
|
| + size_t next_box_index = 0;
|
| + if (box)
|
| + next_box_index = BoxIndexInLeaves(box) + 1;
|
| +
|
| + for (size_t i = next_box_index; i < leaf_boxes_.size(); ++i) {
|
| + if (leaf_boxes_[i]->IsInlineTextBox())
|
| + return ToInlineTextBox(leaf_boxes_[i]);
|
| + }
|
| +
|
| + return nullptr;
|
| +}
|
| +
|
| +const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::CollectBoxes(
|
| + const RootInlineBox* root) {
|
| + if (root_inline_box_ != root) {
|
| + root_inline_box_ = root;
|
| + leaf_boxes_.clear();
|
| + root->CollectLeafBoxesInLogicalOrder(leaf_boxes_);
|
| + }
|
| + return leaf_boxes_;
|
| +}
|
| +
|
| +int CachedLogicallyOrderedLeafBoxes::BoxIndexInLeaves(
|
| + const InlineTextBox* box) const {
|
| + for (size_t i = 0; i < leaf_boxes_.size(); ++i) {
|
| + if (box == leaf_boxes_[i])
|
| + return i;
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| +const InlineTextBox* LogicallyPreviousBox(
|
| + const VisiblePosition& visible_position,
|
| + const InlineTextBox* text_box,
|
| + bool& previous_box_in_different_block,
|
| + CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
|
| + DCHECK(visible_position.IsValid()) << visible_position;
|
| + const InlineBox* start_box = text_box;
|
| +
|
| + const InlineTextBox* previous_box =
|
| + leaf_boxes.PreviousTextBox(&start_box->Root(), text_box);
|
| + if (previous_box)
|
| + return previous_box;
|
| +
|
| + previous_box =
|
| + leaf_boxes.PreviousTextBox(start_box->Root().PrevRootBox(), nullptr);
|
| + if (previous_box)
|
| + return previous_box;
|
| +
|
| + for (;;) {
|
| + Node* start_node = start_box->GetLineLayoutItem().NonPseudoNode();
|
| + if (!start_node)
|
| + break;
|
| +
|
| + Position position = PreviousRootInlineBoxCandidatePosition(
|
| + start_node, visible_position, kContentIsEditable);
|
| + if (position.IsNull())
|
| + break;
|
| +
|
| + RenderedPosition rendered_position(position, TextAffinity::kDownstream);
|
| + RootInlineBox* previous_root = rendered_position.RootBox();
|
| + if (!previous_root)
|
| + break;
|
| +
|
| + previous_box = leaf_boxes.PreviousTextBox(previous_root, nullptr);
|
| + if (previous_box) {
|
| + previous_box_in_different_block = true;
|
| + return previous_box;
|
| + }
|
| +
|
| + if (!leaf_boxes.size())
|
| + break;
|
| + start_box = leaf_boxes.FirstBox();
|
| + }
|
| + return nullptr;
|
| +}
|
| +
|
| +const InlineTextBox* LogicallyNextBox(
|
| + const VisiblePosition& visible_position,
|
| + const InlineTextBox* text_box,
|
| + bool& next_box_in_different_block,
|
| + CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
|
| + DCHECK(visible_position.IsValid()) << visible_position;
|
| + const InlineBox* start_box = text_box;
|
| +
|
| + const InlineTextBox* next_box =
|
| + leaf_boxes.NextTextBox(&start_box->Root(), text_box);
|
| + if (next_box)
|
| + return next_box;
|
| +
|
| + next_box = leaf_boxes.NextTextBox(start_box->Root().NextRootBox(), nullptr);
|
| + if (next_box)
|
| + return next_box;
|
| +
|
| + for (;;) {
|
| + Node* start_node = start_box->GetLineLayoutItem().NonPseudoNode();
|
| + if (!start_node)
|
| + break;
|
| +
|
| + Position position = NextRootInlineBoxCandidatePosition(
|
| + start_node, visible_position, kContentIsEditable);
|
| + if (position.IsNull())
|
| + break;
|
| +
|
| + RenderedPosition rendered_position(position, TextAffinity::kDownstream);
|
| + RootInlineBox* next_root = rendered_position.RootBox();
|
| + if (!next_root)
|
| + break;
|
| +
|
| + next_box = leaf_boxes.NextTextBox(next_root, nullptr);
|
| + if (next_box) {
|
| + next_box_in_different_block = true;
|
| + return next_box;
|
| + }
|
| +
|
| + if (!leaf_boxes.size())
|
| + break;
|
| + start_box = leaf_boxes.FirstBox();
|
| + }
|
| + return nullptr;
|
| +}
|
| +
|
| +TextBreakIterator* WordBreakIteratorForMinOffsetBoundary(
|
| + const VisiblePosition& visible_position,
|
| + const InlineTextBox* text_box,
|
| + int& previous_box_length,
|
| + bool& previous_box_in_different_block,
|
| + Vector<UChar, 1024>& string,
|
| + CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
|
| + DCHECK(visible_position.IsValid()) << visible_position;
|
| + previous_box_in_different_block = false;
|
| +
|
| + // TODO(editing-dev) Handle the case when we don't have an inline text box.
|
| + const InlineTextBox* previous_box = LogicallyPreviousBox(
|
| + visible_position, text_box, previous_box_in_different_block, leaf_boxes);
|
| +
|
| + int len = 0;
|
| + string.clear();
|
| + if (previous_box) {
|
| + previous_box_length = previous_box->Len();
|
| + previous_box->GetLineLayoutItem().GetText().AppendTo(
|
| + string, previous_box->Start(), previous_box_length);
|
| + len += previous_box_length;
|
| + }
|
| + text_box->GetLineLayoutItem().GetText().AppendTo(string, text_box->Start(),
|
| + text_box->Len());
|
| + len += text_box->Len();
|
| +
|
| + return WordBreakIterator(string.data(), len);
|
| +}
|
| +
|
| +TextBreakIterator* WordBreakIteratorForMaxOffsetBoundary(
|
| + const VisiblePosition& visible_position,
|
| + const InlineTextBox* text_box,
|
| + bool& next_box_in_different_block,
|
| + Vector<UChar, 1024>& string,
|
| + CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
|
| + DCHECK(visible_position.IsValid()) << visible_position;
|
| + next_box_in_different_block = false;
|
| +
|
| + // TODO(editing-dev) Handle the case when we don't have an inline text box.
|
| + const InlineTextBox* next_box = LogicallyNextBox(
|
| + visible_position, text_box, next_box_in_different_block, leaf_boxes);
|
| +
|
| + int len = 0;
|
| + string.clear();
|
| + text_box->GetLineLayoutItem().GetText().AppendTo(string, text_box->Start(),
|
| + text_box->Len());
|
| + len += text_box->Len();
|
| + if (next_box) {
|
| + next_box->GetLineLayoutItem().GetText().AppendTo(string, next_box->Start(),
|
| + next_box->Len());
|
| + len += next_box->Len();
|
| + }
|
| +
|
| + return WordBreakIterator(string.data(), len);
|
| +}
|
| +
|
| +bool IsLogicalStartOfWord(TextBreakIterator* iter,
|
| + int position,
|
| + bool hard_line_break) {
|
| + bool boundary = hard_line_break ? true : iter->isBoundary(position);
|
| + if (!boundary)
|
| + return false;
|
| +
|
| + iter->following(position);
|
| + // isWordTextBreak returns true after moving across a word and false after
|
| + // moving across a punctuation/space.
|
| + return IsWordTextBreak(iter);
|
| +}
|
| +
|
| +bool IslogicalEndOfWord(TextBreakIterator* iter,
|
| + int position,
|
| + bool hard_line_break) {
|
| + bool boundary = iter->isBoundary(position);
|
| + return (hard_line_break || boundary) && IsWordTextBreak(iter);
|
| +}
|
| +
|
| +enum CursorMovementDirection { kMoveLeft, kMoveRight };
|
| +
|
| +VisiblePosition VisualWordPosition(const VisiblePosition& visible_position,
|
| + CursorMovementDirection direction,
|
| + bool skips_space_when_moving_right) {
|
| + DCHECK(visible_position.IsValid()) << visible_position;
|
| + if (visible_position.IsNull())
|
| + return VisiblePosition();
|
| +
|
| + TextDirection block_direction =
|
| + DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
|
| + InlineBox* previously_visited_box = nullptr;
|
| + VisiblePosition current = visible_position;
|
| + TextBreakIterator* iter = nullptr;
|
| +
|
| + CachedLogicallyOrderedLeafBoxes leaf_boxes;
|
| + Vector<UChar, 1024> string;
|
| +
|
| + for (;;) {
|
| + VisiblePosition adjacent_character_position = direction == kMoveRight
|
| + ? RightPositionOf(current)
|
| + : LeftPositionOf(current);
|
| + if (adjacent_character_position.DeepEquivalent() ==
|
| + current.DeepEquivalent() ||
|
| + adjacent_character_position.IsNull())
|
| + return VisiblePosition();
|
| +
|
| + InlineBoxPosition box_position = ComputeInlineBoxPosition(
|
| + adjacent_character_position.DeepEquivalent(), TextAffinity::kUpstream);
|
| + InlineBox* box = box_position.inline_box;
|
| + int offset_in_box = box_position.offset_in_box;
|
| +
|
| + if (!box)
|
| + break;
|
| + if (!box->IsInlineTextBox()) {
|
| + current = adjacent_character_position;
|
| + continue;
|
| + }
|
| +
|
| + InlineTextBox* text_box = ToInlineTextBox(box);
|
| + int previous_box_length = 0;
|
| + bool previous_box_in_different_block = false;
|
| + bool next_box_in_different_block = false;
|
| + bool moving_into_new_box = previously_visited_box != box;
|
| +
|
| + if (offset_in_box == box->CaretMinOffset()) {
|
| + iter = WordBreakIteratorForMinOffsetBoundary(
|
| + visible_position, text_box, previous_box_length,
|
| + previous_box_in_different_block, string, leaf_boxes);
|
| + } else if (offset_in_box == box->CaretMaxOffset()) {
|
| + iter = WordBreakIteratorForMaxOffsetBoundary(visible_position, text_box,
|
| + next_box_in_different_block,
|
| + string, leaf_boxes);
|
| + } else if (moving_into_new_box) {
|
| + iter = WordBreakIterator(text_box->GetLineLayoutItem().GetText(),
|
| + text_box->Start(), text_box->Len());
|
| + previously_visited_box = box;
|
| + }
|
| +
|
| + if (!iter)
|
| + break;
|
| +
|
| + iter->first();
|
| + int offset_in_iterator =
|
| + offset_in_box - text_box->Start() + previous_box_length;
|
| +
|
| + bool is_word_break;
|
| + bool box_has_same_directionality_as_block =
|
| + box->Direction() == block_direction;
|
| + bool moving_backward =
|
| + (direction == kMoveLeft && box->Direction() == TextDirection::kLtr) ||
|
| + (direction == kMoveRight && box->Direction() == TextDirection::kRtl);
|
| + if ((skips_space_when_moving_right &&
|
| + box_has_same_directionality_as_block) ||
|
| + (!skips_space_when_moving_right && moving_backward)) {
|
| + bool logical_start_in_layout_object =
|
| + offset_in_box == static_cast<int>(text_box->Start()) &&
|
| + previous_box_in_different_block;
|
| + is_word_break = IsLogicalStartOfWord(iter, offset_in_iterator,
|
| + logical_start_in_layout_object);
|
| + } else {
|
| + bool logical_end_in_layout_object =
|
| + offset_in_box ==
|
| + static_cast<int>(text_box->Start() + text_box->Len()) &&
|
| + next_box_in_different_block;
|
| + is_word_break = IslogicalEndOfWord(iter, offset_in_iterator,
|
| + logical_end_in_layout_object);
|
| + }
|
| +
|
| + if (is_word_break)
|
| + return adjacent_character_position;
|
| +
|
| + current = adjacent_character_position;
|
| + }
|
| + return VisiblePosition();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// TODO(yosin): Once we move |SelectionModifier::ModifyMovingLeft()| in this
|
| +// file, we can make |LeftWordPosition()| as file local function.
|
| +VisiblePosition LeftWordPosition(const VisiblePosition& visible_position,
|
| + bool skips_space_when_moving_right) {
|
| + DCHECK(visible_position.IsValid()) << visible_position;
|
| + VisiblePosition left_word_break = VisualWordPosition(
|
| + visible_position, kMoveLeft, skips_space_when_moving_right);
|
| + left_word_break = HonorEditingBoundaryAtOrBefore(
|
| + left_word_break, visible_position.DeepEquivalent());
|
| +
|
| + // TODO(editing-dev) How should we handle a non-editable position?
|
| + if (left_word_break.IsNull() &&
|
| + IsEditablePosition(visible_position.DeepEquivalent())) {
|
| + TextDirection block_direction =
|
| + DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
|
| + left_word_break = block_direction == TextDirection::kLtr
|
| + ? StartOfEditableContent(visible_position)
|
| + : EndOfEditableContent(visible_position);
|
| + }
|
| + return left_word_break;
|
| +}
|
| +
|
| +// TODO(yosin): Once we move |SelectionModifier::ModifyMovingRight()| in this
|
| +// file, we can make |RightWordPosition()| as file local function.
|
| +VisiblePosition RightWordPosition(const VisiblePosition& visible_position,
|
| + bool skips_space_when_moving_right) {
|
| + DCHECK(visible_position.IsValid()) << visible_position;
|
| + VisiblePosition right_word_break = VisualWordPosition(
|
| + visible_position, kMoveRight, skips_space_when_moving_right);
|
| + right_word_break = HonorEditingBoundaryAtOrBefore(
|
| + right_word_break, visible_position.DeepEquivalent());
|
| +
|
| + // TODO(editing-dev) How should we handle a non-editable position?
|
| + if (right_word_break.IsNull() &&
|
| + IsEditablePosition(visible_position.DeepEquivalent())) {
|
| + TextDirection block_direction =
|
| + DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
|
| + right_word_break = block_direction == TextDirection::kLtr
|
| + ? EndOfEditableContent(visible_position)
|
| + : StartOfEditableContent(visible_position);
|
| + }
|
| + return right_word_break;
|
| +}
|
| +
|
| +} // namespace blink
|
|
|