Move parseFontFaceDescriptor to CSSPropertyParser.cpp
[chromium-blink-merge.git] / third_party / WebKit / Source / core / layout / svg / SVGTextChunkBuilder.cpp
blobc1e3c195c2eae10d42dec79f53a197e608b4e5fb
1 /*
2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
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.
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.
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.
20 #include "config.h"
21 #include "core/layout/svg/SVGTextChunkBuilder.h"
23 #include "core/layout/svg/LayoutSVGInlineText.h"
24 #include "core/layout/svg/line/SVGInlineTextBox.h"
25 #include "core/svg/SVGLengthContext.h"
26 #include "core/svg/SVGTextContentElement.h"
27 #include "platform/transforms/AffineTransform.h"
29 namespace blink {
31 namespace {
33 float calculateTextAnchorShift(const ComputedStyle& style, float length)
35 bool isLTR = style.isLeftToRightDirection();
36 switch (style.svgStyle().textAnchor()) {
37 default:
38 ASSERT_NOT_REACHED();
39 case TA_START:
40 return isLTR ? 0 : -length;
41 case TA_MIDDLE:
42 return -length / 2;
43 case TA_END:
44 return isLTR ? -length : 0;
48 bool needsTextAnchorAdjustment(const ComputedStyle& style)
50 bool isLTR = style.isLeftToRightDirection();
51 switch (style.svgStyle().textAnchor()) {
52 default:
53 ASSERT_NOT_REACHED();
54 case TA_START:
55 return !isLTR;
56 case TA_MIDDLE:
57 return true;
58 case TA_END:
59 return isLTR;
63 class ChunkLengthAccumulator {
64 public:
65 ChunkLengthAccumulator(bool isVertical)
66 : m_numCharacters(0)
67 , m_length(0)
68 , m_isVertical(isVertical)
72 typedef Vector<SVGInlineTextBox*>::const_iterator BoxListConstIterator;
74 void processRange(BoxListConstIterator boxStart, BoxListConstIterator boxEnd);
75 void reset()
77 m_numCharacters = 0;
78 m_length = 0;
81 float length() const { return m_length; }
82 unsigned numCharacters() const { return m_numCharacters; }
84 private:
85 unsigned m_numCharacters;
86 float m_length;
87 const bool m_isVertical;
90 void ChunkLengthAccumulator::processRange(BoxListConstIterator boxStart, BoxListConstIterator boxEnd)
92 SVGTextFragment* lastFragment = nullptr;
93 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) {
94 for (SVGTextFragment& fragment : (*boxIter)->textFragments()) {
95 m_numCharacters += fragment.length;
97 if (m_isVertical)
98 m_length += fragment.height;
99 else
100 m_length += fragment.width;
102 if (!lastFragment) {
103 lastFragment = &fragment;
104 continue;
107 // Respect gap between chunks.
108 if (m_isVertical)
109 m_length += fragment.y - (lastFragment->y + lastFragment->height);
110 else
111 m_length += fragment.x - (lastFragment->x + lastFragment->width);
113 lastFragment = &fragment;
120 SVGTextChunkBuilder::SVGTextChunkBuilder()
124 void SVGTextChunkBuilder::processTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes)
126 if (lineLayoutBoxes.isEmpty())
127 return;
129 bool foundStart = false;
130 auto boxIter = lineLayoutBoxes.begin();
131 auto endBox = lineLayoutBoxes.end();
132 auto chunkStartBox = boxIter;
133 for (; boxIter != endBox; ++boxIter) {
134 if (!(*boxIter)->startsNewTextChunk())
135 continue;
137 if (!foundStart) {
138 foundStart = true;
139 } else {
140 ASSERT(boxIter != chunkStartBox);
141 handleTextChunk(chunkStartBox, boxIter);
143 chunkStartBox = boxIter;
146 if (!foundStart)
147 return;
149 if (boxIter != chunkStartBox)
150 handleTextChunk(chunkStartBox, boxIter);
153 SVGTextPathChunkBuilder::SVGTextPathChunkBuilder()
154 : SVGTextChunkBuilder()
155 , m_totalLength(0)
156 , m_totalCharacters(0)
157 , m_totalTextAnchorShift(0)
161 void SVGTextPathChunkBuilder::handleTextChunk(BoxListConstIterator boxStart, BoxListConstIterator boxEnd)
163 const ComputedStyle& style = (*boxStart)->layoutObject().styleRef();
165 ChunkLengthAccumulator lengthAccumulator(style.svgStyle().isVerticalWritingMode());
166 lengthAccumulator.processRange(boxStart, boxEnd);
168 // Handle text-anchor as additional start offset for text paths.
169 m_totalTextAnchorShift += calculateTextAnchorShift(style, lengthAccumulator.length());
171 m_totalLength += lengthAccumulator.length();
172 m_totalCharacters += lengthAccumulator.numCharacters();
175 static void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
177 spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
179 if (isVerticalText)
180 spacingAndGlyphsTransform.scaleNonUniform(1, scale);
181 else
182 spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
184 spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
187 void SVGTextChunkBuilder::handleTextChunk(BoxListConstIterator boxStart, BoxListConstIterator boxEnd)
189 ASSERT(*boxStart);
191 const LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText((*boxStart)->layoutObject());
192 const ComputedStyle& style = textLayoutObject.styleRef();
194 // Handle 'lengthAdjust' property.
195 float desiredTextLength = 0;
196 SVGLengthAdjustType lengthAdjust = SVGLengthAdjustUnknown;
197 if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromLayoutObject(textLayoutObject.parent())) {
198 lengthAdjust = textContentElement->lengthAdjust()->currentValue()->enumValue();
200 SVGLengthContext lengthContext(textContentElement);
201 if (textContentElement->textLengthIsSpecifiedByUser())
202 desiredTextLength = textContentElement->textLength()->currentValue()->value(lengthContext);
203 else
204 desiredTextLength = 0;
207 bool processTextLength = desiredTextLength > 0;
208 bool processTextAnchor = needsTextAnchorAdjustment(style);
209 if (!processTextAnchor && !processTextLength)
210 return;
212 bool isVerticalText = style.svgStyle().isVerticalWritingMode();
214 // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
215 ChunkLengthAccumulator lengthAccumulator(isVerticalText);
216 lengthAccumulator.processRange(boxStart, boxEnd);
218 if (processTextLength) {
219 float chunkLength = lengthAccumulator.length();
220 if (lengthAdjust == SVGLengthAdjustSpacing) {
221 float textLengthShift = (desiredTextLength - chunkLength) / lengthAccumulator.numCharacters();
222 unsigned atCharacter = 0;
223 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) {
224 Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments();
225 if (fragments.isEmpty())
226 continue;
227 processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
230 // Fragments have been adjusted, we have to recalculate the chunk
231 // length, to be able to apply the text-anchor shift.
232 if (processTextAnchor) {
233 lengthAccumulator.reset();
234 lengthAccumulator.processRange(boxStart, boxEnd);
236 } else {
237 ASSERT(lengthAdjust == SVGLengthAdjustSpacingAndGlyphs);
238 float textLengthScale = desiredTextLength / chunkLength;
239 AffineTransform spacingAndGlyphsTransform;
241 bool foundFirstFragment = false;
242 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) {
243 SVGInlineTextBox* textBox = *boxIter;
244 Vector<SVGTextFragment>& fragments = textBox->textFragments();
245 if (fragments.isEmpty())
246 continue;
248 if (!foundFirstFragment) {
249 foundFirstFragment = true;
250 buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform);
253 applyTextLengthScaleAdjustment(spacingAndGlyphsTransform, fragments);
258 if (!processTextAnchor)
259 return;
261 float textAnchorShift = calculateTextAnchorShift(style, lengthAccumulator.length());
262 for (auto boxIter = boxStart; boxIter != boxEnd; ++boxIter) {
263 Vector<SVGTextFragment>& fragments = (*boxIter)->textFragments();
264 if (fragments.isEmpty())
265 continue;
266 processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
270 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
272 for (SVGTextFragment& fragment : fragments) {
273 if (isVerticalText)
274 fragment.y += textLengthShift * atCharacter;
275 else
276 fragment.x += textLengthShift * atCharacter;
278 atCharacter += fragment.length;
282 void SVGTextChunkBuilder::applyTextLengthScaleAdjustment(const AffineTransform& spacingAndGlyphsTransform, Vector<SVGTextFragment>& fragments)
284 for (SVGTextFragment& fragment : fragments) {
285 ASSERT(fragment.lengthAdjustTransform.isIdentity());
286 fragment.lengthAdjustTransform = spacingAndGlyphsTransform;
290 void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
292 for (SVGTextFragment& fragment : fragments) {
293 if (isVerticalText)
294 fragment.y += textAnchorShift;
295 else
296 fragment.x += textAnchorShift;