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