blob: cf430a648e4b751c16f8ee9fc45b14161913bdc8 [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_abstract_inline_text_box.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
#include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h"
#include "third_party/blink/renderer/platform/fonts/character_range.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
namespace blink {
NGAbstractInlineTextBox::FragmentToNGAbstractInlineTextBoxHashMap*
NGAbstractInlineTextBox::g_abstract_inline_text_box_map_ = nullptr;
scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::GetOrCreate(
const NGPaintFragment& fragment) {
DCHECK(fragment.GetLayoutObject()->IsText()) << fragment.GetLayoutObject();
if (!g_abstract_inline_text_box_map_) {
g_abstract_inline_text_box_map_ =
new FragmentToNGAbstractInlineTextBoxHashMap();
}
const auto it = g_abstract_inline_text_box_map_->find(&fragment);
LayoutText* const layout_text =
ToLayoutText(fragment.GetMutableLayoutObject());
if (it != g_abstract_inline_text_box_map_->end()) {
CHECK(layout_text->HasAbstractInlineTextBox());
return it->value;
}
scoped_refptr<AbstractInlineTextBox> obj = base::AdoptRef(
new NGAbstractInlineTextBox(LineLayoutText(layout_text), fragment));
g_abstract_inline_text_box_map_->Set(&fragment, obj);
layout_text->SetHasAbstractInlineTextBox();
return obj;
}
void NGAbstractInlineTextBox::WillDestroy(NGPaintFragment* fragment) {
if (!g_abstract_inline_text_box_map_)
return;
const auto it = g_abstract_inline_text_box_map_->find(fragment);
if (it != g_abstract_inline_text_box_map_->end()) {
it->value->Detach();
g_abstract_inline_text_box_map_->erase(fragment);
}
}
NGAbstractInlineTextBox::NGAbstractInlineTextBox(
LineLayoutText line_layout_item,
const NGPaintFragment& fragment)
: AbstractInlineTextBox(line_layout_item), fragment_(&fragment) {
DCHECK(fragment_->PhysicalFragment().IsText()) << fragment_;
}
NGAbstractInlineTextBox::~NGAbstractInlineTextBox() {
DCHECK(!fragment_);
}
void NGAbstractInlineTextBox::Detach() {
if (Node* const node = GetNode()) {
if (AXObjectCache* cache = node->GetDocument().ExistingAXObjectCache())
cache->InlineTextBoxesUpdated(GetLineLayoutItem());
}
AbstractInlineTextBox::Detach();
fragment_ = nullptr;
}
const NGPhysicalTextFragment& NGAbstractInlineTextBox::PhysicalTextFragment()
const {
return To<NGPhysicalTextFragment>(fragment_->PhysicalFragment());
}
bool NGAbstractInlineTextBox::NeedsLayout() const {
return fragment_->GetLayoutObject()->NeedsLayout();
}
bool NGAbstractInlineTextBox::NeedsTrailingSpace() const {
if (!fragment_->Style().CollapseWhiteSpace())
return false;
const NGPaintFragment& line_box = *fragment_->ContainerLineBox();
if (!To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment())
.HasSoftWrapToNextLine())
return false;
const NGPhysicalTextFragment& text_fragment = PhysicalTextFragment();
if (text_fragment.EndOffset() >= text_fragment.TextContent().length())
return false;
if (text_fragment.TextContent()[text_fragment.EndOffset()] != ' ')
return false;
const NGInlineBreakToken& break_token = *To<NGInlineBreakToken>(
To<NGPhysicalLineBoxFragment>(line_box.PhysicalFragment()).BreakToken());
// TODO(yosin): We should support OOF fragments between |fragment_| and
// break token.
if (break_token.TextOffset() != text_fragment.EndOffset() + 1)
return false;
// Check a character in text content after |fragment_| comes from same
// layout text of |fragment_|.
const NGOffsetMapping* mapping =
NGOffsetMapping::GetFor(fragment_->GetLayoutObject());
// TODO(kojii): There's not much we can do for dirty-tree. crbug.com/946004
if (!mapping)
return false;
const base::span<const NGOffsetMappingUnit> mapping_units =
mapping->GetMappingUnitsForTextContentOffsetRange(
text_fragment.EndOffset(), text_fragment.EndOffset() + 1);
if (mapping_units.begin() == mapping_units.end())
return false;
const NGOffsetMappingUnit& mapping_unit = mapping_units.front();
return mapping_unit.GetLayoutObject() == fragment_->GetLayoutObject();
}
const NGPaintFragment*
NGAbstractInlineTextBox::NextTextFragmentForSameLayoutObject() const {
const auto fragments =
NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject());
const auto it =
std::find_if(fragments.begin(), fragments.end(),
[&](const auto& sibling) { return fragment_ == sibling; });
DCHECK(it != fragments.end());
const auto next_it = std::next(it);
return next_it == fragments.end() ? nullptr : *next_it;
}
scoped_refptr<AbstractInlineTextBox>
NGAbstractInlineTextBox::NextInlineTextBox() const {
if (!fragment_)
return nullptr;
DCHECK(!NeedsLayout());
const NGPaintFragment* next_fragment = NextTextFragmentForSameLayoutObject();
if (!next_fragment)
return nullptr;
return GetOrCreate(*next_fragment);
}
LayoutRect NGAbstractInlineTextBox::LocalBounds() const {
if (!fragment_ || !GetLineLayoutItem())
return LayoutRect();
return LayoutRect(fragment_->InlineOffsetToContainerBox().ToLayoutPoint(),
fragment_->Size().ToLayoutSize());
}
unsigned NGAbstractInlineTextBox::Len() const {
if (!fragment_)
return 0;
if (NeedsTrailingSpace())
return PhysicalTextFragment().Length() + 1;
return PhysicalTextFragment().Length();
}
AbstractInlineTextBox::Direction NGAbstractInlineTextBox::GetDirection() const {
if (!fragment_ || !GetLineLayoutItem())
return kLeftToRight;
const TextDirection text_direction =
PhysicalTextFragment().ResolvedDirection();
if (GetLineLayoutItem().Style()->IsHorizontalWritingMode())
return IsLtr(text_direction) ? kLeftToRight : kRightToLeft;
return IsLtr(text_direction) ? kTopToBottom : kBottomToTop;
}
void NGAbstractInlineTextBox::CharacterWidths(Vector<float>& widths) const {
if (!fragment_)
return;
if (!PhysicalTextFragment().TextShapeResult()) {
// When |fragment_| for BR, we don't have shape result.
// "aom-computed-boolean-properties.html" reaches here.
widths.resize(Len());
return;
}
// TODO(layout-dev): Add support for IndividualCharacterRanges to
// ShapeResultView to avoid the copy below.
auto shape_result =
PhysicalTextFragment().TextShapeResult()->CreateShapeResult();
Vector<CharacterRange> ranges;
shape_result->IndividualCharacterRanges(&ranges);
widths.ReserveCapacity(ranges.size());
widths.resize(0);
for (const auto& range : ranges)
widths.push_back(range.Width());
// The shaper can fail to return glyph metrics for all characters (see
// crbug.com/613915 and crbug.com/615661) so add empty ranges to ensure all
// characters have an associated range.
widths.resize(Len());
}
String NGAbstractInlineTextBox::GetText() const {
if (!fragment_ || !GetLineLayoutItem())
return String();
String result = PhysicalTextFragment().Text().ToString();
// For compatibility with |InlineTextBox|, we should have a space character
// for soft line break.
// Following tests require this:
// - accessibility/inline-text-change-style.html
// - accessibility/inline-text-changes.html
// - accessibility/inline-text-word-boundaries.html
if (NeedsTrailingSpace())
result = result + " ";
// When the CSS first-letter pseudoselector is used, the LayoutText for the
// first letter is excluded from the accessibility tree, so we need to prepend
// its text here.
if (LayoutText* first_letter = GetFirstLetterPseudoLayoutText())
result = first_letter->GetText().SimplifyWhiteSpace() + result;
return result;
}
bool NGAbstractInlineTextBox::IsFirst() const {
if (!fragment_)
return true;
DCHECK(!NeedsLayout());
const auto fragments =
NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject());
return fragment_ == &fragments.front();
}
bool NGAbstractInlineTextBox::IsLast() const {
if (!fragment_)
return true;
DCHECK(!NeedsLayout());
const auto fragments =
NGPaintFragment::InlineFragmentsFor(fragment_->GetLayoutObject());
return fragment_ == &fragments.back();
}
scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::NextOnLine()
const {
if (!fragment_)
return nullptr;
DCHECK(!NeedsLayout());
DCHECK(fragment_->ContainerLineBox());
NGPaintFragmentTraversal cursor(*fragment_->ContainerLineBox(), *fragment_);
for (cursor.MoveToNext(); !cursor.IsAtEnd(); cursor.MoveToNext()) {
if (cursor->GetLayoutObject()->IsText())
return GetOrCreate(*cursor);
}
return nullptr;
}
scoped_refptr<AbstractInlineTextBox> NGAbstractInlineTextBox::PreviousOnLine()
const {
if (!fragment_)
return nullptr;
DCHECK(!NeedsLayout());
DCHECK(fragment_->ContainerLineBox());
NGPaintFragmentTraversal cursor(*fragment_->ContainerLineBox(), *fragment_);
for (cursor.MoveToPrevious(); !cursor.IsAtEnd(); cursor.MoveToPrevious()) {
if (cursor->GetLayoutObject()->IsText())
return GetOrCreate(*cursor);
}
return nullptr;
}
bool NGAbstractInlineTextBox::IsLineBreak() const {
if (!fragment_)
return false;
DCHECK(!NeedsLayout());
return PhysicalTextFragment().IsLineBreak();
}
} // namespace blink