blob: 6bef0e6f0f6e00cb3cb235fed84451ddb878e93c [file] [log] [blame]
// 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 "third_party/blink/renderer/core/layout/ng/inline/ng_caret_rect.h"
#include "third_party/blink/renderer/core/editing/local_caret_rect.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_caret_position.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
namespace blink {
namespace {
PhysicalRect ComputeLocalCaretRectByBoxSide(const NGInlineCursor& cursor,
NGCaretPositionType position_type) {
const bool is_horizontal = cursor.Current().Style().IsHorizontalWritingMode();
NGInlineCursor line_box(cursor);
line_box.MoveToContainingLine();
DCHECK(line_box);
const PhysicalOffset offset_to_line_box =
cursor.Current().OffsetInContainerBlock() -
line_box.Current().OffsetInContainerBlock();
LayoutUnit caret_height = is_horizontal ? line_box.Current().Size().height
: line_box.Current().Size().width;
LayoutUnit caret_top =
is_horizontal ? -offset_to_line_box.top : -offset_to_line_box.left;
const LocalFrameView* frame_view =
cursor.Current().GetLayoutObject()->GetDocument().View();
LayoutUnit caret_width = frame_view->CaretWidth();
const bool is_ltr = IsLtr(cursor.Current().ResolvedDirection());
LayoutUnit caret_left;
if (is_ltr != (position_type == NGCaretPositionType::kBeforeBox)) {
if (is_horizontal)
caret_left = cursor.Current().Size().width - caret_width;
else
caret_left = cursor.Current().Size().height - caret_width;
}
if (!is_horizontal) {
std::swap(caret_top, caret_left);
std::swap(caret_width, caret_height);
}
const PhysicalOffset caret_location(caret_left, caret_top);
const PhysicalSize caret_size(caret_width, caret_height);
return PhysicalRect(caret_location, caret_size);
}
bool ShouldAlignCaretRight(ETextAlign text_align, TextDirection direction) {
switch (text_align) {
case ETextAlign::kRight:
case ETextAlign::kWebkitRight:
return true;
case ETextAlign::kLeft:
case ETextAlign::kWebkitLeft:
case ETextAlign::kCenter:
case ETextAlign::kWebkitCenter:
return false;
case ETextAlign::kJustify:
case ETextAlign::kStart:
return IsRtl(direction);
case ETextAlign::kEnd:
return IsLtr(direction);
}
NOTREACHED();
return false;
}
PhysicalRect ComputeLocalCaretRectAtTextOffset(const NGInlineCursor& cursor,
unsigned offset) {
DCHECK(cursor.Current().IsText());
DCHECK_GE(offset, cursor.Current().TextStartOffset());
DCHECK_LE(offset, cursor.Current().TextEndOffset());
const LocalFrameView* frame_view =
cursor.Current().GetLayoutObject()->GetDocument().View();
LayoutUnit caret_width = frame_view->CaretWidth();
const ComputedStyle& style = cursor.Current().Style();
const bool is_horizontal = style.IsHorizontalWritingMode();
LayoutUnit caret_height = is_horizontal ? cursor.Current().Size().height
: cursor.Current().Size().width;
LayoutUnit caret_top;
LayoutUnit caret_left = cursor.InlinePositionForOffset(offset);
if (!cursor.Current().IsLineBreak())
caret_left -= caret_width / 2;
if (!is_horizontal) {
std::swap(caret_top, caret_left);
std::swap(caret_width, caret_height);
}
// Adjust the location to be relative to the inline formatting context.
PhysicalOffset caret_location = PhysicalOffset(caret_left, caret_top) +
cursor.Current().OffsetInContainerBlock();
const PhysicalSize caret_size(caret_width, caret_height);
const NGPhysicalBoxFragment& fragmentainer =
*cursor.Current().GetLayoutObject()->ContainingBlockFlowFragment();
NGInlineCursor line_box(cursor);
line_box.MoveToContainingLine();
const PhysicalOffset line_box_offset =
line_box.Current().OffsetInContainerBlock();
const PhysicalRect line_box_rect(line_box_offset, line_box.Current().Size());
const NGInlineBreakToken& break_token =
*line_box.Current().InlineBreakToken();
const bool is_last_line =
break_token.IsFinished() || break_token.IsForcedBreak();
const ComputedStyle& block_style = fragmentainer.Style();
bool should_align_caret_right =
ShouldAlignCaretRight(block_style.GetTextAlign(is_last_line),
block_style.Direction()) &&
(style.GetUnicodeBidi() != UnicodeBidi::kPlaintext ||
IsLtr(cursor.Current().ResolvedDirection()));
// For horizontal text, adjust the location in the x direction to ensure that
// it completely falls in the union of line box and containing block, and
// then round it to the nearest pixel.
if (is_horizontal) {
if (should_align_caret_right) {
const LayoutUnit left_edge = std::min(LayoutUnit(), line_box_rect.X());
caret_location.left = std::max(caret_location.left, left_edge);
caret_location.left =
std::min(caret_location.left, line_box_rect.Right() - caret_width);
} else {
const LayoutUnit right_edge =
std::max(fragmentainer.Size().width, line_box_rect.Right());
caret_location.left =
std::min(caret_location.left, right_edge - caret_width);
caret_location.left = std::max(caret_location.left, line_box_rect.X());
}
caret_location.left = LayoutUnit(caret_location.left.Round());
return PhysicalRect(caret_location, caret_size);
}
// Similar adjustment and rounding for vertical text.
const LayoutUnit min_y = std::min(LayoutUnit(), line_box_offset.top);
caret_location.top = std::max(caret_location.top, min_y);
const LayoutUnit max_y =
std::max(fragmentainer.Size().height, line_box_rect.Bottom());
caret_location.top = std::min(caret_location.top, max_y - caret_height);
caret_location.top = LayoutUnit(caret_location.top.Round());
return PhysicalRect(caret_location, caret_size);
}
LocalCaretRect ComputeLocalCaretRect(const NGCaretPosition& caret_position) {
if (caret_position.IsNull())
return LocalCaretRect();
const LayoutObject* layout_object =
caret_position.cursor.Current().GetLayoutObject();
switch (caret_position.position_type) {
case NGCaretPositionType::kBeforeBox:
case NGCaretPositionType::kAfterBox: {
DCHECK(!caret_position.cursor.Current().IsText());
const PhysicalRect fragment_local_rect = ComputeLocalCaretRectByBoxSide(
caret_position.cursor, caret_position.position_type);
return {layout_object, fragment_local_rect};
}
case NGCaretPositionType::kAtTextOffset: {
DCHECK(caret_position.cursor.Current().IsText());
DCHECK(caret_position.text_offset.has_value());
const PhysicalRect caret_rect = ComputeLocalCaretRectAtTextOffset(
caret_position.cursor, *caret_position.text_offset);
return {layout_object, caret_rect};
}
}
NOTREACHED();
return {layout_object, PhysicalRect()};
}
LocalCaretRect ComputeLocalSelectionRect(
const NGCaretPosition& caret_position) {
const LocalCaretRect caret_rect = ComputeLocalCaretRect(caret_position);
if (!caret_rect.layout_object)
return caret_rect;
NGInlineCursor line_box(caret_position.cursor);
line_box.MoveToContainingLine();
// TODO(yosin): We'll hit this DCHECK for caret in empty block if we
// enable LayoutNG in contenteditable.
DCHECK(line_box);
PhysicalRect rect = caret_rect.rect;
if (caret_position.cursor.Current().Style().IsHorizontalWritingMode()) {
rect.SetY(line_box.Current().OffsetInContainerBlock().top);
rect.SetHeight(line_box.Current().Size().height);
} else {
rect.SetX(line_box.Current().OffsetInContainerBlock().left);
rect.SetHeight(line_box.Current().Size().width);
}
return {caret_rect.layout_object, rect};
}
} // namespace
LocalCaretRect ComputeNGLocalCaretRect(const PositionWithAffinity& position) {
return ComputeLocalCaretRect(ComputeNGCaretPosition(position));
}
LocalCaretRect ComputeNGLocalSelectionRect(
const PositionWithAffinity& position) {
return ComputeLocalSelectionRect(ComputeNGCaretPosition(position));
}
} // namespace blink