diff --git a/sky/engine/core/core.gni b/sky/engine/core/core.gni index 5b01d018797..1a4d5063c24 100644 --- a/sky/engine/core/core.gni +++ b/sky/engine/core/core.gni @@ -14,6 +14,8 @@ sky_core_files = [ "editing/CompositionUnderline.h", "editing/CompositionUnderlineRangeFilter.cpp", "editing/CompositionUnderlineRangeFilter.h", + "editing/PositionWithAffinity.cpp", + "editing/PositionWithAffinity.h", "painting/Canvas.cpp", "painting/Canvas.h", "painting/CanvasColor.cpp", diff --git a/sky/engine/core/dart/text.dart b/sky/engine/core/dart/text.dart index 0354ada25b9..7b8667a2758 100644 --- a/sky/engine/core/dart/text.dart +++ b/sky/engine/core/dart/text.dart @@ -474,7 +474,44 @@ class TextBox { int get hashCode => hashValues(left, top, right, bottom, direction); - String toString() => "TextBox.fromLTRBD(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)}, $direction)"; + String toString() => 'TextBox.fromLTRBD(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)}, $direction)'; +} + +/// Whether a [TextPosition] is visually upstream or downstream of its offset. +/// +/// For example, when a text position exists at a line break, a single offset has +/// two visual positions, one prior to the line break (at the end of the first +/// line) and one after the line break (at the start of the second line). A text +/// affinity disambiguates between those cases. (Something similar happens with +/// between runs of bidirectional text.) +enum TextAffinity { + /// The position has affinity for the upstream side of the text position. + /// + /// For example, if the offset of the text position is a line break, the + /// position represents the end of the first line. + upstream, + + /// The position has affinity for the downstream side of the text position. + /// + /// For example, if the offset of the text position is a line break, the + /// position represents the start of the second line. + downstream +} + +/// A visual position in a string of text. +class TextPosition { + const TextPosition({ this.offset, this.affinity: TextAffinity.downstream }); + + /// The index of the character just prior to the position. + final int offset; + + /// If the offset has more than one visual location (e.g., occurs at a line + /// break), which of the two locations is represented by this position. + final TextAffinity affinity; + + String toString() { + return '$runtimeType(offset: $offset, affinity: $affinity)'; + } } abstract class Paragraph extends NativeFieldWrapperClass2 { @@ -496,7 +533,16 @@ abstract class Paragraph extends NativeFieldWrapperClass2 { void layout() native "Paragraph_layout"; void paint(Canvas canvas, Offset offset) native "Paragraph_paint"; + /// Returns a list of text boxes that enclose the given text range. List getBoxesForRange(int start, int end) native "Paragraph_getRectsForRange"; + + List _getPositionForOffset(Offset offset) native "Paragraph_getPositionForOffset"; + + /// Returns the text position closest to the given offset. + TextPosition getPositionForOffset(Offset offset) { + List encoded = _getPositionForOffset(offset); + return new TextPosition(offset: encoded[0], affinity: TextAffinity.values[encoded[1]]); + } } class ParagraphBuilder extends NativeFieldWrapperClass2 { diff --git a/sky/engine/core/editing/PositionWithAffinity.cpp b/sky/engine/core/editing/PositionWithAffinity.cpp new file mode 100644 index 00000000000..3df5f74ef12 --- /dev/null +++ b/sky/engine/core/editing/PositionWithAffinity.cpp @@ -0,0 +1,20 @@ +// Copyright 2014 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 "sky/engine/core/editing/PositionWithAffinity.h" + +namespace blink { + +PositionWithAffinity::PositionWithAffinity(RenderObject* renderer, int offset, EAffinity affinity) + : m_renderer(renderer) + , m_offset(offset) + , m_affinity(affinity) +{ +} + +PositionWithAffinity::~PositionWithAffinity() +{ +} + +} // namespace blink diff --git a/sky/engine/core/editing/PositionWithAffinity.h b/sky/engine/core/editing/PositionWithAffinity.h new file mode 100644 index 00000000000..2d7ca88b97e --- /dev/null +++ b/sky/engine/core/editing/PositionWithAffinity.h @@ -0,0 +1,44 @@ +// Copyright 2014 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 SKY_ENGINE_CORE_EDITING_POSITIONWITHAFFINITY_H_ +#define SKY_ENGINE_CORE_EDITING_POSITIONWITHAFFINITY_H_ + +namespace blink { + +class RenderObject; + +enum EAffinity { UPSTREAM, DOWNSTREAM }; + +// VisiblePosition default affinity is downstream because +// the callers do not really care (they just want the +// deep position without regard to line position), and this +// is cheaper than UPSTREAM +#define VP_DEFAULT_AFFINITY DOWNSTREAM + +// Callers who do not know where on the line the position is, +// but would like UPSTREAM if at a line break or DOWNSTREAM +// otherwise, need a clear way to specify that. The +// constructors auto-correct UPSTREAM to DOWNSTREAM if the +// position is not at a line break. +#define VP_UPSTREAM_IF_POSSIBLE UPSTREAM + +class PositionWithAffinity { +public: + PositionWithAffinity(RenderObject* renderer, int offset, EAffinity = DOWNSTREAM); + ~PositionWithAffinity(); + + RenderObject* renderer() const { return m_renderer; } + int offset() const { return m_offset; } + EAffinity affinity() const { return m_affinity; } + +private: + RenderObject* m_renderer; + int m_offset; + EAffinity m_affinity; +}; + +} // namespace blink + +#endif // SKY_ENGINE_CORE_EDITING_POSITIONWITHAFFINITY_H_ diff --git a/sky/engine/core/rendering/RenderBlock.cpp b/sky/engine/core/rendering/RenderBlock.cpp index 4171734abcc..aa50e263513 100644 --- a/sky/engine/core/rendering/RenderBlock.cpp +++ b/sky/engine/core/rendering/RenderBlock.cpp @@ -952,6 +952,112 @@ bool RenderBlock::hitTestContents(const HitTestRequest& request, HitTestResult& return false; } +static PositionWithAffinity positionForPointInChild(RenderBox* child, const LayoutPoint& pointInParentCoordinates) +{ + LayoutPoint childLocation = child->location(); + + // FIXME: This is wrong if the child's writing-mode is different from the parent's. + LayoutPoint pointInChildCoordinates(toLayoutPoint(pointInParentCoordinates - childLocation)); + return child->positionForPoint(pointInChildCoordinates); +} + +PositionWithAffinity RenderBlock::positionForPointWithInlineChildren(const LayoutPoint& pointInLogicalContents) +{ + ASSERT(isRenderParagraph()); + + if (!firstRootBox()) + return createPositionWithAffinity(0, DOWNSTREAM); + + // look for the closest line box in the root box which is at the passed-in y coordinate + InlineBox* closestBox = 0; + RootInlineBox* firstRootBoxWithChildren = 0; + RootInlineBox* lastRootBoxWithChildren = 0; + for (RootInlineBox* root = firstRootBox(); root; root = root->nextRootBox()) { + if (!root->firstLeafChild()) + continue; + if (!firstRootBoxWithChildren) + firstRootBoxWithChildren = root; + + lastRootBoxWithChildren = root; + + // check if this root line box is located at this y coordinate + if (pointInLogicalContents.y() < root->selectionBottom()) { + closestBox = root->closestLeafChildForLogicalLeftPosition(pointInLogicalContents.x()); + if (closestBox) + break; + } + } + + if (!closestBox && lastRootBoxWithChildren) { + // y coordinate is below last root line box, pretend we hit it + closestBox = lastRootBoxWithChildren->closestLeafChildForLogicalLeftPosition(pointInLogicalContents.x()); + } + + if (closestBox) { + // pass the box a top position that is inside it + LayoutPoint point(pointInLogicalContents.x(), closestBox->root().blockDirectionPointInLine()); + if (closestBox->renderer().isReplaced()) + return positionForPointInChild(&toRenderBox(closestBox->renderer()), point); + return closestBox->renderer().positionForPoint(point); + } + + // Can't reach this. We have a root line box, but it has no kids. + // FIXME: This should ASSERT_NOT_REACHED(), but clicking on placeholder text + // seems to hit this code path. + return createPositionWithAffinity(0, DOWNSTREAM); +} + +static inline bool isChildHitTestCandidate(RenderBox* box) +{ + return box->height() && !box->isFloatingOrOutOfFlowPositioned(); +} + +PositionWithAffinity RenderBlock::positionForPoint(const LayoutPoint& point) +{ + if (isReplaced()) { + // FIXME: This seems wrong when the object's writing-mode doesn't match the line's writing-mode. + LayoutUnit pointLogicalLeft = point.x(); + LayoutUnit pointLogicalTop = point.y(); + + if (pointLogicalLeft < 0) + return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); + if (pointLogicalLeft >= logicalWidth()) + return createPositionWithAffinity(caretMaxOffset(), DOWNSTREAM); + if (pointLogicalTop < 0) + return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); + if (pointLogicalTop >= logicalHeight()) + return createPositionWithAffinity(caretMaxOffset(), DOWNSTREAM); + } + + LayoutPoint pointInContents = point; + LayoutPoint pointInLogicalContents(pointInContents); + + if (isRenderParagraph()) + return positionForPointWithInlineChildren(pointInLogicalContents); + + RenderBox* lastCandidateBox = lastChildBox(); + while (lastCandidateBox && !isChildHitTestCandidate(lastCandidateBox)) + lastCandidateBox = lastCandidateBox->previousSiblingBox(); + + if (lastCandidateBox) { + if (pointInLogicalContents.y() > logicalTopForChild(lastCandidateBox) + || (pointInLogicalContents.y() == logicalTopForChild(lastCandidateBox))) + return positionForPointInChild(lastCandidateBox, pointInContents); + + for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) { + if (!isChildHitTestCandidate(childBox)) + continue; + LayoutUnit childLogicalBottom = logicalTopForChild(childBox) + logicalHeightForChild(childBox); + // We hit child if our click is above the bottom of its padding box (like IE6/7 and FF3). + if (isChildHitTestCandidate(childBox) && (pointInLogicalContents.y() < childLogicalBottom)) + return positionForPointInChild(childBox, pointInContents); + } + } + + // We only get here if there are no hit test candidate children below the click. + return RenderBox::positionForPoint(point); +} + LayoutUnit RenderBlock::availableLogicalWidth() const { return RenderBox::availableLogicalWidth(); diff --git a/sky/engine/core/rendering/RenderBlock.h b/sky/engine/core/rendering/RenderBlock.h index 4a6403f08ea..36d42007ce3 100644 --- a/sky/engine/core/rendering/RenderBlock.h +++ b/sky/engine/core/rendering/RenderBlock.h @@ -132,6 +132,8 @@ public: LayoutUnit textIndentOffset() const; + virtual PositionWithAffinity positionForPoint(const LayoutPoint&) override; + // Block flows subclass availableWidth to handle multi column layout (shrinking the width available to children when laying out.) virtual LayoutUnit availableLogicalWidth() const override final; @@ -275,6 +277,8 @@ private: virtual LayoutRect localCaretRect(InlineBox*, int caretOffset, LayoutUnit* extraWidthToEndOfLine = 0) override final; + PositionWithAffinity positionForPointWithInlineChildren(const LayoutPoint&); + void removeFromGlobalMaps(); bool widthAvailableToChildrenHasChanged(); diff --git a/sky/engine/core/rendering/RenderBox.cpp b/sky/engine/core/rendering/RenderBox.cpp index 57722a69ad7..bcb07ec042c 100644 --- a/sky/engine/core/rendering/RenderBox.cpp +++ b/sky/engine/core/rendering/RenderBox.cpp @@ -2610,6 +2610,73 @@ LayoutRect RenderBox::localCaretRect(InlineBox* box, int caretOffset, LayoutUnit return rect; } +PositionWithAffinity RenderBox::positionForPoint(const LayoutPoint& point) +{ + // no children...return this render object's element, if there is one, and offset 0 + RenderObject* firstChild = slowFirstChild(); + if (!firstChild) + return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); + + // Pass off to the closest child. + LayoutUnit minDist = LayoutUnit::max(); + RenderBox* closestRenderer = 0; + LayoutPoint adjustedPoint = point; + + for (RenderObject* renderObject = firstChild; renderObject; renderObject = renderObject->nextSibling()) { + if (!renderObject->slowFirstChild() && !renderObject->isInline() && !renderObject->isRenderParagraph()) + continue; + + if (!renderObject->isBox()) + continue; + + RenderBox* renderer = toRenderBox(renderObject); + + LayoutUnit top = renderer->borderTop() + renderer->paddingTop() + renderer->y(); + LayoutUnit bottom = top + renderer->contentHeight(); + LayoutUnit left = renderer->borderLeft() + renderer->paddingLeft() + renderer->x(); + LayoutUnit right = left + renderer->contentWidth(); + + if (point.x() <= right && point.x() >= left && point.y() <= top && point.y() >= bottom) + return renderer->positionForPoint(point - renderer->locationOffset()); + + // Find the distance from (x, y) to the box. Split the space around the box into 8 pieces + // and use a different compare depending on which piece (x, y) is in. + LayoutPoint cmp; + if (point.x() > right) { + if (point.y() < top) + cmp = LayoutPoint(right, top); + else if (point.y() > bottom) + cmp = LayoutPoint(right, bottom); + else + cmp = LayoutPoint(right, point.y()); + } else if (point.x() < left) { + if (point.y() < top) + cmp = LayoutPoint(left, top); + else if (point.y() > bottom) + cmp = LayoutPoint(left, bottom); + else + cmp = LayoutPoint(left, point.y()); + } else { + if (point.y() < top) + cmp = LayoutPoint(point.x(), top); + else + cmp = LayoutPoint(point.x(), bottom); + } + + LayoutSize difference = cmp - point; + + LayoutUnit dist = difference.width() * difference.width() + difference.height() * difference.height(); + if (dist < minDist) { + closestRenderer = renderer; + minDist = dist; + } + } + + if (closestRenderer) + return closestRenderer->positionForPoint(adjustedPoint - closestRenderer->locationOffset()); + return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); +} + void RenderBox::addVisualEffectOverflow() { if (!style()->hasVisualOverflowingEffect()) diff --git a/sky/engine/core/rendering/RenderBox.h b/sky/engine/core/rendering/RenderBox.h index 88cd2215581..47279607e12 100644 --- a/sky/engine/core/rendering/RenderBox.h +++ b/sky/engine/core/rendering/RenderBox.h @@ -435,6 +435,8 @@ public: return true; } + virtual PositionWithAffinity positionForPoint(const LayoutPoint&) override; + void removeFloatingOrPositionedChildFromBlockLists(); RenderLayer* enclosingFloatPaintingLayer() const; diff --git a/sky/engine/core/rendering/RenderInline.cpp b/sky/engine/core/rendering/RenderInline.cpp index 9fc107de214..32f5b83d614 100644 --- a/sky/engine/core/rendering/RenderInline.cpp +++ b/sky/engine/core/rendering/RenderInline.cpp @@ -370,6 +370,23 @@ bool RenderInline::hitTestCulledInline(const HitTestRequest& request, HitTestRes return false; } +PositionWithAffinity RenderInline::positionForPoint(const LayoutPoint& point) +{ + // FIXME(sky): Now that we don't have continuations, can this whole function just be the following? + // return containingBlock()->positionForPoint(point); + + // FIXME: Does not deal with relative positioned inlines (should it?) + RenderBlock* cb = containingBlock(); + if (firstLineBox()) { + // This inline actually has a line box. We must have clicked in the border/padding of one of these boxes. We + // should try to find a result by asking our containing block. + return cb->positionForPoint(point); + } + + // Translate the coords from the pre-anonymous block to the post-anonymous block. + return RenderBoxModelObject::positionForPoint(point); +} + namespace { class LinesBoundingBoxGeneratorContext { diff --git a/sky/engine/core/rendering/RenderInline.h b/sky/engine/core/rendering/RenderInline.h index 9173b0b9709..8f07ba190fe 100644 --- a/sky/engine/core/rendering/RenderInline.h +++ b/sky/engine/core/rendering/RenderInline.h @@ -116,6 +116,8 @@ private: virtual void mapLocalToContainer(const RenderBox* paintInvalidationContainer, TransformState&, MapCoordinatesFlags = ApplyContainerFlip) const override; + virtual PositionWithAffinity positionForPoint(const LayoutPoint&) override final; + virtual IntRect borderBoundingBox() const override final { IntRect boundingBox = linesBoundingBox(); diff --git a/sky/engine/core/rendering/RenderObject.cpp b/sky/engine/core/rendering/RenderObject.cpp index 4954a7029bc..b65c8fbd15e 100644 --- a/sky/engine/core/rendering/RenderObject.cpp +++ b/sky/engine/core/rendering/RenderObject.cpp @@ -1402,6 +1402,11 @@ void RenderObject::postDestroy() delete this; } +PositionWithAffinity RenderObject::positionForPoint(const LayoutPoint&) +{ + return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); +} + // FIXME(sky): Change the callers to use nodeAtPoint direclty and remove this function. // Or, rename nodeAtPoint to hitTest? bool RenderObject::hitTest(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) @@ -1527,6 +1532,11 @@ bool RenderObject::supportsTouchAction() const return true; } +PositionWithAffinity RenderObject::createPositionWithAffinity(int offset, EAffinity affinity) +{ + return PositionWithAffinity(this, offset, affinity); +} + bool RenderObject::canUpdateSelectionOnRootLineBoxes() { if (needsLayout()) diff --git a/sky/engine/core/rendering/RenderObject.h b/sky/engine/core/rendering/RenderObject.h index 672eaaa78cf..6a8a7300b94 100644 --- a/sky/engine/core/rendering/RenderObject.h +++ b/sky/engine/core/rendering/RenderObject.h @@ -26,6 +26,7 @@ #ifndef SKY_ENGINE_CORE_RENDERING_RENDEROBJECT_H_ #define SKY_ENGINE_CORE_RENDERING_RENDEROBJECT_H_ +#include "sky/engine/core/editing/PositionWithAffinity.h" #include "sky/engine/core/rendering/HitTestRequest.h" #include "sky/engine/core/rendering/RenderObjectChildList.h" #include "sky/engine/core/rendering/SubtreeLayoutScope.h" @@ -390,6 +391,10 @@ public: virtual void updateHitTestResult(HitTestResult&, const LayoutPoint&); virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset); + virtual PositionWithAffinity positionForPoint(const LayoutPoint&); + PositionWithAffinity createPositionWithAffinity(int offset, EAffinity); + PositionWithAffinity createPositionWithAffinity(const Position&); + virtual void dirtyLinesFromChangedChild(RenderObject*); // Set the style of the object and update the state of the object accordingly. diff --git a/sky/engine/core/rendering/RenderReplaced.cpp b/sky/engine/core/rendering/RenderReplaced.cpp index 73c30c2f20b..ef4d2b51a32 100644 --- a/sky/engine/core/rendering/RenderReplaced.cpp +++ b/sky/engine/core/rendering/RenderReplaced.cpp @@ -374,6 +374,26 @@ void RenderReplaced::computePreferredLogicalWidths() clearPreferredLogicalWidthsDirty(); } +PositionWithAffinity RenderReplaced::positionForPoint(const LayoutPoint& point) +{ + // FIXME: This code is buggy if the replaced element is relative positioned. + InlineBox* box = inlineBoxWrapper(); + RootInlineBox* rootBox = box ? &box->root() : 0; + + LayoutUnit top = rootBox ? rootBox->selectionTop() : logicalTop(); + LayoutUnit bottom = rootBox ? rootBox->selectionBottom() : logicalBottom(); + + LayoutUnit blockDirectionPosition = point.y() + y(); + + if (blockDirectionPosition < top) + return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); // coordinates are above + + if (blockDirectionPosition >= bottom) + return createPositionWithAffinity(caretMaxOffset(), DOWNSTREAM); // coordinates are below + + return RenderBox::positionForPoint(point); +} + LayoutRect RenderReplaced::localSelectionRect(bool checkWhetherSelected) const { if (checkWhetherSelected && !isSelected()) diff --git a/sky/engine/core/rendering/RenderReplaced.h b/sky/engine/core/rendering/RenderReplaced.h index 6e62eefda04..5f33b361dec 100644 --- a/sky/engine/core/rendering/RenderReplaced.h +++ b/sky/engine/core/rendering/RenderReplaced.h @@ -78,6 +78,8 @@ private: virtual void computePreferredLogicalWidths() override final; virtual void paintReplaced(PaintInfo&, const LayoutPoint&) { } + virtual PositionWithAffinity positionForPoint(const LayoutPoint&) override final; + virtual bool canBeSelectionLeaf() const override { return true; } void computeAspectRatioInformationForRenderBox(FloatSize& constrainedSize, double& intrinsicRatio) const; diff --git a/sky/engine/core/rendering/RenderText.cpp b/sky/engine/core/rendering/RenderText.cpp index 51951eae25c..da9c74552f5 100644 --- a/sky/engine/core/rendering/RenderText.cpp +++ b/sky/engine/core/rendering/RenderText.cpp @@ -276,6 +276,169 @@ void RenderText::absoluteQuadsForRange(Vector& quads, unsigned start, enum ShouldAffinityBeDownstream { AlwaysDownstream, AlwaysUpstream, UpstreamIfPositionIsNotAtStart }; +static bool lineDirectionPointFitsInBox(int pointLineDirection, InlineTextBox* box, ShouldAffinityBeDownstream& shouldAffinityBeDownstream) +{ + shouldAffinityBeDownstream = AlwaysDownstream; + + // the x coordinate is equal to the left edge of this box + // the affinity must be downstream so the position doesn't jump back to the previous line + // except when box is the first box in the line + if (pointLineDirection <= box->logicalLeft()) { + shouldAffinityBeDownstream = !box->prevLeafChild() ? UpstreamIfPositionIsNotAtStart : AlwaysDownstream; + return true; + } + + // and the x coordinate is to the left of the right edge of this box + // check to see if position goes in this box + if (pointLineDirection < box->logicalRight()) { + shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart; + return true; + } + + // box is first on line + // and the x coordinate is to the left of the first text box left edge + if (!box->prevLeafChildIgnoringLineBreak() && pointLineDirection < box->logicalLeft()) + return true; + + if (!box->nextLeafChildIgnoringLineBreak()) { + // box is last on line + // and the x coordinate is to the right of the last text box right edge + // generate VisiblePosition, use UPSTREAM affinity if possible + shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart; + return true; + } + + return false; +} + +static PositionWithAffinity createPositionWithAffinityForBox(const InlineBox* box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream) +{ + EAffinity affinity = VP_DEFAULT_AFFINITY; + switch (shouldAffinityBeDownstream) { + case AlwaysDownstream: + affinity = DOWNSTREAM; + break; + case AlwaysUpstream: + affinity = VP_UPSTREAM_IF_POSSIBLE; + break; + case UpstreamIfPositionIsNotAtStart: + affinity = offset > box->caretMinOffset() ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM; + break; + } + int textStartOffset = box->renderer().isText() ? toRenderText(box->renderer()).textStartOffset() : 0; + return box->renderer().createPositionWithAffinity(offset + textStartOffset, affinity); +} + +static PositionWithAffinity createPositionWithAffinityForBoxAfterAdjustingOffsetForBiDi(const InlineTextBox* box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream) +{ + ASSERT(box); + ASSERT(offset >= 0); + + if (offset && static_cast(offset) < box->len()) + return createPositionWithAffinityForBox(box, box->start() + offset, shouldAffinityBeDownstream); + + bool positionIsAtStartOfBox = !offset; + if (positionIsAtStartOfBox == box->isLeftToRightDirection()) { + // offset is on the left edge + + const InlineBox* prevBox = box->prevLeafChildIgnoringLineBreak(); + if ((prevBox && prevBox->bidiLevel() == box->bidiLevel()) + || box->renderer().containingBlock()->style()->direction() == box->direction()) // FIXME: left on 12CBA + return createPositionWithAffinityForBox(box, box->caretLeftmostOffset(), shouldAffinityBeDownstream); + + if (prevBox && prevBox->bidiLevel() > box->bidiLevel()) { + // e.g. left of B in aDC12BAb + const InlineBox* leftmostBox; + do { + leftmostBox = prevBox; + prevBox = leftmostBox->prevLeafChildIgnoringLineBreak(); + } while (prevBox && prevBox->bidiLevel() > box->bidiLevel()); + return createPositionWithAffinityForBox(leftmostBox, leftmostBox->caretRightmostOffset(), shouldAffinityBeDownstream); + } + + if (!prevBox || prevBox->bidiLevel() < box->bidiLevel()) { + // e.g. left of D in aDC12BAb + const InlineBox* rightmostBox; + const InlineBox* nextBox = box; + do { + rightmostBox = nextBox; + nextBox = rightmostBox->nextLeafChildIgnoringLineBreak(); + } while (nextBox && nextBox->bidiLevel() >= box->bidiLevel()); + return createPositionWithAffinityForBox(rightmostBox, + box->isLeftToRightDirection() ? rightmostBox->caretMaxOffset() : rightmostBox->caretMinOffset(), shouldAffinityBeDownstream); + } + + return createPositionWithAffinityForBox(box, box->caretRightmostOffset(), shouldAffinityBeDownstream); + } + + const InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak(); + if ((nextBox && nextBox->bidiLevel() == box->bidiLevel()) + || box->renderer().containingBlock()->style()->direction() == box->direction()) + return createPositionWithAffinityForBox(box, box->caretRightmostOffset(), shouldAffinityBeDownstream); + + // offset is on the right edge + if (nextBox && nextBox->bidiLevel() > box->bidiLevel()) { + // e.g. right of C in aDC12BAb + const InlineBox* rightmostBox; + do { + rightmostBox = nextBox; + nextBox = rightmostBox->nextLeafChildIgnoringLineBreak(); + } while (nextBox && nextBox->bidiLevel() > box->bidiLevel()); + return createPositionWithAffinityForBox(rightmostBox, rightmostBox->caretLeftmostOffset(), shouldAffinityBeDownstream); + } + + if (!nextBox || nextBox->bidiLevel() < box->bidiLevel()) { + // e.g. right of A in aDC12BAb + const InlineBox* leftmostBox; + const InlineBox* prevBox = box; + do { + leftmostBox = prevBox; + prevBox = leftmostBox->prevLeafChildIgnoringLineBreak(); + } while (prevBox && prevBox->bidiLevel() >= box->bidiLevel()); + return createPositionWithAffinityForBox(leftmostBox, + box->isLeftToRightDirection() ? leftmostBox->caretMinOffset() : leftmostBox->caretMaxOffset(), shouldAffinityBeDownstream); + } + + return createPositionWithAffinityForBox(box, box->caretLeftmostOffset(), shouldAffinityBeDownstream); +} + +PositionWithAffinity RenderText::positionForPoint(const LayoutPoint& point) +{ + if (!firstTextBox() || textLength() == 0) + return createPositionWithAffinity(0, DOWNSTREAM); + + LayoutUnit pointLineDirection = point.x(); + LayoutUnit pointBlockDirection = point.y(); + + InlineTextBox* lastBox = 0; + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + if (box->isLineBreak() && !box->prevLeafChild() && box->nextLeafChild() && !box->nextLeafChild()->isLineBreak()) + box = box->nextTextBox(); + + RootInlineBox& rootBox = box->root(); + LayoutUnit top = std::min(rootBox.selectionTop(), rootBox.lineTop()); + if (pointBlockDirection > top || pointBlockDirection == top) { + LayoutUnit bottom = rootBox.selectionBottom(); + if (rootBox.nextRootBox()) + bottom = std::min(bottom, rootBox.nextRootBox()->lineTop()); + + if (pointBlockDirection < bottom) { + ShouldAffinityBeDownstream shouldAffinityBeDownstream; + if (lineDirectionPointFitsInBox(pointLineDirection, box, shouldAffinityBeDownstream)) + return createPositionWithAffinityForBoxAfterAdjustingOffsetForBiDi(box, box->offsetForPosition(pointLineDirection.toFloat()), shouldAffinityBeDownstream); + } + } + lastBox = box; + } + + if (lastBox) { + ShouldAffinityBeDownstream shouldAffinityBeDownstream; + lineDirectionPointFitsInBox(pointLineDirection, lastBox, shouldAffinityBeDownstream); + return createPositionWithAffinityForBoxAfterAdjustingOffsetForBiDi(lastBox, lastBox->offsetForPosition(pointLineDirection.toFloat()) + lastBox->start(), shouldAffinityBeDownstream); + } + return createPositionWithAffinity(0, DOWNSTREAM); +} + LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, LayoutUnit* extraWidthToEndOfLine) { if (!inlineBox) diff --git a/sky/engine/core/rendering/RenderText.h b/sky/engine/core/rendering/RenderText.h index 23267cb1f77..1f128d31ead 100644 --- a/sky/engine/core/rendering/RenderText.h +++ b/sky/engine/core/rendering/RenderText.h @@ -65,6 +65,8 @@ public: enum ClippingOption { NoClipping, ClipToEllipsis }; void absoluteQuads(Vector&, ClippingOption = NoClipping) const; + virtual PositionWithAffinity positionForPoint(const LayoutPoint&) override; + bool is8Bit() const { return m_text.is8Bit(); } const LChar* characters8() const { return m_text.impl()->characters8(); } const UChar* characters16() const { return m_text.impl()->characters16(); } diff --git a/sky/engine/core/text/Paragraph.cpp b/sky/engine/core/text/Paragraph.cpp index f366d58da58..9a613113520 100644 --- a/sky/engine/core/text/Paragraph.cpp +++ b/sky/engine/core/text/Paragraph.cpp @@ -36,6 +36,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Paragraph); V(Paragraph, ideographicBaseline) \ V(Paragraph, layout) \ V(Paragraph, getRectsForRange) \ + V(Paragraph, getPositionForOffset) \ V(Paragraph, paint) DART_BIND_ALL(Paragraph, FOR_EACH_BINDING) @@ -134,4 +135,28 @@ std::vector Paragraph::getRectsForRange(unsigned start, unsigned end) { return boxes; } +int Paragraph::absoluteOffsetForPosition(const PositionWithAffinity& position) { + DCHECK(position.renderer()); + unsigned offset = 0; + for (RenderObject* object = m_renderView.get(); object; object = object->nextInPreOrder()) { + if (object == position.renderer()) + return offset + position.offset(); + if (object->isText()) { + RenderText* text = toRenderText(object); + offset += text->textLength(); + } + } + DCHECK(false); + return 0; +} + +Dart_Handle Paragraph::getPositionForOffset(const Offset& offset) { + LayoutPoint point(offset.sk_size.width(), offset.sk_size.height()); + PositionWithAffinity position = m_renderView->positionForPoint(point); + Dart_Handle result = Dart_NewList(2); + Dart_ListSetAt(result, 0, ToDart(absoluteOffsetForPosition(position))); + Dart_ListSetAt(result, 1, ToDart(static_cast(position.affinity()))); + return result; +} + } // namespace blink diff --git a/sky/engine/core/text/Paragraph.h b/sky/engine/core/text/Paragraph.h index 536f039afc7..32005d53b88 100644 --- a/sky/engine/core/text/Paragraph.h +++ b/sky/engine/core/text/Paragraph.h @@ -48,6 +48,7 @@ public: void paint(Canvas* canvas, const Offset& offset); std::vector getRectsForRange(unsigned start, unsigned end); + Dart_Handle getPositionForOffset(const Offset& offset); RenderView* renderView() const { return m_renderView.get(); } @@ -56,6 +57,8 @@ public: private: RenderBox* firstChildBox() const { return m_renderView->firstChildBox(); } + int absoluteOffsetForPosition(const PositionWithAffinity& position); + LayoutUnit m_minWidth; LayoutUnit m_maxWidth; LayoutUnit m_minHeight;