1 /*
2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "third_party/blink/renderer/core/layout/svg/svg_text_chunk_builder.h"
21
22 #include "third_party/blink/renderer/core/layout/api/line_layout_svg_inline_text.h"
23 #include "third_party/blink/renderer/core/layout/svg/line/svg_inline_text_box.h"
24 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
25 #include "third_party/blink/renderer/core/svg/svg_text_content_element.h"
26
27 namespace blink {
28
CalculateTextAnchorShift(const ComputedStyle & style,float length)29 float CalculateTextAnchorShift(const ComputedStyle& style, float length) {
30 bool is_ltr = style.IsLeftToRightDirection();
31 switch (style.SvgStyle().TextAnchor()) {
32 default:
33 NOTREACHED();
34 FALLTHROUGH;
35 case TA_START:
36 return is_ltr ? 0 : -length;
37 case TA_MIDDLE:
38 return -length / 2;
39 case TA_END:
40 return is_ltr ? -length : 0;
41 }
42 }
43
44 namespace {
45
NeedsTextAnchorAdjustment(const ComputedStyle & style)46 bool NeedsTextAnchorAdjustment(const ComputedStyle& style) {
47 bool is_ltr = style.IsLeftToRightDirection();
48 switch (style.SvgStyle().TextAnchor()) {
49 default:
50 NOTREACHED();
51 FALLTHROUGH;
52 case TA_START:
53 return !is_ltr;
54 case TA_MIDDLE:
55 return true;
56 case TA_END:
57 return is_ltr;
58 }
59 }
60
61 class ChunkLengthAccumulator {
62 public:
ChunkLengthAccumulator(bool is_vertical)63 ChunkLengthAccumulator(bool is_vertical)
64 : num_characters_(0), length_(0), is_vertical_(is_vertical) {}
65
66 typedef Vector<SVGInlineTextBox*>::const_iterator BoxListConstIterator;
67
68 void ProcessRange(BoxListConstIterator box_start,
69 BoxListConstIterator box_end);
Reset()70 void Reset() {
71 num_characters_ = 0;
72 length_ = 0;
73 }
74
length() const75 float length() const { return length_; }
NumCharacters() const76 unsigned NumCharacters() const { return num_characters_; }
77
78 private:
79 unsigned num_characters_;
80 float length_;
81 const bool is_vertical_;
82 };
83
ProcessRange(BoxListConstIterator box_start,BoxListConstIterator box_end)84 void ChunkLengthAccumulator::ProcessRange(BoxListConstIterator box_start,
85 BoxListConstIterator box_end) {
86 SVGTextFragment* last_fragment = nullptr;
87 for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
88 for (SVGTextFragment& fragment : (*box_iter)->TextFragments()) {
89 num_characters_ += fragment.length;
90
91 if (is_vertical_)
92 length_ += fragment.height;
93 else
94 length_ += fragment.width;
95
96 if (!last_fragment) {
97 last_fragment = &fragment;
98 continue;
99 }
100
101 // Respect gap between chunks.
102 if (is_vertical_)
103 length_ += fragment.y - (last_fragment->y + last_fragment->height);
104 else
105 length_ += fragment.x - (last_fragment->x + last_fragment->width);
106
107 last_fragment = &fragment;
108 }
109 }
110 }
111
112 } // namespace
113
114 SVGTextChunkBuilder::SVGTextChunkBuilder() = default;
115
ProcessTextChunks(const Vector<SVGInlineTextBox * > & line_layout_boxes)116 void SVGTextChunkBuilder::ProcessTextChunks(
117 const Vector<SVGInlineTextBox*>& line_layout_boxes) {
118 if (line_layout_boxes.IsEmpty())
119 return;
120
121 bool found_start = false;
122 auto* const* box_iter = line_layout_boxes.begin();
123 auto* const* end_box = line_layout_boxes.end();
124 auto* const* chunk_start_box = box_iter;
125 for (; box_iter != end_box; ++box_iter) {
126 if (!(*box_iter)->StartsNewTextChunk())
127 continue;
128
129 if (!found_start) {
130 found_start = true;
131 } else {
132 DCHECK_NE(box_iter, chunk_start_box);
133 HandleTextChunk(chunk_start_box, box_iter);
134 }
135 chunk_start_box = box_iter;
136 }
137
138 if (!found_start)
139 return;
140
141 if (box_iter != chunk_start_box)
142 HandleTextChunk(chunk_start_box, box_iter);
143 }
144
SVGTextPathChunkBuilder()145 SVGTextPathChunkBuilder::SVGTextPathChunkBuilder()
146 : SVGTextChunkBuilder(), total_length_(0), total_characters_(0) {}
147
HandleTextChunk(BoxListConstIterator box_start,BoxListConstIterator box_end)148 void SVGTextPathChunkBuilder::HandleTextChunk(BoxListConstIterator box_start,
149 BoxListConstIterator box_end) {
150 const ComputedStyle& style = (*box_start)->GetLineLayoutItem().StyleRef();
151
152 ChunkLengthAccumulator length_accumulator(!style.IsHorizontalWritingMode());
153 length_accumulator.ProcessRange(box_start, box_end);
154
155 total_length_ += length_accumulator.length();
156 total_characters_ += length_accumulator.NumCharacters();
157 }
158
ComputeTextLengthBias(const SVGTextFragment & fragment,float scale)159 static float ComputeTextLengthBias(const SVGTextFragment& fragment,
160 float scale) {
161 float initial_position = fragment.is_vertical ? fragment.y : fragment.x;
162 return initial_position + scale * -initial_position;
163 }
164
HandleTextChunk(BoxListConstIterator box_start,BoxListConstIterator box_end)165 void SVGTextChunkBuilder::HandleTextChunk(BoxListConstIterator box_start,
166 BoxListConstIterator box_end) {
167 DCHECK(*box_start);
168
169 const LineLayoutSVGInlineText text_line_layout =
170 LineLayoutSVGInlineText((*box_start)->GetLineLayoutItem());
171 const ComputedStyle& style = text_line_layout.StyleRef();
172
173 // Handle 'lengthAdjust' property.
174 float desired_text_length = 0;
175 SVGLengthAdjustType length_adjust = kSVGLengthAdjustUnknown;
176 if (SVGTextContentElement* text_content_element =
177 SVGTextContentElement::ElementFromLineLayoutItem(
178 text_line_layout.Parent())) {
179 length_adjust =
180 text_content_element->lengthAdjust()->CurrentValue()->EnumValue();
181
182 SVGLengthContext length_context(text_content_element);
183 if (text_content_element->TextLengthIsSpecifiedByUser())
184 desired_text_length =
185 text_content_element->textLength()->CurrentValue()->Value(
186 length_context);
187 else
188 desired_text_length = 0;
189 }
190
191 bool process_text_length = desired_text_length > 0;
192 bool process_text_anchor = NeedsTextAnchorAdjustment(style);
193 if (!process_text_anchor && !process_text_length)
194 return;
195
196 bool is_vertical_text = !style.IsHorizontalWritingMode();
197
198 // Calculate absolute length of whole text chunk (starting from text box
199 // 'start', spanning 'length' text boxes).
200 ChunkLengthAccumulator length_accumulator(is_vertical_text);
201 length_accumulator.ProcessRange(box_start, box_end);
202
203 if (process_text_length) {
204 float chunk_length = length_accumulator.length();
205 if (length_adjust == kSVGLengthAdjustSpacing) {
206 float text_length_shift = 0;
207 if (length_accumulator.NumCharacters() > 1) {
208 text_length_shift = desired_text_length - chunk_length;
209 text_length_shift /= length_accumulator.NumCharacters() - 1;
210 }
211 unsigned at_character = 0;
212 for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
213 Vector<SVGTextFragment>& fragments = (*box_iter)->TextFragments();
214 if (fragments.IsEmpty())
215 continue;
216 ProcessTextLengthSpacingCorrection(is_vertical_text, text_length_shift,
217 fragments, at_character);
218 }
219
220 // Fragments have been adjusted, we have to recalculate the chunk
221 // length, to be able to apply the text-anchor shift.
222 if (process_text_anchor) {
223 length_accumulator.Reset();
224 length_accumulator.ProcessRange(box_start, box_end);
225 }
226 } else {
227 DCHECK_EQ(length_adjust, kSVGLengthAdjustSpacingAndGlyphs);
228 float text_length_scale = desired_text_length / chunk_length;
229 float text_length_bias = 0;
230
231 bool found_first_fragment = false;
232 for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
233 SVGInlineTextBox* text_box = *box_iter;
234 Vector<SVGTextFragment>& fragments = text_box->TextFragments();
235 if (fragments.IsEmpty())
236 continue;
237
238 if (!found_first_fragment) {
239 found_first_fragment = true;
240 text_length_bias =
241 ComputeTextLengthBias(fragments.front(), text_length_scale);
242 }
243
244 ApplyTextLengthScaleAdjustment(text_length_scale, text_length_bias,
245 fragments);
246 }
247 }
248 }
249
250 if (!process_text_anchor)
251 return;
252
253 float text_anchor_shift =
254 CalculateTextAnchorShift(style, length_accumulator.length());
255 for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
256 Vector<SVGTextFragment>& fragments = (*box_iter)->TextFragments();
257 if (fragments.IsEmpty())
258 continue;
259 ProcessTextAnchorCorrection(is_vertical_text, text_anchor_shift, fragments);
260 }
261 }
262
ProcessTextLengthSpacingCorrection(bool is_vertical_text,float text_length_shift,Vector<SVGTextFragment> & fragments,unsigned & at_character)263 void SVGTextChunkBuilder::ProcessTextLengthSpacingCorrection(
264 bool is_vertical_text,
265 float text_length_shift,
266 Vector<SVGTextFragment>& fragments,
267 unsigned& at_character) {
268 for (SVGTextFragment& fragment : fragments) {
269 if (is_vertical_text)
270 fragment.y += text_length_shift * at_character;
271 else
272 fragment.x += text_length_shift * at_character;
273
274 at_character += fragment.length;
275 }
276 }
277
ApplyTextLengthScaleAdjustment(float text_length_scale,float text_length_bias,Vector<SVGTextFragment> & fragments)278 void SVGTextChunkBuilder::ApplyTextLengthScaleAdjustment(
279 float text_length_scale,
280 float text_length_bias,
281 Vector<SVGTextFragment>& fragments) {
282 for (SVGTextFragment& fragment : fragments) {
283 DCHECK_EQ(fragment.length_adjust_scale, 1u);
284 fragment.length_adjust_scale = text_length_scale;
285 fragment.length_adjust_bias = text_length_bias;
286 }
287 }
288
ProcessTextAnchorCorrection(bool is_vertical_text,float text_anchor_shift,Vector<SVGTextFragment> & fragments)289 void SVGTextChunkBuilder::ProcessTextAnchorCorrection(
290 bool is_vertical_text,
291 float text_anchor_shift,
292 Vector<SVGTextFragment>& fragments) {
293 for (SVGTextFragment& fragment : fragments) {
294 if (is_vertical_text)
295 fragment.y += text_anchor_shift;
296 else
297 fragment.x += text_anchor_shift;
298 }
299 }
300
301 } // namespace blink
302