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_animated_length.h"
25 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
26 #include "third_party/blink/renderer/core/svg/svg_text_content_element.h"
27
28 namespace blink {
29
CalculateTextAnchorShift(const ComputedStyle & style,float length)30 float CalculateTextAnchorShift(const ComputedStyle& style, float length) {
31 bool is_ltr = style.IsLeftToRightDirection();
32 switch (style.SvgStyle().TextAnchor()) {
33 default:
34 NOTREACHED();
35 FALLTHROUGH;
36 case TA_START:
37 return is_ltr ? 0 : -length;
38 case TA_MIDDLE:
39 return -length / 2;
40 case TA_END:
41 return is_ltr ? -length : 0;
42 }
43 }
44
45 namespace {
46
NeedsTextAnchorAdjustment(const ComputedStyle & style)47 bool NeedsTextAnchorAdjustment(const ComputedStyle& style) {
48 bool is_ltr = style.IsLeftToRightDirection();
49 switch (style.SvgStyle().TextAnchor()) {
50 default:
51 NOTREACHED();
52 FALLTHROUGH;
53 case TA_START:
54 return !is_ltr;
55 case TA_MIDDLE:
56 return true;
57 case TA_END:
58 return is_ltr;
59 }
60 }
61
62 class ChunkLengthAccumulator {
63 public:
ChunkLengthAccumulator(bool is_vertical)64 ChunkLengthAccumulator(bool is_vertical)
65 : num_characters_(0), length_(0), is_vertical_(is_vertical) {}
66
67 typedef Vector<SVGInlineTextBox*>::const_iterator BoxListConstIterator;
68
69 void ProcessRange(BoxListConstIterator box_start,
70 BoxListConstIterator box_end);
Reset()71 void Reset() {
72 num_characters_ = 0;
73 length_ = 0;
74 }
75
length() const76 float length() const { return length_; }
NumCharacters() const77 unsigned NumCharacters() const { return num_characters_; }
78
79 private:
80 unsigned num_characters_;
81 float length_;
82 const bool is_vertical_;
83 };
84
ProcessRange(BoxListConstIterator box_start,BoxListConstIterator box_end)85 void ChunkLengthAccumulator::ProcessRange(BoxListConstIterator box_start,
86 BoxListConstIterator box_end) {
87 SVGTextFragment* last_fragment = nullptr;
88 for (auto* const* box_iter = box_start; box_iter != box_end; ++box_iter) {
89 for (SVGTextFragment& fragment : (*box_iter)->TextFragments()) {
90 num_characters_ += fragment.length;
91
92 if (is_vertical_)
93 length_ += fragment.height;
94 else
95 length_ += fragment.width;
96
97 if (!last_fragment) {
98 last_fragment = &fragment;
99 continue;
100 }
101
102 // Respect gap between chunks.
103 if (is_vertical_)
104 length_ += fragment.y - (last_fragment->y + last_fragment->height);
105 else
106 length_ += fragment.x - (last_fragment->x + last_fragment->width);
107
108 last_fragment = &fragment;
109 }
110 }
111 }
112
113 } // namespace
114
115 SVGTextChunkBuilder::SVGTextChunkBuilder() = default;
116
ProcessTextChunks(const Vector<SVGInlineTextBox * > & line_layout_boxes)117 void SVGTextChunkBuilder::ProcessTextChunks(
118 const Vector<SVGInlineTextBox*>& line_layout_boxes) {
119 if (line_layout_boxes.IsEmpty())
120 return;
121
122 bool found_start = false;
123 auto* const* box_iter = line_layout_boxes.begin();
124 auto* const* end_box = line_layout_boxes.end();
125 auto* const* chunk_start_box = box_iter;
126 for (; box_iter != end_box; ++box_iter) {
127 if (!(*box_iter)->StartsNewTextChunk())
128 continue;
129
130 if (!found_start) {
131 found_start = true;
132 } else {
133 DCHECK_NE(box_iter, chunk_start_box);
134 HandleTextChunk(chunk_start_box, box_iter);
135 }
136 chunk_start_box = box_iter;
137 }
138
139 if (!found_start)
140 return;
141
142 if (box_iter != chunk_start_box)
143 HandleTextChunk(chunk_start_box, box_iter);
144 }
145
SVGTextPathChunkBuilder()146 SVGTextPathChunkBuilder::SVGTextPathChunkBuilder()
147 : SVGTextChunkBuilder(), total_length_(0), total_characters_(0) {}
148
HandleTextChunk(BoxListConstIterator box_start,BoxListConstIterator box_end)149 void SVGTextPathChunkBuilder::HandleTextChunk(BoxListConstIterator box_start,
150 BoxListConstIterator box_end) {
151 const ComputedStyle& style = (*box_start)->GetLineLayoutItem().StyleRef();
152
153 ChunkLengthAccumulator length_accumulator(!style.IsHorizontalWritingMode());
154 length_accumulator.ProcessRange(box_start, box_end);
155
156 total_length_ += length_accumulator.length();
157 total_characters_ += length_accumulator.NumCharacters();
158 }
159
ComputeTextLengthBias(const SVGTextFragment & fragment,float scale)160 static float ComputeTextLengthBias(const SVGTextFragment& fragment,
161 float scale) {
162 float initial_position = fragment.is_vertical ? fragment.y : fragment.x;
163 return initial_position + scale * -initial_position;
164 }
165
HandleTextChunk(BoxListConstIterator box_start,BoxListConstIterator box_end)166 void SVGTextChunkBuilder::HandleTextChunk(BoxListConstIterator box_start,
167 BoxListConstIterator box_end) {
168 DCHECK(*box_start);
169
170 const LineLayoutSVGInlineText text_line_layout =
171 LineLayoutSVGInlineText((*box_start)->GetLineLayoutItem());
172 const ComputedStyle& style = text_line_layout.StyleRef();
173
174 // Handle 'lengthAdjust' property.
175 float desired_text_length = 0;
176 SVGLengthAdjustType length_adjust = kSVGLengthAdjustUnknown;
177 if (SVGTextContentElement* text_content_element =
178 SVGTextContentElement::ElementFromLineLayoutItem(
179 text_line_layout.Parent())) {
180 length_adjust = text_content_element->lengthAdjust()->CurrentEnumValue();
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