blob: 6385f7506558addce320fbc4a835f7e0460cb146 [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 "core/layout/ng/inline/ng_offset_mapping_builder.h"
#include "core/layout/LayoutText.h"
#include "core/layout/LayoutTextFragment.h"
#include "core/layout/ng/inline/ng_offset_mapping.h"
namespace blink {
namespace {
// Returns the type of a unit-length simple offset mapping.
NGOffsetMappingUnitType GetUnitLengthMappingType(unsigned value) {
if (value == 0u)
return NGOffsetMappingUnitType::kCollapsed;
if (value == 1u)
return NGOffsetMappingUnitType::kIdentity;
return NGOffsetMappingUnitType::kExpanded;
}
// Returns the associated node of a possibly null LayoutObject.
const Node* GetAssociatedNode(const LayoutObject* layout_object) {
if (!layout_object)
return nullptr;
if (!layout_object->IsText() ||
!ToLayoutText(layout_object)->IsTextFragment())
return layout_object->NonPseudoNode();
const LayoutTextFragment* fragment = ToLayoutTextFragment(layout_object);
return fragment->AssociatedTextNode();
}
// Finds the offset mapping unit starting from index |start|.
std::pair<NGOffsetMappingUnitType, unsigned> GetMappingUnitTypeAndEnd(
const Vector<unsigned>& mapping,
const Vector<const LayoutObject*>& annotation,
unsigned start) {
DCHECK_LT(start + 1, mapping.size());
NGOffsetMappingUnitType type =
GetUnitLengthMappingType(mapping[start + 1] - mapping[start]);
if (type == NGOffsetMappingUnitType::kExpanded)
return std::make_pair(type, start + 1);
unsigned end = start + 1;
for (; end + 1 < mapping.size(); ++end) {
if (annotation[end] != annotation[start])
break;
NGOffsetMappingUnitType next_type =
GetUnitLengthMappingType(mapping[end + 1] - mapping[end]);
if (next_type != type)
break;
}
return std::make_pair(type, end);
}
// If |layout_object| is the remaining text of a text node, returns the start
// offset; In all other cases, returns 0.
unsigned GetRemainingTextOffset(const LayoutObject* layout_object) {
if (!layout_object || !layout_object->IsText())
return 0;
return ToLayoutText(layout_object)->TextStartOffset();
}
} // namespace
NGOffsetMappingBuilder::NGOffsetMappingBuilder() {
mapping_.push_back(0);
}
NGOffsetMappingBuilder::SourceNodeScope::SourceNodeScope(
NGOffsetMappingBuilder* builder,
const LayoutObject* node)
: auto_reset_(&builder->current_source_node_, node) {
#if DCHECK_IS_ON()
builder_ = builder;
if (!node)
return;
// We allow at most one scope with non-null node at any time.
DCHECK(!builder->has_nonnull_node_scope_);
builder->has_nonnull_node_scope_ = true;
#endif
}
NGOffsetMappingBuilder::SourceNodeScope::~SourceNodeScope() {
#if DCHECK_IS_ON()
if (builder_->current_source_node_)
builder_->has_nonnull_node_scope_ = false;
#endif
}
void NGOffsetMappingBuilder::AppendIdentityMapping(unsigned length) {
DCHECK_GT(length, 0u);
DCHECK(!mapping_.IsEmpty());
for (unsigned i = 0; i < length; ++i) {
unsigned next = mapping_.back() + 1;
mapping_.push_back(next);
}
annotation_.resize(annotation_.size() + length);
std::fill(annotation_.end() - length, annotation_.end(),
current_source_node_);
}
void NGOffsetMappingBuilder::AppendCollapsedMapping(unsigned length) {
DCHECK_GT(length, 0u);
DCHECK(!mapping_.IsEmpty());
const unsigned back = mapping_.back();
for (unsigned i = 0; i < length; ++i)
mapping_.push_back(back);
annotation_.resize(annotation_.size() + length);
std::fill(annotation_.end() - length, annotation_.end(),
current_source_node_);
}
void NGOffsetMappingBuilder::CollapseTrailingSpace(unsigned skip_length) {
DCHECK(!mapping_.IsEmpty());
// Find the |skipped_count + 1|-st last uncollapsed character. By collapsing
// it, all mapping values beyond this position are decremented by 1.
unsigned skipped_count = 0;
for (unsigned i = mapping_.size() - 1; skipped_count <= skip_length; --i) {
DCHECK_GT(i, 0u);
if (mapping_[i] != mapping_[i - 1])
++skipped_count;
--mapping_[i];
}
}
void NGOffsetMappingBuilder::Concatenate(const NGOffsetMappingBuilder& other) {
DCHECK(!mapping_.IsEmpty());
DCHECK(!other.mapping_.IsEmpty());
const unsigned shift_amount = mapping_.back();
for (unsigned i = 1; i < other.mapping_.size(); ++i)
mapping_.push_back(other.mapping_[i] + shift_amount);
annotation_.AppendVector(other.annotation_);
}
void NGOffsetMappingBuilder::Composite(const NGOffsetMappingBuilder& other) {
DCHECK(!mapping_.IsEmpty());
DCHECK_EQ(mapping_.back() + 1, other.mapping_.size());
for (unsigned i = 0; i < mapping_.size(); ++i)
mapping_[i] = other.mapping_[mapping_[i]];
}
void NGOffsetMappingBuilder::SetDestinationString(String string) {
DCHECK_EQ(mapping_.back(), string.length());
destination_string_ = string;
}
NGOffsetMapping NGOffsetMappingBuilder::Build() {
NGOffsetMapping::UnitVector units;
NGOffsetMapping::RangeMap ranges;
// Information of current owner node
const Node* nonnull_owner = nullptr;
unsigned processed_length = 0;
unsigned unit_range_start = 0;
unsigned remaining_text_offset = 0;
// Information of non-atomic inlines that we are currently in
using NodeAndStart = std::pair<const Node*, unsigned>;
Vector<NodeAndStart> current_inlines;
// Sentinels
annotation_.push_back(nullptr);
boundaries_.push_back(InlineBoundary({nullptr, mapping_.size() - 1, false}));
const InlineBoundary* boundary_it = boundaries_.begin();
unsigned unit_start = 0;
while (unit_start + 1 < mapping_.size() ||
std::next(boundary_it) != boundaries_.end()) {
// Handle boundaries of non-atomic inline nodes
if (std::next(boundary_it) != boundaries_.end() &&
boundary_it->offset <= unit_start) {
const InlineBoundary& boundary = *boundary_it++;
const Node* node = GetAssociatedNode(boundary.node);
// Skip generated content
if (!node)
continue;
// Enter non-atomic inline
if (boundary.is_enter) {
current_inlines.push_back(NodeAndStart({node, units.size()}));
continue;
}
// Exit non-atomic inline
DCHECK(current_inlines.size());
DCHECK_EQ(node, current_inlines.back().first);
const unsigned node_unit_range_start = current_inlines.back().second;
if (units.size() > node_unit_range_start) {
ranges.insert(node,
std::make_pair(node_unit_range_start, units.size()));
}
current_inlines.pop_back();
continue;
}
// Create mapping units and/or unit ranges, if any
DCHECK_LT(unit_start, annotation_.size());
const Node* node = GetAssociatedNode(annotation_[unit_start]);
const auto type_and_end =
GetMappingUnitTypeAndEnd(mapping_, annotation_, unit_start);
unsigned unit_end = type_and_end.second;
// Create unit only for non-generated content.
if (node) {
if (node != nonnull_owner) {
nonnull_owner = node;
processed_length = 0;
}
if (!unit_start ||
node != GetAssociatedNode(annotation_[unit_start - 1])) {
unit_range_start = units.size();
// We get a non-zero |remaining_text_offset| only when |current_node| is
// a text node that has blockified ::first-letter style, and we are at
// the remaining text of |current_node|.
remaining_text_offset = GetRemainingTextOffset(annotation_[unit_start]);
}
NGOffsetMappingUnitType type = type_and_end.first;
unsigned dom_start = processed_length + remaining_text_offset;
unsigned dom_end = unit_end - unit_start + dom_start;
unsigned text_content_start = mapping_[unit_start];
unsigned text_content_end = mapping_[unit_end];
units.emplace_back(type, *node, dom_start, dom_end, text_content_start,
text_content_end);
processed_length += dom_end - dom_start;
if (GetAssociatedNode(annotation_[unit_end]) != node) {
auto iter = ranges.find(node);
if (iter != ranges.end()) {
// We are here if characters of |node| were appended inconsecutively,
// separated by generated characters. An example is BiDi-overridden
// text node with 'white-space: pre' style, where generated BiDi-
// control characters are inserted around '\n' characters.
DCHECK_EQ(iter->value.second, unit_range_start);
iter->value.second = units.size();
} else {
ranges.insert(node, std::make_pair(unit_range_start, units.size()));
}
}
}
unit_start = unit_end;
}
return NGOffsetMapping(std::move(units), std::move(ranges),
destination_string_);
}
void NGOffsetMappingBuilder::EnterInline(const LayoutObject& node) {
boundaries_.push_back(InlineBoundary({&node, mapping_.size() - 1, true}));
}
void NGOffsetMappingBuilder::ExitInline(const LayoutObject& node) {
boundaries_.push_back(InlineBoundary({&node, mapping_.size() - 1, false}));
}
Vector<unsigned> NGOffsetMappingBuilder::DumpOffsetMappingForTesting() const {
return mapping_;
}
Vector<const LayoutObject*> NGOffsetMappingBuilder::DumpAnnotationForTesting()
const {
return annotation_;
}
} // namespace blink