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