blob: 5c3e12cf09699b15f5b83cca958f641c19b7a907 [file] [log] [blame]
/*
* Copyright (C) Research In Motion Limited 2010. 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_chunk_builder.h"
#include "third_party/blink/renderer/core/layout/api/line_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/svg/svg_length_context.h"
#include "third_party/blink/renderer/core/svg/svg_text_content_element.h"
namespace blink {
namespace {
float CalculateTextAnchorShift(const ComputedStyle& style, float length) {
bool is_ltr = style.IsLeftToRightDirection();
switch (style.SvgStyle().TextAnchor()) {
default:
NOTREACHED();
FALLTHROUGH;
case TA_START:
return is_ltr ? 0 : -length;
case TA_MIDDLE:
return -length / 2;
case TA_END:
return is_ltr ? -length : 0;
}
}
bool NeedsTextAnchorAdjustment(const ComputedStyle& style) {
bool is_ltr = style.IsLeftToRightDirection();
switch (style.SvgStyle().TextAnchor()) {
default:
NOTREACHED();
FALLTHROUGH;
case TA_START:
return !is_ltr;
case TA_MIDDLE:
return true;
case TA_END:
return is_ltr;
}
}
class ChunkLengthAccumulator {
public:
ChunkLengthAccumulator(bool is_vertical)
: num_characters_(0), length_(0), is_vertical_(is_vertical) {}
typedef Vector<SVGInlineTextBox*>::const_iterator BoxListConstIterator;
void ProcessRange(BoxListConstIterator box_start,
BoxListConstIterator box_end);
void Reset() {
num_characters_ = 0;
length_ = 0;
}
float length() const { return length_; }
unsigned NumCharacters() const { return num_characters_; }
private:
unsigned num_characters_;
float length_;
const bool is_vertical_;
};
void ChunkLengthAccumulator::ProcessRange(BoxListConstIterator box_start,
BoxListConstIterator box_end) {
SVGTextFragment* last_fragment = nullptr;
for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
for (SVGTextFragment& fragment : (*box_iter)->TextFragments()) {
num_characters_ += fragment.length;
if (is_vertical_)
length_ += fragment.height;
else
length_ += fragment.width;
if (!last_fragment) {
last_fragment = &fragment;
continue;
}
// Respect gap between chunks.
if (is_vertical_)
length_ += fragment.y - (last_fragment->y + last_fragment->height);
else
length_ += fragment.x - (last_fragment->x + last_fragment->width);
last_fragment = &fragment;
}
}
}
} // namespace
SVGTextChunkBuilder::SVGTextChunkBuilder() = default;
void SVGTextChunkBuilder::ProcessTextChunks(
const Vector<SVGInlineTextBox*>& line_layout_boxes) {
if (line_layout_boxes.IsEmpty())
return;
bool found_start = false;
auto* const* box_iter = line_layout_boxes.begin();
auto* const* end_box = line_layout_boxes.end();
auto* const* chunk_start_box = box_iter;
for (; box_iter != end_box; ++box_iter) {
if (!(*box_iter)->StartsNewTextChunk())
continue;
if (!found_start) {
found_start = true;
} else {
DCHECK_NE(box_iter, chunk_start_box);
HandleTextChunk(chunk_start_box, box_iter);
}
chunk_start_box = box_iter;
}
if (!found_start)
return;
if (box_iter != chunk_start_box)
HandleTextChunk(chunk_start_box, box_iter);
}
SVGTextPathChunkBuilder::SVGTextPathChunkBuilder()
: SVGTextChunkBuilder(),
total_length_(0),
total_characters_(0),
total_text_anchor_shift_(0) {}
void SVGTextPathChunkBuilder::HandleTextChunk(BoxListConstIterator box_start,
BoxListConstIterator box_end) {
const ComputedStyle& style = (*box_start)->GetLineLayoutItem().StyleRef();
ChunkLengthAccumulator length_accumulator(!style.IsHorizontalWritingMode());
length_accumulator.ProcessRange(box_start, box_end);
// Handle text-anchor as additional start offset for text paths.
total_text_anchor_shift_ +=
CalculateTextAnchorShift(style, length_accumulator.length());
total_length_ += length_accumulator.length();
total_characters_ += length_accumulator.NumCharacters();
}
static float ComputeTextLengthBias(const SVGTextFragment& fragment,
float scale) {
float initial_position = fragment.is_vertical ? fragment.y : fragment.x;
return initial_position + scale * -initial_position;
}
void SVGTextChunkBuilder::HandleTextChunk(BoxListConstIterator box_start,
BoxListConstIterator box_end) {
DCHECK(*box_start);
const LineLayoutSVGInlineText text_line_layout =
LineLayoutSVGInlineText((*box_start)->GetLineLayoutItem());
const ComputedStyle& style = text_line_layout.StyleRef();
// Handle 'lengthAdjust' property.
float desired_text_length = 0;
SVGLengthAdjustType length_adjust = kSVGLengthAdjustUnknown;
if (SVGTextContentElement* text_content_element =
SVGTextContentElement::ElementFromLineLayoutItem(
text_line_layout.Parent())) {
length_adjust =
text_content_element->lengthAdjust()->CurrentValue()->EnumValue();
SVGLengthContext length_context(text_content_element);
if (text_content_element->TextLengthIsSpecifiedByUser())
desired_text_length =
text_content_element->textLength()->CurrentValue()->Value(
length_context);
else
desired_text_length = 0;
}
bool process_text_length = desired_text_length > 0;
bool process_text_anchor = NeedsTextAnchorAdjustment(style);
if (!process_text_anchor && !process_text_length)
return;
bool is_vertical_text = !style.IsHorizontalWritingMode();
// Calculate absolute length of whole text chunk (starting from text box
// 'start', spanning 'length' text boxes).
ChunkLengthAccumulator length_accumulator(is_vertical_text);
length_accumulator.ProcessRange(box_start, box_end);
if (process_text_length) {
float chunk_length = length_accumulator.length();
if (length_adjust == kSVGLengthAdjustSpacing) {
float text_length_shift = 0;
if (length_accumulator.NumCharacters() > 1) {
text_length_shift = desired_text_length - chunk_length;
text_length_shift /= length_accumulator.NumCharacters() - 1;
}
unsigned at_character = 0;
for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
Vector<SVGTextFragment>& fragments = (*box_iter)->TextFragments();
if (fragments.IsEmpty())
continue;
ProcessTextLengthSpacingCorrection(is_vertical_text, text_length_shift,
fragments, at_character);
}
// Fragments have been adjusted, we have to recalculate the chunk
// length, to be able to apply the text-anchor shift.
if (process_text_anchor) {
length_accumulator.Reset();
length_accumulator.ProcessRange(box_start, box_end);
}
} else {
DCHECK_EQ(length_adjust, kSVGLengthAdjustSpacingAndGlyphs);
float text_length_scale = desired_text_length / chunk_length;
float text_length_bias = 0;
bool found_first_fragment = false;
for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
SVGInlineTextBox* text_box = *box_iter;
Vector<SVGTextFragment>& fragments = text_box->TextFragments();
if (fragments.IsEmpty())
continue;
if (!found_first_fragment) {
found_first_fragment = true;
text_length_bias =
ComputeTextLengthBias(fragments.front(), text_length_scale);
}
ApplyTextLengthScaleAdjustment(text_length_scale, text_length_bias,
fragments);
}
}
}
if (!process_text_anchor)
return;
float text_anchor_shift =
CalculateTextAnchorShift(style, length_accumulator.length());
for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
Vector<SVGTextFragment>& fragments = (*box_iter)->TextFragments();
if (fragments.IsEmpty())
continue;
ProcessTextAnchorCorrection(is_vertical_text, text_anchor_shift, fragments);
}
}
void SVGTextChunkBuilder::ProcessTextLengthSpacingCorrection(
bool is_vertical_text,
float text_length_shift,
Vector<SVGTextFragment>& fragments,
unsigned& at_character) {
for (SVGTextFragment& fragment : fragments) {
if (is_vertical_text)
fragment.y += text_length_shift * at_character;
else
fragment.x += text_length_shift * at_character;
at_character += fragment.length;
}
}
void SVGTextChunkBuilder::ApplyTextLengthScaleAdjustment(
float text_length_scale,
float text_length_bias,
Vector<SVGTextFragment>& fragments) {
for (SVGTextFragment& fragment : fragments) {
DCHECK_EQ(fragment.length_adjust_scale, 1u);
fragment.length_adjust_scale = text_length_scale;
fragment.length_adjust_bias = text_length_bias;
}
}
void SVGTextChunkBuilder::ProcessTextAnchorCorrection(
bool is_vertical_text,
float text_anchor_shift,
Vector<SVGTextFragment>& fragments) {
for (SVGTextFragment& fragment : fragments) {
if (is_vertical_text)
fragment.y += text_anchor_shift;
else
fragment.x += text_anchor_shift;
}
}
} // namespace blink