blob: 236be0fde7424c0bdf03e2d10b1d6382d3251751 [file] [log] [blame]
/*
* Copyright (C) Research In Motion Limited 2010-2012. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/layout/svg/svg_text_query.h"
#include <algorithm>
#include "third_party/blink/renderer/core/layout/api/line_layout_svg_inline_text.h"
#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/line/inline_flow_box.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h"
#include "third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.h"
#include "third_party/blink/renderer/core/layout/svg/svg_text_fragment.h"
#include "third_party/blink/renderer/core/layout/svg/svg_text_metrics.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
// Base structure for callback user data
struct QueryData {
QueryData()
: is_vertical_text(false),
current_offset(0),
text_line_layout(nullptr),
text_box(nullptr) {}
bool is_vertical_text;
unsigned current_offset;
LineLayoutSVGInlineText text_line_layout;
const SVGInlineTextBox* text_box;
};
static inline InlineFlowBox* FlowBoxForLayoutObject(
LayoutObject* layout_object) {
if (!layout_object)
return nullptr;
if (layout_object->IsLayoutBlock()) {
// If we're given a block element, it has to be a LayoutSVGText.
DCHECK(layout_object->IsSVGText());
LayoutBlockFlow* layout_block_flow = ToLayoutBlockFlow(layout_object);
// LayoutSVGText only ever contains a single line box.
InlineFlowBox* flow_box = layout_block_flow->FirstLineBox();
DCHECK_EQ(flow_box, layout_block_flow->LastLineBox());
return flow_box;
}
if (layout_object->IsLayoutInline()) {
// We're given a LayoutSVGInline or objects that derive from it
// (LayoutSVGTSpan / LayoutSVGTextPath)
LayoutInline* layout_inline = ToLayoutInline(layout_object);
// LayoutSVGInline only ever contains a single line box.
InlineFlowBox* flow_box = layout_inline->FirstLineBox();
DCHECK_EQ(flow_box, layout_inline->LastLineBox());
return flow_box;
}
NOTREACHED();
return nullptr;
}
static void CollectTextBoxesInFlowBox(InlineFlowBox* flow_box,
Vector<SVGInlineTextBox*>& text_boxes) {
if (!flow_box)
return;
for (InlineBox* child = flow_box->FirstChild(); child;
child = child->NextOnLine()) {
if (child->IsInlineFlowBox()) {
// Skip generated content.
if (!child->GetLineLayoutItem().GetNode())
continue;
CollectTextBoxesInFlowBox(ToInlineFlowBox(child), text_boxes);
continue;
}
if (child->IsSVGInlineTextBox())
text_boxes.push_back(ToSVGInlineTextBox(child));
}
}
typedef bool ProcessTextFragmentCallback(QueryData*, const SVGTextFragment&);
static bool QueryTextBox(QueryData* query_data,
const SVGInlineTextBox* text_box,
ProcessTextFragmentCallback fragment_callback) {
query_data->text_box = text_box;
query_data->text_line_layout =
LineLayoutSVGInlineText(text_box->GetLineLayoutItem());
query_data->is_vertical_text =
!query_data->text_line_layout.StyleRef().IsHorizontalWritingMode();
// Loop over all text fragments in this text box, firing a callback for each.
for (const SVGTextFragment& fragment : text_box->TextFragments()) {
if (fragment_callback(query_data, fragment))
return true;
}
return false;
}
// Execute a query in "spatial" order starting at |queryRoot|. This means
// walking the lines boxes in the order they would get painted.
static void SpatialQuery(LayoutObject* query_root,
QueryData* query_data,
ProcessTextFragmentCallback fragment_callback) {
Vector<SVGInlineTextBox*> text_boxes;
CollectTextBoxesInFlowBox(FlowBoxForLayoutObject(query_root), text_boxes);
// Loop over all text boxes
for (const SVGInlineTextBox* text_box : text_boxes) {
if (QueryTextBox(query_data, text_box, fragment_callback))
return;
}
}
static void CollectTextBoxesInLogicalOrder(
LineLayoutSVGInlineText text_line_layout,
Vector<SVGInlineTextBox*>& text_boxes) {
text_boxes.Shrink(0);
for (InlineTextBox* text_box = text_line_layout.FirstTextBox(); text_box;
text_box = text_box->NextForSameLayoutObject())
text_boxes.push_back(ToSVGInlineTextBox(text_box));
std::sort(text_boxes.begin(), text_boxes.end(),
InlineTextBox::CompareByStart);
}
// Execute a query in "logical" order starting at |queryRoot|. This means
// walking the lines boxes for each layout object in layout tree (pre)order.
static void LogicalQuery(LayoutObject* query_root,
QueryData* query_data,
ProcessTextFragmentCallback fragment_callback) {
if (!query_root)
return;
// Walk the layout tree in pre-order, starting at the specified root, and
// run the query for each text node.
Vector<SVGInlineTextBox*> text_boxes;
for (LayoutObject* layout_object = query_root->SlowFirstChild();
layout_object;
layout_object = layout_object->NextInPreOrder(query_root)) {
if (!layout_object->IsSVGInlineText())
continue;
LineLayoutSVGInlineText text_line_layout =
LineLayoutSVGInlineText(ToLayoutSVGInlineText(layout_object));
DCHECK(text_line_layout.Style());
// TODO(fs): Allow filtering the search earlier, since we should be
// able to trivially reject (prune) at least some of the queries.
CollectTextBoxesInLogicalOrder(text_line_layout, text_boxes);
for (const SVGInlineTextBox* text_box : text_boxes) {
if (QueryTextBox(query_data, text_box, fragment_callback))
return;
query_data->current_offset += text_box->Len();
}
}
}
static bool MapStartEndPositionsIntoFragmentCoordinates(
const QueryData* query_data,
const SVGTextFragment& fragment,
int& start_position,
int& end_position) {
unsigned box_start = query_data->current_offset;
// Make <startPosition, endPosition> offsets relative to the current text box.
start_position -= box_start;
end_position -= box_start;
// Reuse the same logic used for text selection & painting, to map our
// query start/length into start/endPositions of the current text fragment.
return query_data->text_box->MapStartEndPositionsIntoFragmentCoordinates(
fragment, start_position, end_position);
}
// numberOfCharacters() implementation
static bool NumberOfCharactersCallback(QueryData*, const SVGTextFragment&) {
// no-op
return false;
}
unsigned SVGTextQuery::NumberOfCharacters() const {
QueryData data;
LogicalQuery(query_root_layout_object_, &data, NumberOfCharactersCallback);
return data.current_offset;
}
// textLength() implementation
struct TextLengthData : QueryData {
TextLengthData() : text_length(0) {}
float text_length;
};
static bool TextLengthCallback(QueryData* query_data,
const SVGTextFragment& fragment) {
TextLengthData* data = static_cast<TextLengthData*>(query_data);
data->text_length +=
query_data->is_vertical_text ? fragment.height : fragment.width;
return false;
}
float SVGTextQuery::TextLength() const {
TextLengthData data;
LogicalQuery(query_root_layout_object_, &data, TextLengthCallback);
return data.text_length;
}
using MetricsList = Vector<SVGTextMetrics>;
MetricsList::const_iterator FindMetricsForCharacter(
const MetricsList& metrics_list,
const SVGTextFragment& fragment,
unsigned start_in_fragment) {
// Find the text metrics cell that starts at or contains the character at
// |startInFragment|.
MetricsList::const_iterator metrics =
metrics_list.begin() + fragment.metrics_list_offset;
unsigned fragment_offset = 0;
while (fragment_offset < fragment.length) {
fragment_offset += metrics->length();
if (start_in_fragment < fragment_offset)
break;
++metrics;
}
DCHECK_LE(metrics, metrics_list.end());
return metrics;
}
static float CalculateGlyphRange(const QueryData* query_data,
const SVGTextFragment& fragment,
unsigned start,
unsigned end) {
const MetricsList& metrics_list = query_data->text_line_layout.MetricsList();
auto* metrics = FindMetricsForCharacter(metrics_list, fragment, start);
auto* end_metrics = FindMetricsForCharacter(metrics_list, fragment, end);
float glyph_range = 0;
for (; metrics != end_metrics; ++metrics)
glyph_range += metrics->Advance(query_data->is_vertical_text);
return glyph_range;
}
// subStringLength() implementation
struct SubStringLengthData : QueryData {
SubStringLengthData(unsigned query_start_position, unsigned query_length)
: start_position(query_start_position),
length(query_length),
sub_string_length(0) {}
unsigned start_position;
unsigned length;
float sub_string_length;
};
static bool SubStringLengthCallback(QueryData* query_data,
const SVGTextFragment& fragment) {
SubStringLengthData* data = static_cast<SubStringLengthData*>(query_data);
int start_position = data->start_position;
int end_position = start_position + data->length;
if (!MapStartEndPositionsIntoFragmentCoordinates(
query_data, fragment, start_position, end_position))
return false;
data->sub_string_length +=
CalculateGlyphRange(query_data, fragment, start_position, end_position);
return false;
}
float SVGTextQuery::SubStringLength(unsigned start_position,
unsigned length) const {
SubStringLengthData data(start_position, length);
LogicalQuery(query_root_layout_object_, &data, SubStringLengthCallback);
return data.sub_string_length;
}
// startPositionOfCharacter() implementation
struct StartPositionOfCharacterData : QueryData {
StartPositionOfCharacterData(unsigned query_position)
: position(query_position) {}
unsigned position;
FloatPoint start_position;
};
static FloatPoint LogicalGlyphPositionToPhysical(
const QueryData* query_data,
const SVGTextFragment& fragment,
float logical_glyph_offset) {
float physical_glyph_offset = logical_glyph_offset;
if (!query_data->text_box->IsLeftToRightDirection()) {
float fragment_extent =
query_data->is_vertical_text ? fragment.height : fragment.width;
physical_glyph_offset = fragment_extent - logical_glyph_offset;
}
FloatPoint glyph_position(fragment.x, fragment.y);
if (query_data->is_vertical_text)
glyph_position.Move(0, physical_glyph_offset);
else
glyph_position.Move(physical_glyph_offset, 0);
return glyph_position;
}
static FloatPoint CalculateGlyphPosition(const QueryData* query_data,
const SVGTextFragment& fragment,
unsigned offset_in_fragment) {
float glyph_offset_in_direction =
CalculateGlyphRange(query_data, fragment, 0, offset_in_fragment);
FloatPoint glyph_position = LogicalGlyphPositionToPhysical(
query_data, fragment, glyph_offset_in_direction);
if (fragment.IsTransformed()) {
AffineTransform fragment_transform = fragment.BuildFragmentTransform(
SVGTextFragment::kTransformIgnoringTextLength);
glyph_position = fragment_transform.MapPoint(glyph_position);
}
return glyph_position;
}
static bool StartPositionOfCharacterCallback(QueryData* query_data,
const SVGTextFragment& fragment) {
StartPositionOfCharacterData* data =
static_cast<StartPositionOfCharacterData*>(query_data);
int start_position = data->position;
int end_position = start_position + 1;
if (!MapStartEndPositionsIntoFragmentCoordinates(
query_data, fragment, start_position, end_position))
return false;
data->start_position =
CalculateGlyphPosition(query_data, fragment, start_position);
return true;
}
FloatPoint SVGTextQuery::StartPositionOfCharacter(unsigned position) const {
StartPositionOfCharacterData data(position);
LogicalQuery(query_root_layout_object_, &data,
StartPositionOfCharacterCallback);
return data.start_position;
}
// endPositionOfCharacter() implementation
struct EndPositionOfCharacterData : QueryData {
EndPositionOfCharacterData(unsigned query_position)
: position(query_position) {}
unsigned position;
FloatPoint end_position;
};
static bool EndPositionOfCharacterCallback(QueryData* query_data,
const SVGTextFragment& fragment) {
EndPositionOfCharacterData* data =
static_cast<EndPositionOfCharacterData*>(query_data);
int start_position = data->position;
int end_position = start_position + 1;
if (!MapStartEndPositionsIntoFragmentCoordinates(
query_data, fragment, start_position, end_position))
return false;
data->end_position =
CalculateGlyphPosition(query_data, fragment, end_position);
return true;
}
FloatPoint SVGTextQuery::EndPositionOfCharacter(unsigned position) const {
EndPositionOfCharacterData data(position);
LogicalQuery(query_root_layout_object_, &data,
EndPositionOfCharacterCallback);
return data.end_position;
}
// rotationOfCharacter() implementation
struct RotationOfCharacterData : QueryData {
RotationOfCharacterData(unsigned query_position)
: position(query_position), rotation(0) {}
unsigned position;
float rotation;
};
static bool RotationOfCharacterCallback(QueryData* query_data,
const SVGTextFragment& fragment) {
RotationOfCharacterData* data =
static_cast<RotationOfCharacterData*>(query_data);
int start_position = data->position;
int end_position = start_position + 1;
if (!MapStartEndPositionsIntoFragmentCoordinates(
query_data, fragment, start_position, end_position))
return false;
if (!fragment.IsTransformed()) {
data->rotation = 0;
} else {
AffineTransform fragment_transform = fragment.BuildFragmentTransform(
SVGTextFragment::kTransformIgnoringTextLength);
fragment_transform.Scale(1 / fragment_transform.XScale(),
1 / fragment_transform.YScale());
data->rotation = clampTo<float>(
rad2deg(atan2(fragment_transform.B(), fragment_transform.A())));
}
return true;
}
float SVGTextQuery::RotationOfCharacter(unsigned position) const {
RotationOfCharacterData data(position);
LogicalQuery(query_root_layout_object_, &data, RotationOfCharacterCallback);
return data.rotation;
}
// extentOfCharacter() implementation
struct ExtentOfCharacterData : QueryData {
ExtentOfCharacterData(unsigned query_position) : position(query_position) {}
unsigned position;
FloatRect extent;
};
static FloatRect PhysicalGlyphExtents(const QueryData* query_data,
const SVGTextMetrics& metrics,
const FloatPoint& glyph_position) {
FloatRect glyph_extents(glyph_position, metrics.Extents());
// If RTL, adjust the starting point to align with the LHS of the glyph
// bounding box.
if (!query_data->text_box->IsLeftToRightDirection()) {
if (query_data->is_vertical_text)
glyph_extents.Move(0, -glyph_extents.Height());
else
glyph_extents.Move(-glyph_extents.Width(), 0);
}
return glyph_extents;
}
static inline FloatRect CalculateGlyphBoundaries(
const QueryData* query_data,
const SVGTextFragment& fragment,
int start_position) {
const float scaling_factor = query_data->text_line_layout.ScalingFactor();
DCHECK(scaling_factor);
const SimpleFontData* font_data =
query_data->text_line_layout.ScaledFont().PrimaryFont();
DCHECK(font_data);
if (!font_data)
return FloatRect();
const float baseline =
font_data->GetFontMetrics().FloatAscent() / scaling_factor;
float glyph_offset_in_direction =
CalculateGlyphRange(query_data, fragment, 0, start_position);
FloatPoint glyph_position = LogicalGlyphPositionToPhysical(
query_data, fragment, glyph_offset_in_direction);
glyph_position.Move(0, -baseline);
// Use the SVGTextMetrics computed by SVGTextMetricsBuilder.
const MetricsList& metrics_list = query_data->text_line_layout.MetricsList();
auto* metrics =
FindMetricsForCharacter(metrics_list, fragment, start_position);
FloatRect extent = PhysicalGlyphExtents(query_data, *metrics, glyph_position);
if (fragment.IsTransformed()) {
AffineTransform fragment_transform = fragment.BuildFragmentTransform(
SVGTextFragment::kTransformIgnoringTextLength);
extent = fragment_transform.MapRect(extent);
}
return extent;
}
static bool ExtentOfCharacterCallback(QueryData* query_data,
const SVGTextFragment& fragment) {
ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(query_data);
int start_position = data->position;
int end_position = start_position + 1;
if (!MapStartEndPositionsIntoFragmentCoordinates(
query_data, fragment, start_position, end_position))
return false;
data->extent = CalculateGlyphBoundaries(query_data, fragment, start_position);
return true;
}
FloatRect SVGTextQuery::ExtentOfCharacter(unsigned position) const {
ExtentOfCharacterData data(position);
LogicalQuery(query_root_layout_object_, &data, ExtentOfCharacterCallback);
return data.extent;
}
// characterNumberAtPosition() implementation
struct CharacterNumberAtPositionData : QueryData {
CharacterNumberAtPositionData(const FloatPoint& query_position)
: position(query_position),
hit_layout_item(nullptr),
offset_in_text_node(0) {}
int CharacterNumberWithin(const LayoutObject* query_root) const;
FloatPoint position;
LineLayoutItem hit_layout_item;
int offset_in_text_node;
};
int CharacterNumberAtPositionData::CharacterNumberWithin(
const LayoutObject* query_root) const {
// http://www.w3.org/TR/SVG/single-page.html#text-__svg__SVGTextContentElement__getCharNumAtPosition
// "If no such character exists, a value of -1 is returned."
if (!hit_layout_item)
return -1;
DCHECK(query_root);
int character_number = offset_in_text_node;
// Accumulate the lengths of all the text nodes preceding the target layout
// object within the queried root, to get the complete character number.
for (LineLayoutItem layout_item =
hit_layout_item.PreviousInPreOrder(query_root);
layout_item; layout_item = layout_item.PreviousInPreOrder(query_root)) {
if (!layout_item.IsSVGInlineText())
continue;
character_number +=
LineLayoutSVGInlineText(layout_item).ResolvedTextLength();
}
return character_number;
}
static unsigned LogicalOffsetInTextNode(
LineLayoutSVGInlineText text_line_layout,
const SVGInlineTextBox* start_text_box,
unsigned fragment_offset) {
Vector<SVGInlineTextBox*> text_boxes;
CollectTextBoxesInLogicalOrder(text_line_layout, text_boxes);
DCHECK(start_text_box);
size_t index = text_boxes.Find(start_text_box);
DCHECK_NE(index, kNotFound);
unsigned offset = fragment_offset;
while (index) {
--index;
offset += text_boxes[index]->Len();
}
return offset;
}
static bool CharacterNumberAtPositionCallback(QueryData* query_data,
const SVGTextFragment& fragment) {
CharacterNumberAtPositionData* data =
static_cast<CharacterNumberAtPositionData*>(query_data);
const float scaling_factor = data->text_line_layout.ScalingFactor();
DCHECK(scaling_factor);
const SimpleFontData* font_data =
data->text_line_layout.ScaledFont().PrimaryFont();
DCHECK(font_data);
if (!font_data)
return false;
const float baseline =
font_data->GetFontMetrics().FloatAscent() / scaling_factor;
// Test the query point against the bounds of the entire fragment first.
if (!fragment.BoundingBox(baseline).Contains(data->position))
return false;
AffineTransform fragment_transform = fragment.BuildFragmentTransform(
SVGTextFragment::kTransformIgnoringTextLength);
// Iterate through the glyphs in this fragment, and check if their extents
// contain the query point.
MetricsList::const_iterator metrics =
data->text_line_layout.MetricsList().begin() +
fragment.metrics_list_offset;
unsigned fragment_offset = 0;
float glyph_offset = 0;
while (fragment_offset < fragment.length) {
FloatPoint glyph_position =
LogicalGlyphPositionToPhysical(data, fragment, glyph_offset);
glyph_position.Move(0, -baseline);
FloatRect extent = fragment_transform.MapRect(
PhysicalGlyphExtents(data, *metrics, glyph_position));
if (extent.Contains(data->position)) {
// Compute the character offset of the glyph within the text node.
unsigned offset_in_box = fragment.character_offset -
query_data->text_box->Start() + fragment_offset;
data->offset_in_text_node = LogicalOffsetInTextNode(
query_data->text_line_layout, query_data->text_box, offset_in_box);
data->hit_layout_item = LineLayoutItem(data->text_line_layout);
return true;
}
fragment_offset += metrics->length();
glyph_offset += metrics->Advance(data->is_vertical_text);
++metrics;
}
return false;
}
int SVGTextQuery::CharacterNumberAtPosition(const FloatPoint& position) const {
CharacterNumberAtPositionData data(position);
SpatialQuery(query_root_layout_object_, &data,
CharacterNumberAtPositionCallback);
return data.CharacterNumberWithin(query_root_layout_object_);
}
} // namespace blink