blob: 6d97f33572bc660c941ac737502191ae00093b15 [file] [log] [blame]
// 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 "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_text_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_ink_overflow.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
namespace blink {
namespace {
struct SameSizeAsNGPhysicalTextFragment : NGPhysicalFragment {
void* pointers[3];
unsigned offsets[2];
};
static_assert(sizeof(NGPhysicalTextFragment) ==
sizeof(SameSizeAsNGPhysicalTextFragment),
"NGPhysicalTextFragment should stay small");
} // anonymous namespace
NGPhysicalTextFragment::NGPhysicalTextFragment(
const NGPhysicalTextFragment& source,
unsigned start_offset,
unsigned end_offset,
scoped_refptr<const ShapeResultView> shape_result)
: NGPhysicalFragment(
source.GetMutableLayoutObject(),
source.StyleVariant(),
source.IsHorizontal()
? PhysicalSize{shape_result->SnappedWidth(), source.Size().height}
: PhysicalSize{source.Size().width, shape_result->SnappedWidth()},
kFragmentText,
source.TextType()),
text_(source.text_),
start_offset_(start_offset),
end_offset_(end_offset),
shape_result_(std::move(shape_result)) {
DCHECK_GE(start_offset_, source.StartOffset());
DCHECK_LE(end_offset_, source.EndOffset());
DCHECK(shape_result_ || IsFlowControl()) << *this;
is_generated_text_ = source.is_generated_text_;
ink_overflow_computed_ = false;
}
NGPhysicalTextFragment::NGPhysicalTextFragment(NGTextFragmentBuilder* builder)
: NGPhysicalFragment(builder, kFragmentText, builder->text_type_),
text_(builder->text_),
start_offset_(builder->start_offset_),
end_offset_(builder->end_offset_),
shape_result_(std::move(builder->shape_result_)) {
DCHECK(shape_result_ || IsFlowControl()) << *this;
is_generated_text_ = builder->IsGeneratedText();
ink_overflow_computed_ = false;
}
LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset(
unsigned offset,
LayoutUnit (*round_function)(float),
AdjustMidCluster adjust_mid_cluster) const {
return NGFragmentItem(*this).InlinePositionForOffset(
Text(), offset, round_function, adjust_mid_cluster);
}
// TODO(yosin): We should move |NGFragmentItem::InlinePositionForOffset" to
// "ng_fragment_item.cc"
// Compute the inline position from text offset, in logical coordinate relative
// to this fragment.
LayoutUnit NGFragmentItem::InlinePositionForOffset(
StringView text,
unsigned offset,
LayoutUnit (*round_function)(float),
AdjustMidCluster adjust_mid_cluster) const {
DCHECK_GE(offset, StartOffset());
DCHECK_LE(offset, EndOffset());
DCHECK_EQ(text.length(), TextLength());
offset -= StartOffset();
if (TextShapeResult()) {
// TODO(layout-dev): Move caret position out of ShapeResult and into a
// separate support class that can take a ShapeResult or ShapeResultView.
// Allows for better code separation and avoids the extra copy below.
return round_function(
TextShapeResult()->CreateShapeResult()->CaretPositionForOffset(
offset, text, adjust_mid_cluster));
}
// This fragment is a flow control because otherwise ShapeResult exists.
DCHECK(IsFlowControl());
DCHECK_EQ(1u, text.length());
if (!offset || UNLIKELY(IsRtl(Style().Direction())))
return LayoutUnit();
return IsHorizontal() ? Size().width : Size().height;
}
// TODO(yosin): We should move |NGFragmentItem::InlinePositionForOffset" to
// "ng_fragment_item.cc"
LayoutUnit NGFragmentItem::InlinePositionForOffset(StringView text,
unsigned offset) const {
return InlinePositionForOffset(text, offset, LayoutUnit::FromFloatRound,
AdjustMidCluster::kToEnd);
}
LayoutUnit NGPhysicalTextFragment::InlinePositionForOffset(
unsigned offset) const {
return NGFragmentItem(*this).InlinePositionForOffset(Text(), offset);
}
std::pair<LayoutUnit, LayoutUnit>
NGPhysicalTextFragment::LineLeftAndRightForOffsets(unsigned start_offset,
unsigned end_offset) const {
return NGFragmentItem(*this).LineLeftAndRightForOffsets(Text(), start_offset,
end_offset);
}
// TODO(yosin): We should move |NGFragmentItem::InlinePositionForOffset" to
// "ng_fragment_item.cc"
std::pair<LayoutUnit, LayoutUnit> NGFragmentItem::LineLeftAndRightForOffsets(
StringView text,
unsigned start_offset,
unsigned end_offset) const {
DCHECK_LE(start_offset, EndOffset());
DCHECK_GE(start_offset, StartOffset());
DCHECK_LE(end_offset, EndOffset());
const LayoutUnit start_position =
InlinePositionForOffset(text, start_offset, LayoutUnit::FromFloatFloor,
AdjustMidCluster::kToStart);
const LayoutUnit end_position = InlinePositionForOffset(
text, end_offset, LayoutUnit::FromFloatCeil, AdjustMidCluster::kToEnd);
// Swap positions if RTL.
return (UNLIKELY(start_position > end_position))
? std::make_pair(end_position, start_position)
: std::make_pair(start_position, end_position);
}
PhysicalRect NGPhysicalTextFragment::LocalRect(unsigned start_offset,
unsigned end_offset) const {
return NGFragmentItem(*this).LocalRect(Text(), start_offset, end_offset);
}
// TODO(yosin): We should move |NGFragmentItem::InlinePositionForOffset" to
// "ng_fragment_item.cc"
PhysicalRect NGFragmentItem::LocalRect(StringView text,
unsigned start_offset,
unsigned end_offset) const {
if (start_offset == StartOffset() && end_offset == EndOffset())
return LocalRect();
LayoutUnit start_position, end_position;
std::tie(start_position, end_position) =
LineLeftAndRightForOffsets(text, start_offset, end_offset);
const LayoutUnit inline_size = end_position - start_position;
switch (GetWritingMode()) {
case WritingMode::kHorizontalTb:
return {start_position, LayoutUnit(), inline_size, Size().height};
case WritingMode::kVerticalRl:
case WritingMode::kVerticalLr:
case WritingMode::kSidewaysRl:
return {LayoutUnit(), start_position, Size().width, inline_size};
case WritingMode::kSidewaysLr:
return {LayoutUnit(), Size().height - end_position, Size().width,
inline_size};
}
NOTREACHED();
return {};
}
PhysicalRect NGPhysicalTextFragment::SelfInkOverflow() const {
if (!ink_overflow_computed_)
ComputeSelfInkOverflow();
if (ink_overflow_)
return ink_overflow_->self_ink_overflow;
return LocalRect();
}
void NGPhysicalTextFragment::ComputeSelfInkOverflow() const {
ink_overflow_computed_ = true;
if (UNLIKELY(!shape_result_)) {
ink_overflow_ = nullptr;
return;
}
ink_overflow_ = NGInkOverflow::TextInkOverflow(PaintInfo(), Style(), Size());
}
scoped_refptr<const NGPhysicalTextFragment>
NGPhysicalTextFragment::CloneAsHiddenForPaint() const {
NGTextFragmentBuilder builder(*this);
builder.SetIsHiddenForPaint(true);
return builder.ToTextFragment();
}
scoped_refptr<const NGPhysicalTextFragment> NGPhysicalTextFragment::TrimText(
unsigned new_start_offset,
unsigned new_end_offset) const {
DCHECK(shape_result_);
DCHECK_GE(new_start_offset, StartOffset());
DCHECK_GT(new_end_offset, new_start_offset);
DCHECK_LE(new_end_offset, EndOffset());
scoped_refptr<ShapeResultView> new_shape_result = ShapeResultView::Create(
shape_result_.get(), new_start_offset, new_end_offset);
return base::AdoptRef(new NGPhysicalTextFragment(
*this, new_start_offset, new_end_offset, std::move(new_shape_result)));
}
unsigned NGPhysicalTextFragment::TextOffsetForPoint(
const PhysicalOffset& point) const {
const ComputedStyle& style = Style();
const LayoutUnit& point_in_line_direction =
style.IsHorizontalWritingMode() ? point.left : point.top;
if (const ShapeResultView* shape_result = TextShapeResult()) {
// TODO(layout-dev): Move caret logic out of ShapeResult into separate
// support class for code health and to avoid this copy.
return shape_result->CreateShapeResult()->CaretOffsetForHitTest(
point_in_line_direction.ToFloat(), Text(), BreakGlyphs) +
StartOffset();
}
// Flow control fragments such as forced line break, tabulation, soft-wrap
// opportunities, etc. do not have ShapeResult.
DCHECK(IsFlowControl());
// Zero-inline-size objects such as newline always return the start offset.
LogicalSize size = Size().ConvertToLogical(style.GetWritingMode());
if (!size.inline_size)
return StartOffset();
// Sized objects such as tabulation returns the next offset if the given point
// is on the right half.
LayoutUnit inline_offset = IsLtr(ResolvedDirection())
? point_in_line_direction
: size.inline_size - point_in_line_direction;
DCHECK_EQ(1u, Length());
return inline_offset <= size.inline_size / 2 ? StartOffset() : EndOffset();
}
UBiDiLevel NGPhysicalTextFragment::BidiLevel() const {
// TODO(xiaochengh): Make the implementation more efficient with, e.g.,
// binary search and/or LayoutNGText::InlineItems().
const auto& items = InlineItemsOfContainingBlock();
const NGInlineItem* containing_item = std::find_if(
items.begin(), items.end(), [this](const NGInlineItem& item) {
return item.StartOffset() <= StartOffset() &&
item.EndOffset() >= EndOffset();
});
DCHECK(containing_item);
DCHECK_NE(containing_item, items.end());
return containing_item->BidiLevel();
}
TextDirection NGPhysicalTextFragment::ResolvedDirection() const {
if (TextShapeResult())
return TextShapeResult()->Direction();
return DirectionFromLevel(BidiLevel());
}
} // namespace blink