fix logic
[personal-kdelibs.git] / khtml / svg / SVGTextContentElement.cpp
blob80df87c6465fc927cd19fa1a2ed4664f088f8b30
1 /*
2 Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 2004, 2005, 2006, 2007, 2008 Rob Buis <buis@kde.org>
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.
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.
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.
21 #include "config.h"
22 #include "wtf/Platform.h"
24 #if ENABLE(SVG)
25 #include "SVGTextContentElement.h"
27 /*#include "CSSPropertyNames.h"
28 #include "CSSValueKeywords.h"*/
29 #include "ExceptionCode.h"
30 #include "FloatPoint.h"
31 #include "FloatRect.h"
32 /*#include "Frame.h"
33 #include "Position.h"*/
34 #include "RenderSVGText.h"
35 /*#include "SelectionController.h"*/
36 #include "SVGCharacterLayoutInfo.h"
37 #include "SVGRootInlineBox.h"
38 #include "SVGLength.h"
39 #include "SVGInlineTextBox.h"
40 #include "SVGNames.h"
41 //#include "XMLNames.h"
43 namespace WebCore {
45 SVGTextContentElement::SVGTextContentElement(const QualifiedName& tagName, Document* doc)
46 : SVGStyledElement(tagName, doc)
47 , SVGTests()
48 , SVGLangSpace()
49 , SVGExternalResourcesRequired()
50 , m_textLength(this, LengthModeOther)
51 , m_lengthAdjust(LENGTHADJUST_SPACING)
55 SVGTextContentElement::~SVGTextContentElement()
59 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, SVGLength, Length, length, TextLength, textLength, SVGNames::textLengthAttr, m_textLength)
60 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, int, Enumeration, enumeration, LengthAdjust, lengthAdjust, SVGNames::lengthAdjustAttr, m_lengthAdjust)
62 static inline float cumulativeCharacterRangeLength(const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end, SVGInlineTextBox* textBox,
63 int startOffset, long startPosition, long length, bool isVerticalText, long& atCharacter)
65 if (!length)
66 return 0.0f;
68 float textLength = 0.0f;
69 RenderStyle* style = textBox->renderText()->style();
71 bool usesFullRange = (startPosition == -1 && length == -1);
73 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
74 if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) {
75 unsigned int newOffset = textBox->start() + (it - start) + startOffset;
77 // Take RTL text into account and pick right glyph width/height.
78 /*FIXME khtml if (textBox->direction() == RTL)
79 newOffset = textBox->start() + textBox->end() - newOffset;*/
81 // FIXME: does this handle multichar glyphs ok? not sure
82 int charsConsumed = 0;
83 String glyphName;
84 if (isVerticalText)
85 textLength += textBox->calculateGlyphHeight(style, newOffset, 0);
86 else
87 textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName);
90 if (!usesFullRange) {
91 if (atCharacter == startPosition + length - 1)
92 break;
94 atCharacter++;
98 return textLength;
101 // Helper class for querying certain glyph information
102 struct SVGInlineTextBoxQueryWalker {
103 typedef enum {
104 NumberOfCharacters,
105 TextLength,
106 SubStringLength,
107 StartPosition,
108 EndPosition,
109 Extent,
110 Rotation,
111 CharacterNumberAtPosition
112 } QueryMode;
114 SVGInlineTextBoxQueryWalker(const SVGTextContentElement* reference, QueryMode mode)
115 : m_reference(reference)
116 , m_mode(mode)
117 , m_queryStartPosition(0)
118 , m_queryLength(0)
119 , m_queryPointInput()
120 , m_queryLongResult(0)
121 , m_queryFloatResult(0.0f)
122 , m_queryPointResult()
123 , m_queryRectResult()
124 , m_stopProcessing(true)
125 , m_atCharacter(0)
129 void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform& chunkCtm,
130 const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end)
132 RenderStyle* style = textBox->renderText()->style();
133 bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB;
135 switch (m_mode) {
136 case NumberOfCharacters:
138 m_queryLongResult += (end - start);
139 m_stopProcessing = false;
140 return;
142 case TextLength:
144 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter);
146 if (isVerticalText)
147 m_queryFloatResult += textLength;
148 else
149 m_queryFloatResult += textLength;
151 m_stopProcessing = false;
152 return;
154 case SubStringLength:
156 long startPosition = m_queryStartPosition;
157 long length = m_queryLength;
159 float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter);
161 if (isVerticalText)
162 m_queryFloatResult += textLength;
163 else
164 m_queryFloatResult += textLength;
166 if (m_atCharacter == startPosition + length)
167 m_stopProcessing = true;
168 else
169 m_stopProcessing = false;
171 return;
173 case StartPosition:
175 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
176 if (m_atCharacter == m_queryStartPosition) {
177 m_queryPointResult = FloatPoint(it->x, it->y);
178 m_stopProcessing = true;
179 return;
182 m_atCharacter++;
185 m_stopProcessing = false;
186 return;
188 case EndPosition:
190 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
191 if (m_atCharacter == m_queryStartPosition) {
192 unsigned int newOffset = textBox->start() + (it - start) + startOffset;
194 // Take RTL text into account and pick right glyph width/height.
195 /*FIXME khtml if (textBox->direction() == RTL)
196 newOffset = textBox->start() + textBox->end() - newOffset;*/
198 int charsConsumed;
199 String glyphName;
200 if (isVerticalText)
201 m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, end - it));
202 else
203 m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, end - it, charsConsumed, glyphName), it->y);
205 m_stopProcessing = true;
206 return;
209 m_atCharacter++;
212 m_stopProcessing = false;
213 return;
215 case Extent:
217 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
218 if (m_atCharacter == m_queryStartPosition) {
219 unsigned int newOffset = textBox->start() + (it - start) + startOffset;
220 m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it);
221 m_stopProcessing = true;
222 return;
225 m_atCharacter++;
228 m_stopProcessing = false;
229 return;
231 case Rotation:
233 for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
234 if (m_atCharacter == m_queryStartPosition) {
235 m_queryFloatResult = it->angle;
236 m_stopProcessing = true;
237 return;
240 m_atCharacter++;
243 m_stopProcessing = false;
244 return;
246 case CharacterNumberAtPosition:
248 int offset = 0;
249 SVGChar* charAtPos = textBox->closestCharacterToPosition(m_queryPointInput.x(), m_queryPointInput.y(), offset);
251 offset += m_atCharacter;
252 if (charAtPos && offset > m_queryLongResult)
253 m_queryLongResult = offset;
255 m_atCharacter += end - start;
256 m_stopProcessing = false;
257 return;
259 default:
260 ASSERT_NOT_REACHED();
261 m_stopProcessing = true;
262 return;
266 void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint)
268 m_queryStartPosition = startPosition;
269 m_queryLength = length;
270 m_queryPointInput = referencePoint;
273 long longResult() const { return m_queryLongResult; }
274 float floatResult() const { return m_queryFloatResult; }
275 FloatPoint pointResult() const { return m_queryPointResult; }
276 FloatRect rectResult() const { return m_queryRectResult; }
277 bool stopProcessing() const { return m_stopProcessing; }
279 private:
280 const SVGTextContentElement* m_reference;
281 QueryMode m_mode;
283 long m_queryStartPosition;
284 long m_queryLength;
285 FloatPoint m_queryPointInput;
287 long m_queryLongResult;
288 float m_queryFloatResult;
289 FloatPoint m_queryPointResult;
290 FloatRect m_queryRectResult;
292 bool m_stopProcessing;
293 long m_atCharacter;
296 static Vector<SVGInlineTextBox*> findInlineTextBoxInTextChunks(const SVGTextContentElement* element, const Vector<SVGTextChunk>& chunks)
298 Vector<SVGTextChunk>::const_iterator it = chunks.begin();
299 const Vector<SVGTextChunk>::const_iterator end = chunks.end();
301 Vector<SVGInlineTextBox*> boxes;
303 for (; it != end; ++it) {
304 Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin();
305 const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end();
307 for (; boxIt != boxEnd; ++boxIt) {
308 SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(boxIt->box);
310 Node* textElement = textBox->renderText()->parent()->element();
311 ASSERT(textElement);
313 if (textElement == element || textElement->parent() == element)
314 boxes.append(textBox);
318 return boxes;
321 static inline SVGRootInlineBox* rootInlineBoxForTextContentElement(const SVGTextContentElement* element)
323 RenderObject* object = element->renderer();
325 if (!object || !object->isSVGText() || object->isText())
326 return 0;
328 RenderSVGText* svgText = static_cast<RenderSVGText*>(object);
330 // Find root inline box
331 SVGRootInlineBox* rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
332 if (!rootBox) {
333 // Layout is not sync yet!
334 /*FIXME khtml element->document()->updateLayoutIgnorePendingStylesheets();*/
335 rootBox = static_cast<SVGRootInlineBox*>(svgText->firstRootBox());
338 ASSERT(rootBox);
339 return rootBox;
342 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement* element, SVGInlineTextBoxQueryWalker::QueryMode mode,
343 long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint())
345 SVGRootInlineBox* rootBox = rootInlineBoxForTextContentElement(element);
346 if (!rootBox)
347 return SVGInlineTextBoxQueryWalker(0, mode);
349 // Find all inline text box associated with our renderer
350 Vector<SVGInlineTextBox*> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks());
352 // Walk text chunks to find chunks associated with our inline text box
353 SVGInlineTextBoxQueryWalker walkerCallback(element, mode);
354 walkerCallback.setQueryInputParameters(startPosition, length, referencePoint);
356 SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback);
358 Vector<SVGInlineTextBox*>::iterator it = textBoxes.begin();
359 Vector<SVGInlineTextBox*>::iterator end = textBoxes.end();
361 for (; it != end; ++it) {
362 rootBox->walkTextChunks(&walker, *it);
364 if (walkerCallback.stopProcessing())
365 break;
368 return walkerCallback;
371 long SVGTextContentElement::getNumberOfChars() const
373 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult();
376 float SVGTextContentElement::getComputedTextLength() const
378 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult();
381 float SVGTextContentElement::getSubStringLength(long charnum, long nchars, ExceptionCode& ec) const
383 // Differences to SVG 1.1 spec, as the spec is clearly wrong. TODO: Raise SVG WG issue!
384 // #1: We accept a 'long nchars' parameter instead of 'unsigned long nchars' to be able
385 // to catch cases where someone called us with a negative 'nchars' value - in those
386 // cases we'll just throw a 'INDEX_SIZE_ERR' (acid3 implicitly agrees with us)
388 // #2: We only throw if 'charnum + nchars' is greater than the number of characters, not
389 // if it's equal, as this really doesn't make any sense (no way to measure the last character!)
391 // #3: If 'charnum' is greater than or equal to 'numberOfChars', we're throwing an exception here
392 // as the result is undefined for every other value of 'nchars' than '0'.
394 long numberOfChars = getNumberOfChars();
395 if (charnum < 0 || nchars < 0 || numberOfChars <= charnum || charnum + nchars > numberOfChars) {
396 ec = DOMException::INDEX_SIZE_ERR;
397 return 0.0f;
400 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult();
403 FloatPoint SVGTextContentElement::getStartPositionOfChar(long charnum, ExceptionCode& ec) const
405 if (charnum < 0 || charnum > getNumberOfChars()) {
406 ec = DOMException::INDEX_SIZE_ERR;
407 return FloatPoint();
410 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult();
413 FloatPoint SVGTextContentElement::getEndPositionOfChar(long charnum, ExceptionCode& ec) const
415 if (charnum < 0 || charnum > getNumberOfChars()) {
416 ec = DOMException::INDEX_SIZE_ERR;
417 return FloatPoint();
420 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult();
423 FloatRect SVGTextContentElement::getExtentOfChar(long charnum, ExceptionCode& ec) const
425 if (charnum < 0 || charnum > getNumberOfChars()) {
426 ec = DOMException::INDEX_SIZE_ERR;
427 return FloatRect();
430 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult();
433 float SVGTextContentElement::getRotationOfChar(long charnum, ExceptionCode& ec) const
435 if (charnum < 0 || charnum > getNumberOfChars()) {
436 ec = DOMException::INDEX_SIZE_ERR;
437 return 0.0f;
440 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult();
443 long SVGTextContentElement::getCharNumAtPosition(const FloatPoint& point) const
445 return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult();
448 void SVGTextContentElement::selectSubString(long charnum, long nchars, ExceptionCode& ec) const
450 long numberOfChars = getNumberOfChars();
451 if (charnum < 0 || nchars < 0 || charnum > numberOfChars) {
452 ec = DOMException::INDEX_SIZE_ERR;
453 return;
456 if (nchars > numberOfChars - charnum)
457 nchars = numberOfChars - charnum;
459 ASSERT(document());
460 //khtml ASSERT(document()->frame());
462 /*FIXME SelectionController* controller = document()->frame()->selectionController();
463 if (!controller)
464 return;
466 // Find selection start
467 VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY);
468 for (long i = 0; i < charnum; ++i)
469 start = start.next();
471 // Find selection end
472 VisiblePosition end(start);
473 for (long i = 0; i < nchars; ++i)
474 end = end.next();
476 controller->setSelection(Selection(start, end));*/
479 void SVGTextContentElement::parseMappedAttribute(MappedAttribute* attr)
481 if (attr->name() == SVGNames::lengthAdjustAttr) {
482 if (attr->value() == "spacing")
483 setLengthAdjustBaseValue(LENGTHADJUST_SPACING);
484 else if (attr->value() == "spacingAndGlyphs")
485 setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS);
486 } else if (attr->name() == SVGNames::textLengthAttr) {
487 setTextLengthBaseValue(SVGLength(this, LengthModeOther, attr->value()));
488 if (textLength().value() < 0.0)
489 document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed");
490 } else {
491 if (SVGTests::parseMappedAttribute(attr))
492 return;
493 if (SVGLangSpace::parseMappedAttribute(attr)) {
494 /*if (attr->name().matches(XMLNames::spaceAttr)) {
495 static const AtomicString preserveString("preserve");
497 if (attr->value() == preserveString)
498 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePre);
499 else
500 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValueNowrap);
502 return;
504 if (SVGExternalResourcesRequired::parseMappedAttribute(attr))
505 return;
507 SVGStyledElement::parseMappedAttribute(attr);
511 bool SVGTextContentElement::isKnownAttribute(const QualifiedName& attrName)
513 return (attrName.matches(SVGNames::lengthAdjustAttr) ||
514 attrName.matches(SVGNames::textLengthAttr) ||
515 SVGTests::isKnownAttribute(attrName) ||
516 SVGLangSpace::isKnownAttribute(attrName) ||
517 SVGExternalResourcesRequired::isKnownAttribute(attrName) ||
518 SVGStyledElement::isKnownAttribute(attrName));
523 #endif // ENABLE(SVG)