1/*
2 * Copyright (C) Research In Motion Limited 2010-2011. 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 "config.h"
21#include "SVGTextLayoutAttributesBuilder.h"
22
23#include "RenderChildIterator.h"
24#include "RenderSVGInline.h"
25#include "RenderSVGInlineText.h"
26#include "RenderSVGText.h"
27#include "SVGTextPositioningElement.h"
28
29namespace WebCore {
30
31SVGTextLayoutAttributesBuilder::SVGTextLayoutAttributesBuilder()
32 : m_textLength(0)
33{
34}
35
36void SVGTextLayoutAttributesBuilder::buildLayoutAttributesForTextRenderer(RenderSVGInlineText& text)
37{
38 auto* textRoot = RenderSVGText::locateRenderSVGTextAncestor(text);
39 if (!textRoot)
40 return;
41
42 if (m_textPositions.isEmpty()) {
43 m_characterDataMap.clear();
44
45 m_textLength = 0;
46 bool lastCharacterWasSpace = true;
47 collectTextPositioningElements(*textRoot, lastCharacterWasSpace);
48
49 if (!m_textLength)
50 return;
51
52 buildCharacterDataMap(*textRoot);
53 }
54
55 m_metricsBuilder.buildMetricsAndLayoutAttributes(*textRoot, &text, m_characterDataMap);
56}
57
58bool SVGTextLayoutAttributesBuilder::buildLayoutAttributesForForSubtree(RenderSVGText& textRoot)
59{
60 m_characterDataMap.clear();
61
62 if (m_textPositions.isEmpty()) {
63 m_textLength = 0;
64 bool lastCharacterWasSpace = true;
65 collectTextPositioningElements(textRoot, lastCharacterWasSpace);
66 }
67
68 if (!m_textLength)
69 return false;
70
71 buildCharacterDataMap(textRoot);
72 m_metricsBuilder.buildMetricsAndLayoutAttributes(textRoot, nullptr, m_characterDataMap);
73 return true;
74}
75
76void SVGTextLayoutAttributesBuilder::rebuildMetricsForTextRenderer(RenderSVGInlineText& text)
77{
78 m_metricsBuilder.measureTextRenderer(text);
79}
80
81static inline void processRenderSVGInlineText(const RenderSVGInlineText& text, unsigned& atCharacter, bool& lastCharacterWasSpace)
82{
83 auto& string = text.text();
84 auto length = string.length();
85 if (text.style().whiteSpace() == WhiteSpace::Pre) {
86 atCharacter += length;
87 return;
88 }
89
90 // FIXME: This is not a complete whitespace collapsing implementation; it doesn't handle newlines or tabs.
91 for (unsigned i = 0; i < length; ++i) {
92 UChar character = string[i];
93 if (character == ' ' && lastCharacterWasSpace)
94 continue;
95
96 lastCharacterWasSpace = character == ' ';
97 ++atCharacter;
98 }
99}
100
101void SVGTextLayoutAttributesBuilder::collectTextPositioningElements(RenderBoxModelObject& start, bool& lastCharacterWasSpace)
102{
103 ASSERT(!is<RenderSVGText>(start) || m_textPositions.isEmpty());
104
105 for (auto& child : childrenOfType<RenderObject>(start)) {
106 if (is<RenderSVGInlineText>(child)) {
107 processRenderSVGInlineText(downcast<RenderSVGInlineText>(child), m_textLength, lastCharacterWasSpace);
108 continue;
109 }
110
111 if (!is<RenderSVGInline>(child))
112 continue;
113
114 auto& inlineChild = downcast<RenderSVGInline>(child);
115 SVGTextPositioningElement* element = SVGTextPositioningElement::elementFromRenderer(inlineChild);
116
117 unsigned atPosition = m_textPositions.size();
118 if (element)
119 m_textPositions.append(TextPosition(element, m_textLength));
120
121 collectTextPositioningElements(inlineChild, lastCharacterWasSpace);
122
123 if (!element)
124 continue;
125
126 // Update text position, after we're back from recursion.
127 TextPosition& position = m_textPositions[atPosition];
128 ASSERT(!position.length);
129 position.length = m_textLength - position.start;
130 }
131}
132
133void SVGTextLayoutAttributesBuilder::buildCharacterDataMap(RenderSVGText& textRoot)
134{
135 SVGTextPositioningElement* outermostTextElement = SVGTextPositioningElement::elementFromRenderer(textRoot);
136 ASSERT(outermostTextElement);
137
138 // Grab outermost <text> element value lists and insert them in the character data map.
139 TextPosition wholeTextPosition(outermostTextElement, 0, m_textLength);
140 fillCharacterDataMap(wholeTextPosition);
141
142 // Handle x/y default attributes.
143 SVGCharacterDataMap::iterator it = m_characterDataMap.find(1);
144 if (it == m_characterDataMap.end()) {
145 SVGCharacterData data;
146 data.x = 0;
147 data.y = 0;
148 m_characterDataMap.set(1, data);
149 } else {
150 SVGCharacterData& data = it->value;
151 if (data.x == SVGTextLayoutAttributes::emptyValue())
152 data.x = 0;
153 if (data.y == SVGTextLayoutAttributes::emptyValue())
154 data.y = 0;
155 }
156
157 // Fill character data map using child text positioning elements in top-down order.
158 unsigned size = m_textPositions.size();
159 for (unsigned i = 0; i < size; ++i)
160 fillCharacterDataMap(m_textPositions[i]);
161}
162
163static inline void updateCharacterData(unsigned i, float& lastRotation, SVGCharacterData& data, const SVGLengthContext& lengthContext, const SVGLengthList* xList, const SVGLengthList* yList, const SVGLengthList* dxList, const SVGLengthList* dyList, const SVGNumberList* rotateList)
164{
165 if (xList)
166 data.x = xList->items()[i]->value().value(lengthContext);
167 if (yList)
168 data.y = yList->items()[i]->value().value(lengthContext);
169 if (dxList)
170 data.dx = dxList->items()[i]->value().value(lengthContext);
171 if (dyList)
172 data.dy = dyList->items()[i]->value().value(lengthContext);
173 if (rotateList) {
174 data.rotate = rotateList->items()[i]->value();
175 lastRotation = data.rotate;
176 }
177}
178
179void SVGTextLayoutAttributesBuilder::fillCharacterDataMap(const TextPosition& position)
180{
181 const auto& xList = position.element->x();
182 const auto& yList = position.element->y();
183 const auto& dxList = position.element->dx();
184 const auto& dyList = position.element->dy();
185 const auto& rotateList = position.element->rotate();
186
187 unsigned xListSize = xList.size();
188 unsigned yListSize = yList.size();
189 unsigned dxListSize = dxList.size();
190 unsigned dyListSize = dyList.size();
191 unsigned rotateListSize = rotateList.items().size();
192 if (!xListSize && !yListSize && !dxListSize && !dyListSize && !rotateListSize)
193 return;
194
195 float lastRotation = SVGTextLayoutAttributes::emptyValue();
196 SVGLengthContext lengthContext(position.element);
197 for (unsigned i = 0; i < position.length; ++i) {
198 const SVGLengthList* xListPtr = i < xListSize ? &xList : nullptr;
199 const SVGLengthList* yListPtr = i < yListSize ? &yList : nullptr;
200 const SVGLengthList* dxListPtr = i < dxListSize ? &dxList : nullptr;
201 const SVGLengthList* dyListPtr = i < dyListSize ? &dyList : nullptr;
202 const SVGNumberList* rotateListPtr = i < rotateListSize ? &rotateList : nullptr;
203 if (!xListPtr && !yListPtr && !dxListPtr && !dyListPtr && !rotateListPtr)
204 break;
205
206 SVGCharacterDataMap::iterator it = m_characterDataMap.find(position.start + i + 1);
207 if (it == m_characterDataMap.end()) {
208 SVGCharacterData data;
209 updateCharacterData(i, lastRotation, data, lengthContext, xListPtr, yListPtr, dxListPtr, dyListPtr, rotateListPtr);
210 m_characterDataMap.set(position.start + i + 1, data);
211 continue;
212 }
213
214 updateCharacterData(i, lastRotation, it->value, lengthContext, xListPtr, yListPtr, dxListPtr, dyListPtr, rotateListPtr);
215 }
216
217 // The last rotation value always spans the whole scope.
218 if (lastRotation == SVGTextLayoutAttributes::emptyValue())
219 return;
220
221 for (unsigned i = rotateList.items().size(); i < position.length; ++i) {
222 SVGCharacterDataMap::iterator it = m_characterDataMap.find(position.start + i + 1);
223 if (it == m_characterDataMap.end()) {
224 SVGCharacterData data;
225 data.rotate = lastRotation;
226 m_characterDataMap.set(position.start + i + 1, data);
227 continue;
228 }
229
230 it->value.rotate = lastRotation;
231 }
232}
233
234}
235