blob: e5499cd673f88ec4d1835289277f9df12d8c9434 [file] [log] [blame]
/*
* 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.
*/
// 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/InlineBoxPosition.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/InlineBoxTraversal.h"
#include "core/editing/Position.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleUnits.h"
#include "core/layout/LayoutBlock.h"
#include "core/layout/LayoutText.h"
#include "core/layout/line/InlineTextBox.h"
namespace blink {
namespace {
bool IsNonTextLeafChild(LayoutObject* object) {
if (object->SlowFirstChild())
return false;
if (object->IsText())
return false;
return true;
}
InlineTextBox* SearchAheadForBetterMatch(const LayoutText* layout_object) {
LayoutBlock* container = layout_object->ContainingBlock();
for (LayoutObject* next = layout_object->NextInPreOrder(container); next;
next = next->NextInPreOrder(container)) {
if (next->IsLayoutBlock())
return nullptr;
if (next->IsBR())
return nullptr;
if (IsNonTextLeafChild(next))
return nullptr;
if (next->IsText()) {
InlineTextBox* match = nullptr;
int min_offset = INT_MAX;
for (InlineTextBox* box : InlineTextBoxesOf(*ToLayoutText(next))) {
int caret_min_offset = box->CaretMinOffset();
if (caret_min_offset < min_offset) {
match = box;
min_offset = caret_min_offset;
}
}
if (match)
return match;
}
}
return nullptr;
}
// Returns true if |inlineBox| starts different direction of embedded text ru.
// See [1] for details.
// [1] UNICODE BIDIRECTIONAL ALGORITHM, http://unicode.org/reports/tr9/
bool IsStartOfDifferentDirection(const InlineBox* inline_box) {
InlineBox* prev_box = inline_box->PrevLeafChild();
if (!prev_box)
return true;
if (prev_box->Direction() == inline_box->Direction())
return true;
DCHECK_NE(prev_box->BidiLevel(), inline_box->BidiLevel());
return prev_box->BidiLevel() > inline_box->BidiLevel();
}
template <typename Strategy>
PositionTemplate<Strategy> DownstreamIgnoringEditingBoundaries(
PositionTemplate<Strategy> position) {
PositionTemplate<Strategy> last_position;
while (!position.IsEquivalent(last_position)) {
last_position = position;
position = MostForwardCaretPosition(position, kCanCrossEditingBoundary);
}
return position;
}
template <typename Strategy>
PositionTemplate<Strategy> UpstreamIgnoringEditingBoundaries(
PositionTemplate<Strategy> position) {
PositionTemplate<Strategy> last_position;
while (!position.IsEquivalent(last_position)) {
last_position = position;
position = MostBackwardCaretPosition(position, kCanCrossEditingBoundary);
}
return position;
}
InlineBoxPosition AdjustInlineBoxPositionForPrimaryDirection(
InlineBox* inline_box,
int caret_offset) {
if (caret_offset == inline_box->CaretRightmostOffset()) {
InlineBox* const next_box = inline_box->NextLeafChild();
if (!next_box || next_box->BidiLevel() >= inline_box->BidiLevel())
return InlineBoxPosition(inline_box, caret_offset);
const unsigned level = next_box->BidiLevel();
InlineBox* const prev_box =
InlineBoxTraversal::FindLeftBidiRun(*inline_box, level);
// For example, abc FED 123 ^ CBA
if (prev_box && prev_box->BidiLevel() == level)
return InlineBoxPosition(inline_box, caret_offset);
// For example, abc 123 ^ CBA
InlineBox* const result_box =
InlineBoxTraversal::FindRightBoundaryOfEntireBidiRun(*inline_box,
level);
return InlineBoxPosition(result_box, result_box->CaretRightmostOffset());
}
if (IsStartOfDifferentDirection(inline_box))
return InlineBoxPosition(inline_box, caret_offset);
const unsigned level = inline_box->PrevLeafChild()->BidiLevel();
InlineBox* const next_box =
InlineBoxTraversal::FindRightBidiRun(*inline_box, level);
if (next_box && next_box->BidiLevel() == level)
return InlineBoxPosition(inline_box, caret_offset);
InlineBox* const result_box =
InlineBoxTraversal::FindLeftBoundaryOfEntireBidiRun(*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretLeftmostOffset());
}
InlineBoxPosition AdjustInlineBoxPositionForTextDirection(
InlineBox* inline_box,
int caret_offset,
UnicodeBidi unicode_bidi,
TextDirection primary_direction) {
if (inline_box->Direction() == primary_direction)
return AdjustInlineBoxPositionForPrimaryDirection(inline_box, caret_offset);
const unsigned char level = inline_box->BidiLevel();
if (caret_offset == inline_box->CaretLeftmostOffset()) {
InlineBox* const prev_box = inline_box->PrevLeafChildIgnoringLineBreak();
if (!prev_box || prev_box->BidiLevel() < level) {
// Left edge of a secondary run. Set to the right edge of the entire
// run.
InlineBox* const result_box =
InlineBoxTraversal::FindRightBoundaryOfEntireBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretRightmostOffset());
}
if (prev_box->BidiLevel() <= level)
return InlineBoxPosition(inline_box, caret_offset);
// Right edge of a "tertiary" run. Set to the left edge of that run.
InlineBox* const result_box =
InlineBoxTraversal::FindLeftBoundaryOfBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretLeftmostOffset());
}
if (unicode_bidi == UnicodeBidi::kPlaintext) {
if (inline_box->BidiLevel() < level)
return InlineBoxPosition(inline_box, inline_box->CaretLeftmostOffset());
return InlineBoxPosition(inline_box, inline_box->CaretRightmostOffset());
}
InlineBox* const next_box = inline_box->NextLeafChildIgnoringLineBreak();
if (!next_box || next_box->BidiLevel() < level) {
// Right edge of a secondary run. Set to the left edge of the entire
// run.
InlineBox* const result_box =
InlineBoxTraversal::FindLeftBoundaryOfEntireBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretLeftmostOffset());
}
if (next_box->BidiLevel() <= level)
return InlineBoxPosition(inline_box, caret_offset);
// Left edge of a "tertiary" run. Set to the right edge of that run.
InlineBox* const result_box =
InlineBoxTraversal::FindRightBoundaryOfBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretRightmostOffset());
}
// Returns true if |caret_offset| is at edge of |box| based on |affinity|.
// |caret_offset| must be either |box.CaretMinOffset()| or
// |box.CaretMaxOffset()|.
bool IsCaretAtEdgeOfInlineTextBox(int caret_offset,
const InlineTextBox& box,
TextAffinity affinity) {
if (caret_offset == box.CaretMinOffset())
return affinity == TextAffinity::kDownstream;
DCHECK_EQ(caret_offset, box.CaretMaxOffset());
if (affinity == TextAffinity::kUpstream)
return true;
return box.NextLeafChild() && box.NextLeafChild()->IsLineBreak();
}
template <typename Strategy>
const LayoutObject& GetLayoutObjectSkippingShadowRoot(
const PositionTemplate<Strategy>& position) {
// TODO(editing-dev): This function doesn't handle all types of positions. We
// may want to investigate callers and decide if we need to generalize it.
DCHECK(position.IsNotNull());
const Node* anchor_node = position.AnchorNode();
const LayoutObject* result =
anchor_node->IsShadowRoot()
? ToShadowRoot(anchor_node)->host().GetLayoutObject()
: anchor_node->GetLayoutObject();
DCHECK(result) << position;
return *result;
}
InlineBoxPosition ComputeInlineBoxPositionForTextNode(
const LayoutText* text_layout_object,
int caret_offset,
TextAffinity affinity,
TextDirection primary_direction) {
// TODO(editing-dev): Add the following DCHECK when ready.
// DCHECK(CanUseInlineBox(*text_layout_object));
InlineBox* inline_box = nullptr;
InlineTextBox* candidate = nullptr;
for (InlineTextBox* box : InlineTextBoxesOf(*text_layout_object)) {
int caret_min_offset = box->CaretMinOffset();
int caret_max_offset = box->CaretMaxOffset();
if (caret_offset < caret_min_offset || caret_offset > caret_max_offset ||
(caret_offset == caret_max_offset && box->IsLineBreak()))
continue;
if (caret_offset > caret_min_offset && caret_offset < caret_max_offset)
return InlineBoxPosition(box, caret_offset);
if (IsCaretAtEdgeOfInlineTextBox(caret_offset, *box, affinity)) {
inline_box = box;
break;
}
candidate = box;
}
if (candidate && candidate == text_layout_object->LastTextBox() &&
affinity == TextAffinity::kDownstream) {
inline_box = SearchAheadForBetterMatch(text_layout_object);
if (inline_box)
caret_offset = inline_box->CaretMinOffset();
}
if (!inline_box)
inline_box = candidate;
if (!inline_box)
return InlineBoxPosition();
return AdjustInlineBoxPositionForTextDirection(
inline_box, caret_offset, text_layout_object->Style()->GetUnicodeBidi(),
primary_direction);
}
InlineBoxPosition ComputeInlineBoxPositionForAtomicInline(
const LayoutObject* layout_object,
int caret_offset,
TextDirection primary_direction) {
// TODO(editing-dev): Add the following DCHECK when ready.
// DCHECK(CanUseInlineBox(*layout_object);
DCHECK(layout_object->IsBox());
InlineBox* const inline_box = ToLayoutBox(layout_object)->InlineBoxWrapper();
if (!inline_box)
return InlineBoxPosition();
if ((caret_offset > inline_box->CaretMinOffset() &&
caret_offset < inline_box->CaretMaxOffset()))
return InlineBoxPosition(inline_box, caret_offset);
return AdjustInlineBoxPositionForTextDirection(
inline_box, caret_offset, layout_object->Style()->GetUnicodeBidi(),
primary_direction);
}
template <typename Strategy>
PositionWithAffinityTemplate<Strategy> ComputeInlineAdjustedPositionAlgorithm(
const PositionWithAffinityTemplate<Strategy>&);
template <typename Strategy>
PositionWithAffinityTemplate<Strategy> AdjustBlockFlowPositionToInline(
const PositionTemplate<Strategy>& position) {
// Try a visually equivalent position with possibly opposite editability. This
// helps in case |position| is in an editable block but surrounded by
// non-editable positions. It acts to negate the logic at the beginning of
// |LayoutObject::CreatePositionWithAffinity()|.
const PositionTemplate<Strategy>& downstream_equivalent =
DownstreamIgnoringEditingBoundaries(position);
if (downstream_equivalent != position) {
return ComputeInlineAdjustedPositionAlgorithm(
PositionWithAffinityTemplate<Strategy>(downstream_equivalent,
TextAffinity::kUpstream));
}
const PositionTemplate<Strategy>& upstream_equivalent =
UpstreamIgnoringEditingBoundaries(position);
if (upstream_equivalent == position ||
DownstreamIgnoringEditingBoundaries(upstream_equivalent) == position)
return PositionWithAffinityTemplate<Strategy>();
return ComputeInlineAdjustedPositionAlgorithm(
PositionWithAffinityTemplate<Strategy>(upstream_equivalent,
TextAffinity::kUpstream));
}
template <typename Strategy>
PositionWithAffinityTemplate<Strategy> ComputeInlineAdjustedPositionAlgorithm(
const PositionWithAffinityTemplate<Strategy>& position) {
const LayoutObject& layout_object =
GetLayoutObjectSkippingShadowRoot(position.GetPosition());
if (layout_object.IsText())
return position;
if (layout_object.IsAtomicInlineLevel()) {
// TODO(crbug.com/567964): Change the following branch to DCHECK once fixed.
if (!layout_object.IsInline())
return PositionWithAffinityTemplate<Strategy>();
return position;
}
if (!layout_object.IsLayoutBlockFlow() ||
!CanHaveChildrenForEditing(position.AnchorNode()) ||
!HasRenderedNonAnonymousDescendantsWithHeight(&layout_object))
return PositionWithAffinityTemplate<Strategy>();
return AdjustBlockFlowPositionToInline(position.GetPosition());
}
template <typename Strategy>
InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(
const PositionWithAffinityTemplate<Strategy>& adjusted,
TextDirection primary_direction) {
const PositionTemplate<Strategy>& position = adjusted.GetPosition();
const LayoutObject& layout_object =
GetLayoutObjectSkippingShadowRoot(position);
const int caret_offset = position.ComputeEditingOffset();
if (layout_object.IsText()) {
return ComputeInlineBoxPositionForTextNode(
&ToLayoutText(layout_object), caret_offset, adjusted.Affinity(),
primary_direction);
}
DCHECK(layout_object.IsAtomicInlineLevel());
DCHECK(layout_object.IsInline());
return ComputeInlineBoxPositionForAtomicInline(&layout_object, caret_offset,
primary_direction);
}
template <typename Strategy>
InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(
const PositionWithAffinityTemplate<Strategy>& position) {
return ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(
position, PrimaryDirectionOf(*position.AnchorNode()));
}
template <typename Strategy>
InlineBoxPosition ComputeInlineBoxPositionTemplate(
const PositionWithAffinityTemplate<Strategy>& position,
TextDirection primary_direction) {
const PositionWithAffinityTemplate<Strategy> adjusted =
ComputeInlineAdjustedPosition(position);
if (adjusted.IsNull())
return InlineBoxPosition();
return ComputeInlineBoxPositionForInlineAdjustedPosition(adjusted,
primary_direction);
}
template <typename Strategy>
InlineBoxPosition ComputeInlineBoxPositionTemplate(
const PositionWithAffinityTemplate<Strategy>& position) {
return ComputeInlineBoxPositionTemplate<Strategy>(
position, PrimaryDirectionOf(*position.AnchorNode()));
}
} // namespace
// TODO(xiaochengh): Migrate current callers of ComputeInlineBoxPosition to
// ComputeInlineAdjustedPosition() instead.
InlineBoxPosition ComputeInlineBoxPosition(
const PositionWithAffinity& position) {
return ComputeInlineBoxPositionTemplate<EditingStrategy>(position);
}
InlineBoxPosition ComputeInlineBoxPosition(
const PositionInFlatTreeWithAffinity& position) {
return ComputeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(position);
}
InlineBoxPosition ComputeInlineBoxPosition(const VisiblePosition& position) {
DCHECK(position.IsValid()) << position;
return ComputeInlineBoxPosition(position.ToPositionWithAffinity());
}
InlineBoxPosition ComputeInlineBoxPosition(const PositionWithAffinity& position,
TextDirection primary_direction) {
return ComputeInlineBoxPositionTemplate<EditingStrategy>(position,
primary_direction);
}
InlineBoxPosition ComputeInlineBoxPosition(
const PositionInFlatTreeWithAffinity& position,
TextDirection primary_direction) {
return ComputeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(
position, primary_direction);
}
PositionWithAffinity ComputeInlineAdjustedPosition(
const PositionWithAffinity& position) {
return ComputeInlineAdjustedPositionAlgorithm(position);
}
PositionInFlatTreeWithAffinity ComputeInlineAdjustedPosition(
const PositionInFlatTreeWithAffinity& position) {
return ComputeInlineAdjustedPositionAlgorithm(position);
}
PositionWithAffinity ComputeInlineAdjustedPosition(
const VisiblePosition& position) {
DCHECK(position.IsValid()) << position;
return ComputeInlineAdjustedPositionAlgorithm(
position.ToPositionWithAffinity());
}
InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPosition(
const PositionWithAffinity& position) {
return ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(position);
}
InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPosition(
const PositionInFlatTreeWithAffinity& position) {
return ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(position);
}
InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPosition(
const PositionWithAffinity& position,
TextDirection primary_direction) {
return ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(
position, primary_direction);
}
InlineBoxPosition ComputeInlineBoxPositionForInlineAdjustedPosition(
const PositionInFlatTreeWithAffinity& position,
TextDirection primary_direction) {
return ComputeInlineBoxPositionForInlineAdjustedPositionAlgorithm(
position, primary_direction);
}
} // namespace blink