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