1 /*
2 Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19 */
20
21 #include "config.h"
22
23 #if ENABLE(SVG)
24 #include "SVGTextContentElement.h"
25
26 #include "CSSPropertyNames.h"
27 #include "CSSValueKeywords.h"
28 #include "ExceptionCode.h"
29 #include "FloatPoint.h"
30 #include "FloatRect.h"
31 #include "Frame.h"
32 #include "MappedAttribute.h"
33 #include "Position.h"
34 #include "RenderSVGText.h"
35 #include "SVGCharacterLayoutInfo.h"
36 #include "SVGInlineTextBox.h"
37 #include "SVGLength.h"
38 #include "SVGNames.h"
39 #include "SVGRootInlineBox.h"
40 #include "SelectionController.h"
41 #include "XMLNames.h"
42 #include <wtf/StdLibExtras.h>
43
44 namespace WebCore {
45
SVGTextContentElement(const QualifiedName & tagName,Document * doc)46 SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document* doc)
47 : SVGStyledElement(tagName, doc)
48 , SVGTests()
49 , SVGLangSpace()
50 , SVGExternalResourcesRequired()
51 , m_textLength(LengthModeOther)
52 , m_lengthAdjust(LENGTHADJUST_SPACING)
53 {
54 }
55
~SVGTextContentElement()56 SVGTextContentElement::~SVGTextContentElement()
57 {
58 }
59
cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator & start,const Vector<SVGChar>::iterator & end,SVGInlineTextBox * textBox,int startOffset,long startPosition,long length,bool isVerticalText,long & atCharacter)60 static inline float cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end, SVGInlineTextBox* textBox,
61 int startOffset, long startPosition, long length, bool isVerticalText, long& atCharacter)
62 {
63 if (!length)
64 return 0.0f;
65
66 float textLength = 0.0f;
67 RenderStyle* style = textBox->textRenderer()->style();
68
69 bool usesFullRange = (startPosition == -1 && length == -1);
70
71 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
72 if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) {
73 unsigned int newOffset = textBox->start() + (it - start) + startOffset;
74
75 // Take RTL text into account and pick right glyph width/height.
76 if (textBox->direction() == RTL)
77 newOffset = textBox->start() + textBox->end() - newOffset;
78
79 // FIXME: does this handle multichar glyphs ok? not sure
80 int charsConsumed = 0;
81 String glyphName;
82 if (isVerticalText)
83 textLength += textBox->calculateGlyphHeight(style, newOffset, 0);
84 else
85 textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName);
86 }
87
88 if (!usesFullRange) {
89 if (atCharacter == startPosition + length - 1)
90 break;
91
92 atCharacter++;
93 }
94 }
95
96 return textLength;
97 }
98
99 // Helper class for querying certain glyph information
100 struct SVGInlineTextBoxQueryWalker {
101 typedef enum {
102 NumberOfCharacters,
103 TextLength,
104 SubStringLength,
105 StartPosition,
106 EndPosition,
107 Extent,
108 Rotation,
109 CharacterNumberAtPosition
110 } QueryMode;
111
SVGInlineTextBoxQueryWalkerWebCore::SVGInlineTextBoxQueryWalker112 SVGInlineTextBoxQueryWalker(const SVGTextContentElement* reference, QueryMode mode)
113 : m_reference(reference)
114 , m_mode(mode)
115 , m_queryStartPosition(0)
116 , m_queryLength(0)
117 , m_queryPointInput()
118 , m_queryLongResult(0)
119 , m_queryFloatResult(0.0f)
120 , m_queryPointResult()
121 , m_queryRectResult()
122 , m_stopProcessing(true)
123 , m_atCharacter(0)
124 {
125 }
126
chunkPortionCallbackWebCore::SVGInlineTextBoxQueryWalker127 void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform&,
128 const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end)
129 {
130 RenderStyle* style = textBox->textRenderer()->style();
131 bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB;
132
133 switch (m_mode) {
134 case NumberOfCharacters:
135 {
136 m_queryLongResult += (end - start);
137 m_stopProcessing = false;
138 return;
139 }
140 case TextLength:
141 {
142 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter);
143
144 if (isVerticalText)
145 m_queryFloatResult += textLength;
146 else
147 m_queryFloatResult += textLength;
148
149 m_stopProcessing = false;
150 return;
151 }
152 case SubStringLength:
153 {
154 long startPosition = m_queryStartPosition;
155 long length = m_queryLength;
156
157 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter);
158
159 if (isVerticalText)
160 m_queryFloatResult += textLength;
161 else
162 m_queryFloatResult += textLength;
163
164 if (m_atCharacter == startPosition + length)
165 m_stopProcessing = true;
166 else
167 m_stopProcessing = false;
168
169 return;
170 }
171 case StartPosition:
172 {
173 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
174 if (m_atCharacter == m_queryStartPosition) {
175 m_queryPointResult = FloatPoint(it->x, it->y);
176 m_stopProcessing = true;
177 return;
178 }
179
180 m_atCharacter++;
181 }
182
183 m_stopProcessing = false;
184 return;
185 }
186 case EndPosition:
187 {
188 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
189 if (m_atCharacter == m_queryStartPosition) {
190 unsigned int newOffset = textBox->start() + (it - start) + startOffset;
191
192 // Take RTL text into account and pick right glyph width/height.
193 if (textBox->direction() == RTL)
194 newOffset = textBox->start() + textBox->end() - newOffset;
195
196 int charsConsumed;
197 String glyphName;
198 // calculateGlyph{Height,Width} will consume at least one character. This is the number of characters available
199 // to them beyond that first one.
200 int extraCharactersAvailable = end - it - 1;
201 if (isVerticalText)
202 m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, extraCharactersAvailable));
203 else
204 m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, extraCharactersAvailable, charsConsumed, glyphName), it->y);
205
206 m_stopProcessing = true;
207 return;
208 }
209
210 m_atCharacter++;
211 }
212
213 m_stopProcessing = false;
214 return;
215 }
216 case Extent:
217 {
218 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
219 if (m_atCharacter == m_queryStartPosition) {
220 unsigned int newOffset = textBox->start() + (it - start) + startOffset;
221 m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it);
222 m_stopProcessing = true;
223 return;
224 }
225
226 m_atCharacter++;
227 }
228
229 m_stopProcessing = false;
230 return;
231 }
232 case Rotation:
233 {
234 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
235 if (m_atCharacter == m_queryStartPosition) {
236 m_queryFloatResult = it->angle;
237 m_stopProcessing = true;
238 return;
239 }
240
241 m_atCharacter++;
242 }
243
244 m_stopProcessing = false;
245 return;
246 }
247 case CharacterNumberAtPosition:
248 {
249 int offset = 0;
250 SVGChar* charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset);
251
252 offset += m_atCharacter;
253 if (charAtPos && offset > m_queryLongResult)
254 m_queryLongResult = offset;
255
256 m_atCharacter += end - start;
257 m_stopProcessing = false;
258 return;
259 }
260 default:
261 ASSERT_NOT_REACHED();
262 m_stopProcessing = true;
263 return;
264 }
265 }
266
setQueryInputParametersWebCore::SVGInlineTextBoxQueryWalker267 void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint)
268 {
269 m_queryStartPosition = startPosition;
270 m_queryLength = length;
271 m_queryPointInput = referencePoint;
272 }
273
longResultWebCore::SVGInlineTextBoxQueryWalker274 long longResult() const { return m_queryLongResult; }
floatResultWebCore::SVGInlineTextBoxQueryWalker275 float floatResult() const { return m_queryFloatResult; }
pointResultWebCore::SVGInlineTextBoxQueryWalker276 FloatPoint pointResult() const { return m_queryPointResult; }
rectResultWebCore::SVGInlineTextBoxQueryWalker277 FloatRect rectResult() const { return m_queryRectResult; }
stopProcessingWebCore::SVGInlineTextBoxQueryWalker278 bool stopProcessing() const { return m_stopProcessing; }
279
280 private:
281 const SVGTextContentElement* m_reference;
282 QueryMode m_mode;
283
284 long m_queryStartPosition;
285 long m_queryLength;
286 FloatPoint m_queryPointInput;
287
288 long m_queryLongResult;
289 float m_queryFloatResult;
290 FloatPoint m_queryPointResult;
291 FloatRect m_queryRectResult;
292
293 bool m_stopProcessing;
294 long m_atCharacter;
295 };
296
findInlineTextBoxInTextChunks(const SVGTextContentElement * element,const Vector<SVGTextChunk> & chunks)297 static Vector<SVGInlineTextBox*> findInlineTextBoxInTextChunks(const SVGTextContentElement* element, const Vector<SVGTextChunk>& chunks)
298 {
299 Vector<SVGTextChunk>::const_iterator it = chunks.begin();
300 const Vector<SVGTextChunk>::const_iterator end = chunks.end();
301
302 Vector<SVGInlineTextBox*> boxes;
303
304 for (; it != end; ++it) {
305 Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin();
306 const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end();
307
308 for (; boxIt != boxEnd; ++boxIt) {
309 SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(boxIt->box);
310
311 Node* textElement = textBox->textRenderer()->parent()->node();
312 ASSERT(textElement);
313
314 if (textElement == element || textElement->parent() == element)
315 boxes.append(textBox);
316 }
317 }
318
319 return boxes;
320 }
321
rootInlineBoxForTextContentElement(const SVGTextContentElement * element)322 static inline SVGRootInlineBox* rootInlineBoxForTextContentElement(const SVGTextContentElement* element)
323 {
324 RenderObject* object = element->renderer();
325
326 if (!object || !object->isSVGText() || object->isText())
327 return 0;
328
329 RenderBlock* svgText = toRenderBlock(object);
330
331 // Find root inline box
332 SVGRootInlineBox* rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
333 if (!rootBox) {
334 // Layout is not sync yet!
335 element->document()->updateLayoutIgnorePendingStylesheets();
336 rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
337 }
338
339 ASSERT(rootBox);
340 return rootBox;
341 }
342
executeTextQuery(const SVGTextContentElement * element,SVGInlineTextBoxQueryWalker::QueryMode mode,long startPosition=0,long length=0,FloatPoint referencePoint=FloatPoint ())343 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement* element, SVGInlineTextBoxQueryWalker::QueryMode mode,
344 long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint())
345 {
346 SVGRootInlineBox* rootBox = rootInlineBoxForTextContentElement(element);
347 if (!rootBox)
348 return SVGInlineTextBoxQueryWalker(0, mode);
349
350 // Find all inline text box associated with our renderer
351 Vector<SVGInlineTextBox*> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks());
352
353 // Walk text chunks to find chunks associated with our inline text box
354 SVGInlineTextBoxQueryWalker walkerCallback(element, mode);
355 walkerCallback.setQueryInputParameters(startPosition, length, referencePoint);
356
357 SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback);
358
359 Vector<SVGInlineTextBox*>::iterator it = textBoxes.begin();
360 Vector<SVGInlineTextBox*>::iterator end = textBoxes.end();
361
362 for (; it != end; ++it) {
363 rootBox->walkTextChunks(&walker, *it);
364
365 if (walkerCallback.stopProcessing())
366 break;
367 }
368
369 return walkerCallback;
370 }
371
getNumberOfChars() const372 unsigned SVGTextContentElement::getNumberOfChars() const
373 {
374 document()->updateLayoutIgnorePendingStylesheets();
375
376 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult();
377 }
378
getComputedTextLength() const379 float SVGTextContentElement::getComputedTextLength() const
380 {
381 document()->updateLayoutIgnorePendingStylesheets();
382
383 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult();
384 }
385
getSubStringLength(unsigned charnum,unsigned nchars,ExceptionCode & ec) const386 float SVGTextContentElement::getSubStringLength(unsigned charnum, unsigned nchars, ExceptionCode& ec) const
387 {
388 document()->updateLayoutIgnorePendingStylesheets();
389
390 unsigned numberOfChars = getNumberOfChars();
391 if (charnum >= numberOfChars) {
392 ec = INDEX_SIZE_ERR;
393 return 0.0f;
394 }
395
396 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult();
397 }
398
getStartPositionOfChar(unsigned charnum,ExceptionCode & ec) const399 FloatPoint SVGTextContentElement::getStartPositionOfChar(unsigned charnum, ExceptionCode& ec) const
400 {
401 document()->updateLayoutIgnorePendingStylesheets();
402
403 if (charnum > getNumberOfChars()) {
404 ec = INDEX_SIZE_ERR;
405 return FloatPoint();
406 }
407
408 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult();
409 }
410
getEndPositionOfChar(unsigned charnum,ExceptionCode & ec) const411 FloatPoint SVGTextContentElement::getEndPositionOfChar(unsigned charnum, ExceptionCode& ec) const
412 {
413 document()->updateLayoutIgnorePendingStylesheets();
414
415 if (charnum > getNumberOfChars()) {
416 ec = INDEX_SIZE_ERR;
417 return FloatPoint();
418 }
419
420 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult();
421 }
422
getExtentOfChar(unsigned charnum,ExceptionCode & ec) const423 FloatRect SVGTextContentElement::getExtentOfChar(unsigned charnum, ExceptionCode& ec) const
424 {
425 document()->updateLayoutIgnorePendingStylesheets();
426
427 if (charnum > getNumberOfChars()) {
428 ec = INDEX_SIZE_ERR;
429 return FloatRect();
430 }
431
432 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult();
433 }
434
getRotationOfChar(unsigned charnum,ExceptionCode & ec) const435 float SVGTextContentElement::getRotationOfChar(unsigned charnum, ExceptionCode& ec) const
436 {
437 document()->updateLayoutIgnorePendingStylesheets();
438
439 if (charnum > getNumberOfChars()) {
440 ec = INDEX_SIZE_ERR;
441 return 0.0f;
442 }
443
444 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult();
445 }
446
getCharNumAtPosition(const FloatPoint & point) const447 int SVGTextContentElement::getCharNumAtPosition(const FloatPoint& point) const
448 {
449 document()->updateLayoutIgnorePendingStylesheets();
450
451 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult();
452 }
453
selectSubString(unsigned charnum,unsigned nchars,ExceptionCode & ec) const454 void SVGTextContentElement::selectSubString(unsigned charnum, unsigned nchars, ExceptionCode& ec) const
455 {
456 unsigned numberOfChars = getNumberOfChars();
457 if (charnum >= numberOfChars) {
458 ec = INDEX_SIZE_ERR;
459 return;
460 }
461
462 if (nchars > numberOfChars - charnum)
463 nchars = numberOfChars - charnum;
464
465 ASSERT(document());
466 ASSERT(document()->frame());
467
468 SelectionController* controller = document()->frame()->selection();
469 if (!controller)
470 return;
471
472 // Find selection start
473 VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY);
474 for (unsigned i = 0; i < charnum; ++i)
475 start = start.next();
476
477 // Find selection end
478 VisiblePosition end(start);
479 for (unsigned i = 0; i < nchars; ++i)
480 end = end.next();
481
482 controller->setSelection(VisibleSelection(start, end));
483 }
484
parseMappedAttribute(MappedAttribute * attr)485 void SVGTextContentElement::parseMappedAttribute(MappedAttribute* attr)
486 {
487 if (attr->name() == SVGNames::lengthAdjustAttr) {
488 if (attr->value() == "spacing")
489 setLengthAdjustBaseValue(LENGTHADJUST_SPACING);
490 else if (attr->value() == "spacingAndGlyphs")
491 setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS);
492 } else if (attr->name() == SVGNames::textLengthAttr) {
493 setTextLengthBaseValue(SVGLength(LengthModeOther, attr->value()));
494 if (textLengthBaseValue().value(this) < 0.0)
495 document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed");
496 } else {
497 if (SVGTests::parseMappedAttribute(attr))
498 return;
499 if (SVGLangSpace::parseMappedAttribute(attr)) {
500 if (attr->name().matches(XMLNames::spaceAttr)) {
501 DEFINE_STATIC_LOCAL(const AtomicString, preserveString, ("preserve"));
502
503 if (attr->value() == preserveString)
504 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePre);
505 else
506 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValueNowrap);
507 }
508 return;
509 }
510 if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
511 return;
512
513 SVGStyledElement::parseMappedAttribute(attr);
514 }
515 }
516
synchronizeProperty(const QualifiedName & attrName)517 void SVGTextContentElement::synchronizeProperty(const QualifiedName& attrName)
518 {
519 SVGStyledElement::synchronizeProperty(attrName);
520
521 if (attrName == anyQName()) {
522 synchronizeLengthAdjust();
523 synchronizeTextLength();
524 synchronizeExternalResourcesRequired();
525 return;
526 }
527
528 if (attrName == SVGNames::lengthAdjustAttr)
529 synchronizeLengthAdjust();
530 else if (attrName == SVGNames::textLengthAttr)
531 synchronizeTextLength();
532 else if (SVGExternalResourcesRequired::isKnownAttribute(attrName))
533 synchronizeExternalResourcesRequired();
534 }
535
isKnownAttribute(const QualifiedName & attrName)536 bool SVGTextContentElement::isKnownAttribute(const QualifiedName& attrName)
537 {
538 return (attrName.matches(SVGNames::lengthAdjustAttr) ||
539 attrName.matches(SVGNames::textLengthAttr) ||
540 SVGTests::isKnownAttribute(attrName) ||
541 SVGLangSpace::isKnownAttribute(attrName) ||
542 SVGExternalResourcesRequired::isKnownAttribute(attrName) ||
543 SVGStyledElement::isKnownAttribute(attrName));
544 }
545
546 }
547
548 #endif // ENABLE(SVG)
549