Merge pull request #2327 from abarth/position_for_point

Add Paragraph#getPositionForOffset
This commit is contained in:
Adam Barth 2016-01-30 09:27:57 -08:00
commit 07ec7da5ee
18 changed files with 541 additions and 1 deletions

View File

@ -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",

View File

@ -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<TextBox> getBoxesForRange(int start, int end) native "Paragraph_getRectsForRange";
List<int> _getPositionForOffset(Offset offset) native "Paragraph_getPositionForOffset";
/// Returns the text position closest to the given offset.
TextPosition getPositionForOffset(Offset offset) {
List<int> encoded = _getPositionForOffset(offset);
return new TextPosition(offset: encoded[0], affinity: TextAffinity.values[encoded[1]]);
}
}
class ParagraphBuilder extends NativeFieldWrapperClass2 {

View File

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

View File

@ -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_

View File

@ -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();

View File

@ -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();

View File

@ -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())

View File

@ -435,6 +435,8 @@ public:
return true;
}
virtual PositionWithAffinity positionForPoint(const LayoutPoint&) override;
void removeFloatingOrPositionedChildFromBlockLists();
RenderLayer* enclosingFloatPaintingLayer() const;

View File

@ -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 {

View File

@ -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();

View File

@ -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())

View File

@ -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.

View File

@ -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())

View File

@ -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;

View File

@ -276,6 +276,169 @@ void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& 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<unsigned>(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)

View File

@ -65,6 +65,8 @@ public:
enum ClippingOption { NoClipping, ClipToEllipsis };
void absoluteQuads(Vector<FloatQuad>&, 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(); }

View File

@ -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<TextBox> 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<int>(position.affinity())));
return result;
}
} // namespace blink

View File

@ -48,6 +48,7 @@ public:
void paint(Canvas* canvas, const Offset& offset);
std::vector<TextBox> 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;