blob: 67351ca1501ef9fa7402ebce504a89588f2134bf [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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_OFFSET_MAPPING_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_OFFSET_MAPPING_H_
#include "base/optional.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/editing/forward.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
class LayoutBlockFlow;
class LayoutObject;
enum class NGOffsetMappingUnitType { kIdentity, kCollapsed };
// An NGOffsetMappingUnit indicates a "simple" offset mapping between dom offset
// range [dom_start, dom_end] on node |owner| and text content offset range
// [text_content_start, text_content_end]. The mapping between them falls in one
// of the following categories, depending on |type|:
// - kIdentity: The mapping between the two ranges is the identity mapping. In
// other words, the two ranges have the same length, and the offsets are
// mapped one-to-one.
// - kCollapsed: The mapping is collapsed, namely, |text_content_start| and
// |text_content_end| are the same, and characters in the dom range are
// collapsed.
// - kExpanded: The mapping is expanded, namely, |dom_end == dom_start + 1|, and
// |text_content_end > text_content_start + 1|, indicating that the character
// in the dom range is expanded into multiple characters.
// See design doc https://goo.gl/CJbxky for details.
class CORE_EXPORT NGOffsetMappingUnit {
DISALLOW_NEW();
public:
NGOffsetMappingUnit(NGOffsetMappingUnitType,
const LayoutObject&,
unsigned dom_start,
unsigned dom_end,
unsigned text_content_start,
unsigned text_content_end);
// Returns associated node for this unit or null if this unit is associated
// to generated content.
const Node* AssociatedNode() const;
NGOffsetMappingUnitType GetType() const { return type_; }
const LayoutObject& GetLayoutObject() const { return *layout_object_; }
// Returns |Node| for this unit. If this unit comes from CSS generated
// content, we can't use this function.
// TODO(yosin): We should rename |GetOwner()| to |NonPseudoNode()|.
const Node& GetOwner() const;
unsigned DOMStart() const { return dom_start_; }
unsigned DOMEnd() const { return dom_end_; }
unsigned TextContentStart() const { return text_content_start_; }
unsigned TextContentEnd() const { return text_content_end_; }
// If the passed unit can be concatenated to |this| to create a bigger unit,
// replaces |this| by the result and returns true; Returns false otherwise.
bool Concatenate(const NGOffsetMappingUnit&);
unsigned ConvertDOMOffsetToTextContent(unsigned) const;
unsigned ConvertTextContentToFirstDOMOffset(unsigned) const;
unsigned ConvertTextContentToLastDOMOffset(unsigned) const;
void AssertValid() const;
private:
NGOffsetMappingUnitType type_ = NGOffsetMappingUnitType::kIdentity;
const LayoutObject* layout_object_;
// TODO(yosin): We should rename |dom_start_| and |dom_end_| to appropriate
// names since |layout_object_| is for generated text, these offsets are
// offset in |LayoutText::text_| instead of DOM node.
unsigned dom_start_;
unsigned dom_end_;
// |text_content_start_| and |text_content_end_| are offsets in
// |NGOffsetMapping::text_|. These values are in [0, |text_.length()] to
// represent collapsed spaces at the end of block.
unsigned text_content_start_;
unsigned text_content_end_;
friend class NGOffsetMappingBuilder;
};
// Each inline formatting context laid out with LayoutNG has an NGOffsetMapping
// object that stores the mapping information between DOM positions and offsets
// in the text content string of the context.
// See design doc https://goo.gl/CJbxky for details.
class CORE_EXPORT NGOffsetMapping {
USING_FAST_MALLOC(NGOffsetMapping);
public:
using UnitVector = Vector<NGOffsetMappingUnit>;
using RangeMap =
HashMap<Persistent<const Node>, std::pair<unsigned, unsigned>>;
NGOffsetMapping(UnitVector&&, RangeMap&&, String);
NGOffsetMapping(const NGOffsetMapping&) = delete;
NGOffsetMapping& operator=(const NGOffsetMapping&) = delete;
~NGOffsetMapping();
const UnitVector& GetUnits() const { return units_; }
const RangeMap& GetRanges() const { return ranges_; }
const String& GetText() const { return text_; }
// ------ Static getters for offset mapping objects ------
// TODO(xiaochengh): Unify the following getters and make them work on both
// legacy and LayoutNG.
// NGOffsetMapping APIs only accept the following positions:
// 1. Offset-in-anchor in a text node;
// 2. Before/After-anchor of an atomic inline or a text-like node like <br>.
static bool AcceptsPosition(const Position&);
// Returns the mapping object of the inline formatting context laying out the
// given position.
static const NGOffsetMapping* GetFor(const Position&);
// Returns the mapping object of the inline formatting context containing the
// given LayoutObject, if it's laid out with LayoutNG. If the LayoutObject is
// itself an inline formatting context, returns its own offset mapping object.
// This makes the retrieval of the mapping object easier when we already have
// a LayoutObject at hand.
static const NGOffsetMapping* GetFor(const LayoutObject*);
// Returns the inline formatting context (which is a block flow) where the
// given object is laid out -- this is the block flow whose offset mapping
// contains the given object. Note that the object can be in either legacy or
// NG layout, while NGOffsetMapping is supported on both of them.
static LayoutBlockFlow* GetInlineFormattingContextOf(const LayoutObject&);
// Variants taking position instead of |LayoutObject|.
static LayoutBlockFlow* GetInlineFormattingContextOf(const Position&);
// ------ Mapping APIs from DOM to text content ------
// Returns the NGOffsetMappingUnit whose DOM range contains the position.
// If there are multiple qualifying units, returns the last one.
const NGOffsetMappingUnit* GetMappingUnitForPosition(const Position&) const;
// Returns all NGOffsetMappingUnits whose DOM ranges has non-empty (but
// possibly collapsed) intersections with the passed in DOM range. If a unit
// partially intersects the range, it is clamped with only the part within the
// range returned. This API only accepts ranges whose start and end have the
// same anchor node.
UnitVector GetMappingUnitsForDOMRange(const EphemeralRange&) const;
// Returns all NGOffsetMappingUnits associated to |node|. When |node| is
// laid out with ::first-letter, this function returns both first-letter part
// and remaining part. Note: |node| should have associated mapping.
base::span<const NGOffsetMappingUnit> GetMappingUnitsForNode(
const Node& node) const;
// Returns all NGOffsetMappingUnits associated to |layout_object|. This
// function works even if |layout_object| is for CSS generated content
// ("content" property in ::before/::after, etc.)
// Note: Unlike |GetMappingUnitsForNode()|, this function returns units
// for first-letter or remaining part only instead of both parts.
// Note: |layout_object| should have associated mapping.
base::span<const NGOffsetMappingUnit> GetMappingUnitsForLayoutObject(
const LayoutObject& layout_object) const;
// Returns the text content offset corresponding to the given position.
// Returns nullopt when the position is not laid out in this context.
base::Optional<unsigned> GetTextContentOffset(const Position&) const;
// Starting from the given position, searches for non-collapsed content in
// the anchor node in forward/backward direction and returns the position
// before/after it; Returns null if there is no more non-collapsed content in
// the anchor node.
Position StartOfNextNonCollapsedContent(const Position&) const;
Position EndOfLastNonCollapsedContent(const Position&) const;
// Returns true if the position is right before/after non-collapsed content in
// the anchor node. Note that false is returned if the position is already at
// the end/start of the anchor node.
bool IsBeforeNonCollapsedContent(const Position&) const;
bool IsAfterNonCollapsedContent(const Position&) const;
// Maps the given position to a text content offset, and then returns the text
// content character before the offset. Returns nullopt if it does not exist.
base::Optional<UChar> GetCharacterBefore(const Position&) const;
// ------ Mapping APIs from text content to DOM ------
// These APIs map a text content offset to DOM positions, or return null when
// both characters next to the offset are in generated content (list markers,
// ::before/after, generated BiDi control characters, ...). The returned
// position is either offset in a text node, or before/after an atomic inline
// (IMG, BR, ...).
// Note 1: there can be multiple positions mapped to the same offset when,
// for example, there are collapsed whitespaces. Hence, we have two APIs to
// return the first/last one of them.
// Note 2: there is a corner case where Shadow DOM changes the ordering of
// nodes in the flat tree, so that they are not laid out in the same order as
// in the DOM tree. In this case, "first" and "last" position are defined on
// the layout order, aka the flat tree order.
Position GetFirstPosition(unsigned) const;
Position GetLastPosition(unsigned) const;
// Returns all NGOffsetMappingUnits whose text content ranges has non-empty
// (but possibly collapsed) intersection with (start, end). Note that units
// that only "touch" |start| or |end| are excluded.
// Note: Returned range may include units for generated content.
base::span<const NGOffsetMappingUnit>
GetMappingUnitsForTextContentOffsetRange(unsigned start, unsigned end) const;
// Returns the first |NGOffsetMappingUnit| where |TextContentStart() >=
// offset| including unit for generated content.
const NGOffsetMappingUnit* GetFirstMappingUnit(unsigned offset) const;
// Returns the last |NGOffsetMappingUnit| where |TextContentStart() >= offset|
// including unit for generated content.
const NGOffsetMappingUnit* GetLastMappingUnit(unsigned offset) const;
// ------ APIs inspecting the text content string ------
// Returns false if all characters in [start, end) of |text_| are bidi
// control characters. Returns true otherwise.
bool HasBidiControlCharactersOnly(unsigned start, unsigned end) const;
private:
// The NGOffsetMappingUnits of the inline formatting context in osrted order.
UnitVector units_;
// Stores the unit range for each node in inline formatting context.
RangeMap ranges_;
// The text content string of the inline formatting context. Same string as
// |NGInlineNodeData::text_content_|.
String text_;
};
CORE_EXPORT LayoutBlockFlow* NGInlineFormattingContextOf(const Position&);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_OFFSET_MAPPING_H_