blob: f80da9ada7049f9508b48e7511a3f2c4367095d1 [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_offset_mapping_builder.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
namespace blink {
namespace {
// Returns the associated non-pseudo node from |layout_object|, handling
// first-letter text fragments.
const Node* GetAssociatedNode(const LayoutObject* layout_object) {
if (!layout_object)
return nullptr;
if (const auto* text_fragment = ToLayoutTextFragmentOrNull(layout_object))
return text_fragment->AssociatedTextNode();
return layout_object->GetNode();
}
// Returns 0 unless |layout_object| is the remaining text of a node styled with
// ::first-letter, in which case it returns the start offset of the remaining
// text.
unsigned GetAssociatedStartOffset(const LayoutObject* layout_object) {
if (const auto* text_fragment = ToLayoutTextFragmentOrNull(layout_object))
return text_fragment->Start();
return 0;
}
} // namespace
NGOffsetMappingBuilder::NGOffsetMappingBuilder() = default;
NGOffsetMappingBuilder::SourceNodeScope::SourceNodeScope(
NGOffsetMappingBuilder* builder,
const LayoutObject* node)
: builder_(builder),
layout_object_auto_reset_(&builder->current_node_,
GetAssociatedNode(node)),
appended_length_auto_reset_(&builder->current_offset_,
GetAssociatedStartOffset(node)) {
builder_->has_open_unit_ = false;
#if DCHECK_IS_ON()
if (!builder_->current_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() {
builder_->has_open_unit_ = false;
#if DCHECK_IS_ON()
if (builder_->current_node_)
builder_->has_nonnull_node_scope_ = false;
#endif
}
void NGOffsetMappingBuilder::ReserveCapacity(unsigned capacity) {
unit_ranges_.ReserveCapacityForSize(capacity);
mapping_units_.ReserveCapacity(capacity * 1.5);
}
void NGOffsetMappingBuilder::AppendIdentityMapping(unsigned length) {
DCHECK_GT(length, 0u);
const unsigned dom_start = current_offset_;
const unsigned dom_end = dom_start + length;
const unsigned text_content_start = destination_length_;
const unsigned text_content_end = text_content_start + length;
current_offset_ += length;
destination_length_ += length;
if (!current_node_)
return;
if (has_open_unit_ &&
mapping_units_.back().GetType() == NGOffsetMappingUnitType::kIdentity) {
DCHECK_EQ(mapping_units_.back().GetOwner(), current_node_);
DCHECK_EQ(mapping_units_.back().DOMEnd(), dom_start);
mapping_units_.back().dom_end_ += length;
mapping_units_.back().text_content_end_ += length;
return;
}
mapping_units_.emplace_back(NGOffsetMappingUnitType::kIdentity,
*current_node_, dom_start, dom_end,
text_content_start, text_content_end);
has_open_unit_ = true;
}
void NGOffsetMappingBuilder::AppendCollapsedMapping(unsigned length) {
DCHECK_GT(length, 0u);
const unsigned dom_start = current_offset_;
const unsigned dom_end = dom_start + length;
const unsigned text_content_start = destination_length_;
const unsigned text_content_end = text_content_start;
current_offset_ += length;
if (!current_node_)
return;
if (has_open_unit_ &&
mapping_units_.back().GetType() == NGOffsetMappingUnitType::kCollapsed) {
DCHECK_EQ(mapping_units_.back().GetOwner(), current_node_);
DCHECK_EQ(mapping_units_.back().DOMEnd(), dom_start);
mapping_units_.back().dom_end_ += length;
return;
}
mapping_units_.emplace_back(NGOffsetMappingUnitType::kCollapsed,
*current_node_, dom_start, dom_end,
text_content_start, text_content_end);
has_open_unit_ = true;
}
void NGOffsetMappingBuilder::CollapseTrailingSpace(unsigned space_offset) {
DCHECK_LT(space_offset, destination_length_);
--destination_length_;
NGOffsetMappingUnit* container_unit = nullptr;
for (unsigned i = mapping_units_.size(); i;) {
NGOffsetMappingUnit& unit = mapping_units_[--i];
if (unit.TextContentStart() > space_offset) {
--unit.text_content_start_;
--unit.text_content_end_;
continue;
}
container_unit = &unit;
break;
}
if (!container_unit || container_unit->TextContentEnd() <= space_offset)
return;
// container_unit->TextContentStart()
// <= space_offset <
// container_unit->TextContentEnd()
DCHECK_EQ(NGOffsetMappingUnitType::kIdentity, container_unit->GetType());
const Node& node = container_unit->GetOwner();
unsigned dom_offset = container_unit->DOMStart();
unsigned text_content_offset = container_unit->TextContentStart();
unsigned offset_to_collapse = space_offset - text_content_offset;
Vector<NGOffsetMappingUnit, 3> new_units;
if (offset_to_collapse) {
new_units.emplace_back(NGOffsetMappingUnitType::kIdentity, node, dom_offset,
dom_offset + offset_to_collapse, text_content_offset,
text_content_offset + offset_to_collapse);
dom_offset += offset_to_collapse;
text_content_offset += offset_to_collapse;
}
new_units.emplace_back(NGOffsetMappingUnitType::kCollapsed, node, dom_offset,
dom_offset + 1, text_content_offset,
text_content_offset);
++dom_offset;
if (dom_offset < container_unit->DOMEnd()) {
new_units.emplace_back(NGOffsetMappingUnitType::kIdentity, node, dom_offset,
container_unit->DOMEnd(), text_content_offset,
container_unit->TextContentEnd() - 1);
}
// TODO(xiaochengh): Optimize if this becomes performance bottleneck.
unsigned position = std::distance(mapping_units_.begin(), container_unit);
mapping_units_.EraseAt(position);
mapping_units_.InsertVector(position, new_units);
ShiftRanges(position, new_units.size() - 1);
unsigned new_unit_end = position + new_units.size();
while (new_unit_end && new_unit_end < mapping_units_.size() &&
mapping_units_[new_unit_end - 1].Concatenate(
mapping_units_[new_unit_end])) {
mapping_units_.EraseAt(new_unit_end);
ShiftRanges(new_unit_end, -1);
}
while (position && position < mapping_units_.size() &&
mapping_units_[position - 1].Concatenate(mapping_units_[position])) {
mapping_units_.EraseAt(position);
ShiftRanges(position, -1);
}
}
void NGOffsetMappingBuilder::ShiftRanges(unsigned position, int delta) {
for (auto& range : unit_ranges_) {
auto& pair = range.value;
if (pair.first > position)
pair.first += delta;
if (pair.second > position)
pair.second += delta;
}
}
void NGOffsetMappingBuilder::SetDestinationString(String string) {
DCHECK_EQ(destination_length_, string.length());
destination_string_ = string;
}
NGOffsetMapping NGOffsetMappingBuilder::Build() {
// All mapping units are already built. Scan them to build mapping ranges.
for (unsigned range_start = 0; range_start < mapping_units_.size();) {
const Node* node = &mapping_units_[range_start].GetOwner();
// Units of the same node should be consecutive in the mapping function,
// If not, the layout structure should be already broken.
DCHECK(!unit_ranges_.Contains(node)) << node;
unsigned range_end = range_start + 1;
while (range_end < mapping_units_.size() &&
mapping_units_[range_end].GetOwner() == node)
++range_end;
unit_ranges_.insert(node, std::make_pair(range_start, range_end));
range_start = range_end;
}
return NGOffsetMapping(std::move(mapping_units_), std::move(unit_ranges_),
destination_string_);
}
void NGOffsetMappingBuilder::EnterInline(const LayoutObject& layout_object) {
if (!layout_object.NonPseudoNode())
return;
open_inlines_.push_back(mapping_units_.size());
}
void NGOffsetMappingBuilder::ExitInline(const LayoutObject& layout_object) {
if (!layout_object.NonPseudoNode())
return;
DCHECK(open_inlines_.size());
unit_ranges_.insert(
layout_object.GetNode(),
std::make_pair(open_inlines_.back(), mapping_units_.size()));
open_inlines_.pop_back();
}
} // namespace blink