Commit b0421bf0 authored by Yoshifumi Inoue's avatar Yoshifumi Inoue Committed by Commit Bot

Introduce TextOffsetMapping class for text segment handling

This patch Introduces |TextOffsetMapping|[1] class to mapping between character
offset in block and DOM position for ease of implementing selection modification
with text segments, such as word/sentence/paragraph.

This patch is a preparation of the patch[2].

[1] https://goo.gl/v2Ax8d Text Offset Mapping
[2] http://crrev.com/c/737981 Simplify word granularity handling

Bug: 778507
Change-Id: I63957165ab2741d5c3f1e2b1d29302541d12841b
Reviewed-on: https://chromium-review.googlesource.com/983336Reviewed-by: default avatarYoichi Osato <yoichio@chromium.org>
Commit-Queue: Yoshifumi Inoue <yosin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#547651}
parent 25ce5ac5
......@@ -81,6 +81,8 @@ blink_core_sources("editing") {
"TextAffinity.cpp",
"TextAffinity.h",
"TextGranularity.h",
"TextOffsetMapping.cpp",
"TextOffsetMapping.h",
"VisiblePosition.cpp",
"VisiblePosition.h",
"VisibleSelection.cpp",
......@@ -350,6 +352,7 @@ jumbo_source_set("unit_tests") {
"SelectionModifierTest.cpp",
"SelectionTemplateTest.cpp",
"SetSelectionOptionsTest.cpp",
"TextOffsetMappingTest.cpp",
"VisiblePositionTest.cpp",
"VisibleSelectionTest.cpp",
"VisibleUnitsLineTest.cpp",
......
// Copyright 2018 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/TextOffsetMapping.h"
#include "core/dom/Node.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/Position.h"
#include "core/editing/iterators/CharacterIterator.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/layout/LayoutBlock.h"
namespace blink {
namespace {
// Note: Since "inline-block" and "float" are not considered as text segment
// boundary, we should not consider them as block for scanning.
// Example in selection text:
// <div>|ab<b style="display:inline-box">CD</b>ef</div>
// selection.modify('extent', 'forward', 'word')
// <div>^ab<b style="display:inline-box">CD</b>ef|</div>
// See also test cases for "inline-block" and "float" in "TextIterator.cpp".
bool IsBlockForTextOffsetMapping(const LayoutObject& object) {
return object.IsLayoutBlock() && !object.IsAtomicInlineLevel() &&
!object.IsFloatingOrOutOfFlowPositioned();
}
PositionInFlatTree ComputeEndPosition(const LayoutBlock& block) {
for (LayoutObject* runner = block.LastLeafChild(); runner;
runner = runner->PreviousInPreOrder(&block)) {
Node* node = runner->NonPseudoNode();
if (!node)
continue;
if (node->IsTextNode())
return PositionInFlatTree(node, ToText(node)->length());
return PositionInFlatTree::AfterNode(*node);
}
if (Node* block_node = block.NonPseudoNode()) {
// Empty DIV reaches here.
return PositionInFlatTree::LastPositionInNode(*block_node);
}
// TODO(editing-dev): Once we have the test case reaches here, we should
// change caller to handle this.
NOTREACHED() << block.DebugName();
return PositionInFlatTree();
}
PositionInFlatTree ComputeStartPosition(const LayoutBlock& block) {
for (LayoutObject* runner = block.FirstChild(); runner;
runner = runner->NextInPreOrder(&block)) {
Node* node = runner->NonPseudoNode();
if (!node)
continue;
if (node->IsTextNode())
return PositionInFlatTree(node, 0);
return PositionInFlatTree::BeforeNode(*node);
}
if (Node* block_node = block.NonPseudoNode()) {
// Empty DIV reaches here.
return PositionInFlatTree::FirstPositionInNode(*block_node);
}
// TODO(editing-dev): Once we have the test case reaches here, we should
// change caller to handle this.
NOTREACHED() << block.DebugName();
return PositionInFlatTree();
}
// Returns range of block containing |position|.
// Note: Container node of |position| should be associated to |LayoutObject|.
EphemeralRangeInFlatTree ComputeBlockRange(const LayoutBlock& block) {
DCHECK(!block.IsAtomicInlineLevel());
const PositionInFlatTree& start = ComputeStartPosition(block);
DCHECK(start.IsNotNull()) << block.DebugName();
const PositionInFlatTree& end = ComputeEndPosition(block);
DCHECK(end.IsNotNull()) << block.DebugName();
return EphemeralRangeInFlatTree(start, end);
}
String Ensure16Bit(const String& text) {
String text16(text);
text16.Ensure16Bit();
return text16;
}
} // namespace
TextOffsetMapping::TextOffsetMapping(const LayoutBlock& block,
const TextIteratorBehavior behavior)
: behavior_(behavior),
range_(ComputeBlockRange(block)),
text16_(Ensure16Bit(PlainText(range_, behavior_))) {}
TextOffsetMapping::TextOffsetMapping(const LayoutBlock& block)
: TextOffsetMapping(block,
TextIteratorBehavior::Builder()
.SetEmitsCharactersBetweenAllVisiblePositions(true)
.SetEmitsSmallXForTextSecurity(true)
.Build()) {}
int TextOffsetMapping::ComputeTextOffset(
const PositionInFlatTree& position) const {
return TextIteratorInFlatTree::RangeLength(range_.StartPosition(), position,
behavior_);
}
PositionInFlatTree TextOffsetMapping::GetPositionBefore(unsigned offset) const {
DCHECK_LE(offset, text16_.length());
CharacterIteratorInFlatTree iterator(range_, behavior_);
if (offset >= 1 && offset == text16_.length()) {
iterator.Advance(offset - 1);
return iterator.GetPositionAfter();
}
iterator.Advance(offset);
return iterator.GetPositionBefore();
}
PositionInFlatTree TextOffsetMapping::GetPositionAfter(unsigned offset) const {
DCHECK_LE(offset, text16_.length());
CharacterIteratorInFlatTree iterator(range_, behavior_);
if (offset > 0)
iterator.Advance(offset - 1);
return iterator.GetPositionAfter();
}
EphemeralRangeInFlatTree TextOffsetMapping::ComputeRange(unsigned start,
unsigned end) const {
DCHECK_LE(end, text16_.length());
DCHECK_LE(start, end);
if (start == end)
return EphemeralRangeInFlatTree();
return EphemeralRangeInFlatTree(GetPositionBefore(start),
GetPositionAfter(end));
}
unsigned TextOffsetMapping::FindNonWhitespaceCharacterFrom(
unsigned offset) const {
for (unsigned runner = offset; runner < text16_.length(); ++runner) {
if (!IsWhitespace(text16_[runner]))
return runner;
}
return text16_.length();
}
// static
const LayoutBlock& TextOffsetMapping::ComputeContainigBlock(
const PositionInFlatTree& position) {
const Node& container = *position.ComputeContainerNode();
for (LayoutObject* runner = container.GetLayoutObject(); runner;
runner = runner->ContainingBlock()) {
if (IsBlockForTextOffsetMapping(*runner))
return ToLayoutBlock(*runner);
}
NOTREACHED() << position;
return *ToLayoutBlock(container.GetLayoutObject());
}
// static
LayoutBlock* TextOffsetMapping::NextBlockFor(const LayoutBlock& block) {
for (LayoutObject* runner = block.NextInPreOrderAfterChildren(); runner;
runner = runner->NextInPreOrder()) {
if (IsBlockForTextOffsetMapping(*runner))
return ToLayoutBlock(runner);
}
return nullptr;
}
// static
LayoutBlock* TextOffsetMapping::PreviousBlockFor(const LayoutBlock& block) {
for (LayoutObject* runner = block.PreviousInPreOrder(); runner;
runner = runner->PreviousInPreOrder()) {
if (IsBlockForTextOffsetMapping(*runner) && !block.IsDescendantOf(runner))
return ToLayoutBlock(runner);
}
return nullptr;
}
} // namespace blink
// Copyright 2018 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.
#ifndef TextOffsetMapping_h
#define TextOffsetMapping_h
#include "base/macros.h"
#include "core/CoreExport.h"
#include "core/editing/EphemeralRange.h"
#include "core/editing/Forward.h"
#include "core/editing/iterators/TextIteratorBehavior.h"
#include "platform/heap/Member.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
class LayoutBlock;
// Mapping between position and text offset in |LayoutBlock| == CSS Block
// with using characters from |TextIterator|.
//
// This class is similar to |NGOffsetMapping| which uses |text_offset_| in
// |NGInlineNodeData| except for
// - Treats characters with CSS property "-webkit-text-security" as "x"
// instead of a bullet (U+2022), which breaks words.
// - Contains characters in float and inline-blocks. |NGOffsetMapping| treats
// them as one object replacement character (U+FFFC). See |CollectInlines|
// in |NGInlineNode|.
class CORE_EXPORT TextOffsetMapping final {
STACK_ALLOCATED();
public:
// Constructor |TextOffsetMapping| for specified |LayoutBlock|.
explicit TextOffsetMapping(const LayoutBlock&);
~TextOffsetMapping() = default;
// Returns range of |LayoutBlock|.
const EphemeralRangeInFlatTree GetRange() const { return range_; }
// Returns characters in subtree of |LayoutBlock|, collapsed whitespaces
// are not included.
const String& GetText() const { return text16_; }
// Returns offset in |text16_| of specified position.
int ComputeTextOffset(const PositionInFlatTree&) const;
// Returns position before |offset| in |text16_|
PositionInFlatTree GetPositionBefore(unsigned offset) const;
// Returns position after |offset| in |text16_|
PositionInFlatTree GetPositionAfter(unsigned offset) const;
// Returns a range specified by |start| and |end| offset in |text16_|.
EphemeralRangeInFlatTree ComputeRange(unsigned start, unsigned end) const;
// Returns an offset in |text16_| before non-whitespace character from
// |offset|, inclusive, otherwise returns |text16_.length()|.
// This function is used for computing trailing whitespace after word.
unsigned FindNonWhitespaceCharacterFrom(unsigned offset) const;
// Helper functions to constructor |TextOffsetMapping|.
static const LayoutBlock& ComputeContainigBlock(const PositionInFlatTree&);
static LayoutBlock* NextBlockFor(const LayoutBlock&);
static LayoutBlock* PreviousBlockFor(const LayoutBlock&);
private:
TextOffsetMapping(const LayoutBlock&, const TextIteratorBehavior);
const TextIteratorBehavior behavior_;
const EphemeralRangeInFlatTree range_;
const String text16_;
DISALLOW_COPY_AND_ASSIGN(TextOffsetMapping);
};
} // namespace blink
#endif // TextOffsetMapping_h
......@@ -87,6 +87,40 @@ int CharacterIteratorAlgorithm<Strategy>::EndOffset() const {
return text_iterator_.EndOffsetInCurrentContainer();
}
template <typename Strategy>
PositionTemplate<Strategy>
CharacterIteratorAlgorithm<Strategy>::GetPositionBefore() const {
const Node& node = *text_iterator_.CurrentContainer();
if (text_iterator_.AtEnd()) {
DCHECK_EQ(run_offset_, 0);
return PositionTemplate<Strategy>(
node, text_iterator_.StartOffsetInCurrentContainer());
}
DCHECK_GE(text_iterator_.length(), 1);
if (node.IsTextNode()) {
const int offset = text_iterator_.StartOffsetInCurrentContainer();
return PositionTemplate<Strategy>(node, offset + run_offset_);
}
return PositionTemplate<Strategy>::BeforeNode(node);
}
template <typename Strategy>
PositionTemplate<Strategy>
CharacterIteratorAlgorithm<Strategy>::GetPositionAfter() const {
const Node& node = *text_iterator_.CurrentContainer();
if (text_iterator_.AtEnd()) {
DCHECK_EQ(run_offset_, 0);
return PositionTemplate<Strategy>(
node, text_iterator_.EndOffsetInCurrentContainer());
}
DCHECK_GE(text_iterator_.length(), 1);
if (node.IsTextNode()) {
const int offset = text_iterator_.StartOffsetInCurrentContainer();
return PositionTemplate<Strategy>(node, offset + run_offset_ + 1);
}
return PositionTemplate<Strategy>::AfterNode(node);
}
template <typename Strategy>
PositionTemplate<Strategy> CharacterIteratorAlgorithm<Strategy>::StartPosition()
const {
......
......@@ -68,7 +68,21 @@ class CORE_EXPORT CharacterIteratorAlgorithm {
const Node* CurrentContainer() const;
int StartOffset() const;
int EndOffset() const;
PositionTemplate<Strategy> GetPositionBefore() const;
PositionTemplate<Strategy> GetPositionAfter() const;
// TDOO(editing-dev): We should rename |StartPosition()| to
// |GetPositionBeforeDeprecated()| and use |GetPositionBefore()| to
// avoid using |EditingPositionOf()|.
// Note: Following two tests are failed when using |GetPositionBefore()|
// instead of |StartPosition()|:
// 1. extend-by-sentence-002.html
// 2. move_forward_sentence_empty_line_break.html
PositionTemplate<Strategy> StartPosition() const;
// TDOO(editing-dev): We should rename |EndPosition()| to
// |GetPositionAfterDeprecated()| and use |GetPositionAfter()| to
// avoid using |EditingPositionOf()|.
PositionTemplate<Strategy> EndPosition() const;
EphemeralRangeTemplate<Strategy> CalculateCharacterSubrange(int offset,
......@@ -90,6 +104,8 @@ using CharacterIterator = CharacterIteratorAlgorithm<EditingStrategy>;
extern template class CORE_EXTERN_TEMPLATE_EXPORT
CharacterIteratorAlgorithm<EditingInFlatTreeStrategy>;
using CharacterIteratorInFlatTree =
CharacterIteratorAlgorithm<EditingInFlatTreeStrategy>;
CORE_EXPORT EphemeralRange CalculateCharacterSubrange(const EphemeralRange&,
int character_offset,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment