| // 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 |