mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #2327 from abarth/position_for_point
Add Paragraph#getPositionForOffset
This commit is contained in:
commit
07ec7da5ee
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
20
sky/engine/core/editing/PositionWithAffinity.cpp
Normal file
20
sky/engine/core/editing/PositionWithAffinity.cpp
Normal 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
|
||||
44
sky/engine/core/editing/PositionWithAffinity.h
Normal file
44
sky/engine/core/editing/PositionWithAffinity.h
Normal 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_
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -435,6 +435,8 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual PositionWithAffinity positionForPoint(const LayoutPoint&) override;
|
||||
|
||||
void removeFloatingOrPositionedChildFromBlockLists();
|
||||
|
||||
RenderLayer* enclosingFloatPaintingLayer() const;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(); }
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user