blob: fc4dc4f0668df72a16c3ec8678d1bed5e02d7b8d [file] [log] [blame]
/*
* Copyright (C) 2011 Google 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT
* OWNER 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/RenderedPosition.h"
#include "core/editing/InlineBoxTraversal.h"
#include "core/editing/TextAffinity.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleUnits.h"
#include "core/html/TextControlElement.h"
#include "core/layout/api/LineLayoutAPIShim.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/compositing/CompositedSelectionBound.h"
namespace blink {
template <typename Strategy>
static inline LayoutObject* LayoutObjectFromPosition(
const PositionTemplate<Strategy>& position) {
DCHECK(position.IsNotNull());
Node* layout_object_node = nullptr;
switch (position.AnchorType()) {
case PositionAnchorType::kOffsetInAnchor:
layout_object_node = position.ComputeNodeAfterPosition();
if (!layout_object_node || !layout_object_node->GetLayoutObject())
layout_object_node = position.AnchorNode()->lastChild();
break;
case PositionAnchorType::kBeforeAnchor:
case PositionAnchorType::kAfterAnchor:
break;
case PositionAnchorType::kBeforeChildren:
layout_object_node = Strategy::FirstChild(*position.AnchorNode());
break;
case PositionAnchorType::kAfterChildren:
layout_object_node = Strategy::LastChild(*position.AnchorNode());
break;
}
if (!layout_object_node || !layout_object_node->GetLayoutObject())
layout_object_node = position.AnchorNode();
return layout_object_node->GetLayoutObject();
}
RenderedPosition::RenderedPosition(const VisiblePosition& position)
: RenderedPosition(position.DeepEquivalent(), position.Affinity()) {}
RenderedPosition::RenderedPosition(const VisiblePositionInFlatTree& position)
: RenderedPosition(position.DeepEquivalent(), position.Affinity()) {}
RenderedPosition::RenderedPosition(const Position& position,
TextAffinity affinity)
: layout_object_(nullptr),
inline_box_(nullptr),
offset_(0),
prev_leaf_child_(UncachedInlineBox()),
next_leaf_child_(UncachedInlineBox()) {
if (position.IsNull())
return;
InlineBoxPosition box_position = ComputeInlineBoxPosition(position, affinity);
inline_box_ = box_position.inline_box;
offset_ = box_position.offset_in_box;
if (inline_box_)
layout_object_ =
LineLayoutAPIShim::LayoutObjectFrom(inline_box_->GetLineLayoutItem());
else
layout_object_ = LayoutObjectFromPosition(position);
}
RenderedPosition::RenderedPosition(const PositionInFlatTree& position,
TextAffinity affinity)
: layout_object_(nullptr),
inline_box_(nullptr),
offset_(0),
prev_leaf_child_(UncachedInlineBox()),
next_leaf_child_(UncachedInlineBox()) {
if (position.IsNull())
return;
InlineBoxPosition box_position = ComputeInlineBoxPosition(position, affinity);
inline_box_ = box_position.inline_box;
offset_ = box_position.offset_in_box;
if (inline_box_)
layout_object_ =
LineLayoutAPIShim::LayoutObjectFrom(inline_box_->GetLineLayoutItem());
else
layout_object_ = LayoutObjectFromPosition(position);
}
InlineBox* RenderedPosition::PrevLeafChild() const {
if (prev_leaf_child_ == UncachedInlineBox())
prev_leaf_child_ = inline_box_->PrevLeafChildIgnoringLineBreak();
return prev_leaf_child_;
}
InlineBox* RenderedPosition::NextLeafChild() const {
if (next_leaf_child_ == UncachedInlineBox())
next_leaf_child_ = inline_box_->NextLeafChildIgnoringLineBreak();
return next_leaf_child_;
}
bool RenderedPosition::IsEquivalent(const RenderedPosition& other) const {
return (layout_object_ == other.layout_object_ &&
inline_box_ == other.inline_box_ && offset_ == other.offset_) ||
(AtLeftmostOffsetInBox() && other.AtRightmostOffsetInBox() &&
PrevLeafChild() == other.inline_box_) ||
(AtRightmostOffsetInBox() && other.AtLeftmostOffsetInBox() &&
NextLeafChild() == other.inline_box_);
}
unsigned char RenderedPosition::BidiLevelOnLeft() const {
InlineBox* box = AtLeftmostOffsetInBox() ? PrevLeafChild() : inline_box_;
return box ? box->BidiLevel() : 0;
}
unsigned char RenderedPosition::BidiLevelOnRight() const {
InlineBox* box = AtRightmostOffsetInBox() ? NextLeafChild() : inline_box_;
return box ? box->BidiLevel() : 0;
}
RenderedPosition RenderedPosition::LeftBoundaryOfBidiRun(
unsigned char bidi_level_of_run) {
if (!inline_box_ || bidi_level_of_run > inline_box_->BidiLevel())
return RenderedPosition();
InlineBox* const box =
InlineBoxTraversal::FindLeftBoundaryOfEntireBidiRunIgnoringLineBreak(
*inline_box_, bidi_level_of_run);
return RenderedPosition(
LineLayoutAPIShim::LayoutObjectFrom(box->GetLineLayoutItem()), box,
box->CaretLeftmostOffset());
}
RenderedPosition RenderedPosition::RightBoundaryOfBidiRun(
unsigned char bidi_level_of_run) {
if (!inline_box_ || bidi_level_of_run > inline_box_->BidiLevel())
return RenderedPosition();
InlineBox* const box =
InlineBoxTraversal::FindRightBoundaryOfEntireBidiRunIgnoringLineBreak(
*inline_box_, bidi_level_of_run);
return RenderedPosition(
LineLayoutAPIShim::LayoutObjectFrom(box->GetLineLayoutItem()), box,
box->CaretRightmostOffset());
}
bool RenderedPosition::AtLeftBoundaryOfBidiRun(
ShouldMatchBidiLevel should_match_bidi_level,
unsigned char bidi_level_of_run) const {
if (!inline_box_)
return false;
if (AtLeftmostOffsetInBox()) {
if (should_match_bidi_level == kIgnoreBidiLevel)
return !PrevLeafChild() ||
PrevLeafChild()->BidiLevel() < inline_box_->BidiLevel();
return inline_box_->BidiLevel() >= bidi_level_of_run &&
(!PrevLeafChild() ||
PrevLeafChild()->BidiLevel() < bidi_level_of_run);
}
if (AtRightmostOffsetInBox()) {
if (should_match_bidi_level == kIgnoreBidiLevel)
return NextLeafChild() &&
inline_box_->BidiLevel() < NextLeafChild()->BidiLevel();
return NextLeafChild() && inline_box_->BidiLevel() < bidi_level_of_run &&
NextLeafChild()->BidiLevel() >= bidi_level_of_run;
}
return false;
}
bool RenderedPosition::AtRightBoundaryOfBidiRun(
ShouldMatchBidiLevel should_match_bidi_level,
unsigned char bidi_level_of_run) const {
if (!inline_box_)
return false;
if (AtRightmostOffsetInBox()) {
if (should_match_bidi_level == kIgnoreBidiLevel)
return !NextLeafChild() ||
NextLeafChild()->BidiLevel() < inline_box_->BidiLevel();
return inline_box_->BidiLevel() >= bidi_level_of_run &&
(!NextLeafChild() ||
NextLeafChild()->BidiLevel() < bidi_level_of_run);
}
if (AtLeftmostOffsetInBox()) {
if (should_match_bidi_level == kIgnoreBidiLevel)
return PrevLeafChild() &&
inline_box_->BidiLevel() < PrevLeafChild()->BidiLevel();
return PrevLeafChild() && inline_box_->BidiLevel() < bidi_level_of_run &&
PrevLeafChild()->BidiLevel() >= bidi_level_of_run;
}
return false;
}
Position RenderedPosition::PositionAtLeftBoundaryOfBiDiRun() const {
DCHECK(AtLeftBoundaryOfBidiRun());
if (AtLeftmostOffsetInBox())
return Position::EditingPositionOf(layout_object_->GetNode(), offset_);
return Position::EditingPositionOf(
NextLeafChild()->GetLineLayoutItem().GetNode(),
NextLeafChild()->CaretLeftmostOffset());
}
Position RenderedPosition::PositionAtRightBoundaryOfBiDiRun() const {
DCHECK(AtRightBoundaryOfBidiRun());
if (AtRightmostOffsetInBox())
return Position::EditingPositionOf(layout_object_->GetNode(), offset_);
return Position::EditingPositionOf(
PrevLeafChild()->GetLineLayoutItem().GetNode(),
PrevLeafChild()->CaretRightmostOffset());
}
IntRect RenderedPosition::AbsoluteRect(
LayoutUnit* extra_width_to_end_of_line) const {
if (IsNull())
return IntRect();
IntRect local_rect = PixelSnappedIntRect(layout_object_->LocalCaretRect(
inline_box_, offset_, extra_width_to_end_of_line));
return local_rect == IntRect()
? IntRect()
: layout_object_->LocalToAbsoluteQuad(FloatRect(local_rect))
.EnclosingBoundingBox();
}
// Convert a local point into the coordinate system of backing coordinates.
// Also returns the backing layer if needed.
FloatPoint RenderedPosition::LocalToInvalidationBackingPoint(
const LayoutPoint& local_point,
GraphicsLayer** graphics_layer_backing) const {
const LayoutBoxModelObject& paint_invalidation_container =
layout_object_->ContainerForPaintInvalidation();
DCHECK(paint_invalidation_container.Layer());
FloatPoint container_point = layout_object_->LocalToAncestorPoint(
FloatPoint(local_point), &paint_invalidation_container,
kTraverseDocumentBoundaries);
// A layoutObject can have no invalidation backing if it is from a detached
// frame, or when forced compositing is disabled.
if (paint_invalidation_container.Layer()->GetCompositingState() ==
kNotComposited)
return container_point;
PaintLayer::MapPointInPaintInvalidationContainerToBacking(
paint_invalidation_container, container_point);
// Must not use the scrolling contents layer, so pass
// |paintInvalidationContainer|.
if (GraphicsLayer* graphics_layer =
paint_invalidation_container.Layer()->GraphicsLayerBacking(
&paint_invalidation_container)) {
if (graphics_layer_backing)
*graphics_layer_backing = graphics_layer;
container_point.Move(-graphics_layer->OffsetFromLayoutObject());
}
return container_point;
}
void RenderedPosition::GetLocalSelectionEndpoints(
bool selection_start,
LayoutPoint& edge_top_in_layer,
LayoutPoint& edge_bottom_in_layer,
bool& is_text_direction_rtl) const {
const LayoutRect rect = layout_object_->LocalCaretRect(inline_box_, offset_);
if (layout_object_->Style()->IsHorizontalWritingMode()) {
edge_top_in_layer = rect.MinXMinYCorner();
edge_bottom_in_layer = rect.MinXMaxYCorner();
return;
}
edge_top_in_layer = rect.MinXMinYCorner();
edge_bottom_in_layer = rect.MaxXMinYCorner();
// When text is vertical, it looks better for the start handle baseline to
// be at the starting edge, to enclose the selection fully between the
// handles.
if (selection_start) {
LayoutUnit x_swap = edge_bottom_in_layer.X();
edge_bottom_in_layer.SetX(edge_top_in_layer.X());
edge_top_in_layer.SetX(x_swap);
}
// Flipped blocks writing mode is not only vertical but also right to left.
is_text_direction_rtl = layout_object_->HasFlippedBlocksWritingMode();
}
void RenderedPosition::PositionInGraphicsLayerBacking(
CompositedSelectionBound& bound,
bool selection_start) const {
bound.layer = nullptr;
bound.edge_top_in_layer = bound.edge_bottom_in_layer = FloatPoint();
if (IsNull())
return;
LayoutPoint edge_top_in_layer;
LayoutPoint edge_bottom_in_layer;
GetLocalSelectionEndpoints(selection_start, edge_top_in_layer,
edge_bottom_in_layer, bound.is_text_direction_rtl);
bound.edge_top_in_layer =
LocalToInvalidationBackingPoint(edge_top_in_layer, &bound.layer);
bound.edge_bottom_in_layer =
LocalToInvalidationBackingPoint(edge_bottom_in_layer, nullptr);
}
LayoutPoint RenderedPosition::GetSamplePointForVisibility(
const LayoutPoint& edge_top_in_layer,
const LayoutPoint& edge_bottom_in_layer) {
FloatSize diff(edge_top_in_layer - edge_bottom_in_layer);
// Adjust by ~1px to avoid integer snapping error. This logic is the same
// as that in ComputeViewportSelectionBound in cc.
diff.Scale(1 / diff.DiagonalLength());
LayoutPoint sample_point = edge_bottom_in_layer;
sample_point.Move(LayoutSize(diff));
return sample_point;
}
bool RenderedPosition::IsVisible(bool selection_start) {
if (IsNull())
return false;
Node* node = layout_object_->GetNode();
if (!node)
return true;
TextControlElement* text_control = EnclosingTextControl(node);
if (!text_control)
return true;
if (!isHTMLInputElement(text_control))
return true;
LayoutObject* layout_object = text_control->GetLayoutObject();
if (!layout_object || !layout_object->IsBox())
return true;
LayoutPoint edge_top_in_layer;
LayoutPoint edge_bottom_in_layer;
bool ignored2;
GetLocalSelectionEndpoints(selection_start, edge_top_in_layer,
edge_bottom_in_layer, ignored2);
LayoutPoint sample_point =
GetSamplePointForVisibility(edge_top_in_layer, edge_bottom_in_layer);
LayoutBox* text_control_object = ToLayoutBox(layout_object);
LayoutPoint position_in_input(layout_object_->LocalToAncestorPoint(
FloatPoint(sample_point), text_control_object,
kTraverseDocumentBoundaries));
if (!text_control_object->BorderBoxRect().Contains(position_in_input))
return false;
return true;
}
bool LayoutObjectContainsPosition(LayoutObject* target,
const Position& position) {
for (LayoutObject* layout_object = LayoutObjectFromPosition(position);
layout_object && layout_object->GetNode();
layout_object = layout_object->Parent()) {
if (layout_object == target)
return true;
}
return false;
}
} // namespace blink