56fa0a1b8afc928252dc286977c4501b444c6f25
[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     ASSERT(object);
318
319     if (!object->isSVGText() || object->isText())
320         return 0;
321
322     RenderSVGText* svgText = static_cast<RenderSVGText*>(object);
323
324     // Find root inline box
325     SVGRootInlineBox* rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
326     if (!rootBox) {
327         // Layout is not sync yet!
328         element->document()->updateLayoutIgnorePendingStylesheets();
329         rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
330     }
331
332     ASSERT(rootBox);
333     return rootBox;
334 }
335
336 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement* element, SVGInlineTextBoxQueryWalker::QueryMode mode,
337                                                            long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint())
338 {
339     SVGRootInlineBox* rootBox = rootInlineBoxForTextContentElement(element);
340     if (!rootBox)
341         return SVGInlineTextBoxQueryWalker(0, mode);
342
343     // Find all inline text box associated with our renderer
344     Vector<SVGInlineTextBox*> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks());
345
346     // Walk text chunks to find chunks associated with our inline text box
347     SVGInlineTextBoxQueryWalker walkerCallback(element, mode);
348     walkerCallback.setQueryInputParameters(startPosition, length, referencePoint);
349
350     SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback);
351
352     Vector<SVGInlineTextBox*>::iterator it = textBoxes.begin();
353     Vector<SVGInlineTextBox*>::iterator end = textBoxes.end();
354
355     for (; it != end; ++it) {
356         rootBox->walkTextChunks(&walker, *it);
357
358         if (walkerCallback.stopProcessing())
359             break;
360     }
361
362     return walkerCallback;
363 }
364
365 long SVGTextContentElement::getNumberOfChars() const
366 {
367     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult();
368 }
369
370 float SVGTextContentElement::getComputedTextLength() const
371 {
372     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult();
373 }
374
375 float SVGTextContentElement::getSubStringLength(long charnum, unsigned long nchars, ExceptionCode& ec) const
376 {
377     if (charnum < 0 || charnum > getNumberOfChars()) {
378         ec = INDEX_SIZE_ERR;
379         return 0.0f;
380     }
381
382     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult();
383 }
384
385 FloatPoint SVGTextContentElement::getStartPositionOfChar(long charnum, ExceptionCode& ec) const
386 {
387     if (charnum < 0 || charnum > getNumberOfChars()) {
388         ec = INDEX_SIZE_ERR;
389         return FloatPoint();
390     }
391
392     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult();
393 }
394
395 FloatPoint SVGTextContentElement::getEndPositionOfChar(long charnum, ExceptionCode& ec) const
396 {
397     if (charnum < 0 || charnum > getNumberOfChars()) {
398         ec = INDEX_SIZE_ERR;
399         return FloatPoint();
400     }
401
402     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult();
403 }
404
405 FloatRect SVGTextContentElement::getExtentOfChar(long charnum, ExceptionCode& ec) const
406 {
407     if (charnum < 0 || charnum > getNumberOfChars()) {
408         ec = INDEX_SIZE_ERR;
409         return FloatRect();
410     }
411
412     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult();
413 }
414
415 float SVGTextContentElement::getRotationOfChar(long charnum, ExceptionCode& ec) const
416 {
417     if (charnum < 0 || charnum > getNumberOfChars()) {
418         ec = INDEX_SIZE_ERR;
419         return 0.0f;
420     }
421
422     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult();
423 }
424
425 long SVGTextContentElement::getCharNumAtPosition(const FloatPoint& point) const
426 {
427     return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult();
428 }
429
430 void SVGTextContentElement::selectSubString(long charnum, long nchars, ExceptionCode& ec) const
431 {
432     long numberOfChars = getNumberOfChars();
433     if (charnum < 0 || nchars < 0 || charnum > numberOfChars) {
434         ec = INDEX_SIZE_ERR;
435         return;
436     }
437
438     if (nchars > numberOfChars - charnum)
439         nchars = numberOfChars - charnum;
440
441     ASSERT(document());
442     ASSERT(document()->frame());
443
444     SelectionController* controller = document()->frame()->selectionController();
445     if (!controller)
446         return;
447
448     // Find selection start
449     VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY);
450     for (long i = 0; i < charnum; ++i)
451         start = start.next();
452
453     // Find selection end
454     VisiblePosition end(start);
455     for (long i = 0; i < nchars; ++i)
456         end = end.next();
457
458     controller->setSelection(Selection(start, end));
459 }
460
461 void SVGTextContentElement::parseMappedAttribute(MappedAttribute* attr)
462 {
463     if (attr->name() == SVGNames::lengthAdjustAttr) {
464         if (attr->value() == "spacing")
465             setLengthAdjustBaseValue(LENGTHADJUST_SPACING);
466         else if (attr->value() == "spacingAndGlyphs")
467             setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS);
468     } else if (attr->name() == SVGNames::textLengthAttr) {
469         setTextLengthBaseValue(SVGLength(this, LengthModeOther, attr->value()));
470         if (textLength().value() < 0.0)
471             document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed");
472     } else {
473         if (SVGTests::parseMappedAttribute(attr))
474             return;
475         if (SVGLangSpace::parseMappedAttribute(attr)) {
476             if (attr->name().matches(XMLNames::spaceAttr)) {
477                 static const AtomicString preserveString("preserve");
478
479                 if (attr->value() == preserveString)
480                     addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_PRE);
481                 else
482                     addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_NOWRAP);
483             }
484             return;
485         }
486         if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
487             return;
488
489         SVGStyledElement::parseMappedAttribute(attr);
490     }
491 }
492
493 }
494
495 #endif // ENABLE(SVG)
496
497 // vim:ts=4:noet