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.
22 #include "wtf/Platform.h"
25 #include "SVGTextContentElement.h"
27 /*#include "CSSPropertyNames.h"
28 #include "CSSValueKeywords.h"*/
29 #include "ExceptionCode.h"
30 #include "FloatPoint.h"
31 #include "FloatRect.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"
41 //#include "XMLNames.h"
45 SVGTextContentElement::SVGTextContentElement(const QualifiedName
& tagName
, Document
* doc
)
46 : SVGStyledElement(tagName
, doc
)
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
)
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;
85 textLength
+= textBox
->calculateGlyphHeight(style
, newOffset
, 0);
87 textLength
+= textBox
->calculateGlyphWidth(style
, newOffset
, 0, charsConsumed
, glyphName
);
91 if (atCharacter
== startPosition
+ length
- 1)
101 // Helper class for querying certain glyph information
102 struct SVGInlineTextBoxQueryWalker
{
111 CharacterNumberAtPosition
114 SVGInlineTextBoxQueryWalker(const SVGTextContentElement
* reference
, QueryMode mode
)
115 : m_reference(reference
)
117 , m_queryStartPosition(0)
119 , m_queryPointInput()
120 , m_queryLongResult(0)
121 , m_queryFloatResult(0.0f
)
122 , m_queryPointResult()
123 , m_queryRectResult()
124 , m_stopProcessing(true)
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
;
136 case NumberOfCharacters
:
138 m_queryLongResult
+= (end
- start
);
139 m_stopProcessing
= false;
144 float textLength
= cumulativeCharacterRangeLength(start
, end
, textBox
, startOffset
, -1, -1, isVerticalText
, m_atCharacter
);
147 m_queryFloatResult
+= textLength
;
149 m_queryFloatResult
+= textLength
;
151 m_stopProcessing
= false;
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
);
162 m_queryFloatResult
+= textLength
;
164 m_queryFloatResult
+= textLength
;
166 if (m_atCharacter
== startPosition
+ length
)
167 m_stopProcessing
= true;
169 m_stopProcessing
= false;
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;
185 m_stopProcessing
= false;
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;*/
201 m_queryPointResult
.move(it
->x
, it
->y
+ textBox
->calculateGlyphHeight(style
, newOffset
, end
- it
));
203 m_queryPointResult
.move(it
->x
+ textBox
->calculateGlyphWidth(style
, newOffset
, end
- it
, charsConsumed
, glyphName
), it
->y
);
205 m_stopProcessing
= true;
212 m_stopProcessing
= false;
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;
228 m_stopProcessing
= false;
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;
243 m_stopProcessing
= false;
246 case CharacterNumberAtPosition
:
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;
260 ASSERT_NOT_REACHED();
261 m_stopProcessing
= true;
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
; }
280 const SVGTextContentElement
* m_reference
;
283 long m_queryStartPosition
;
285 FloatPoint m_queryPointInput
;
287 long m_queryLongResult
;
288 float m_queryFloatResult
;
289 FloatPoint m_queryPointResult
;
290 FloatRect m_queryRectResult
;
292 bool m_stopProcessing
;
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();
313 if (textElement
== element
|| textElement
->parent() == element
)
314 boxes
.append(textBox
);
321 static inline SVGRootInlineBox
* rootInlineBoxForTextContentElement(const SVGTextContentElement
* element
)
323 RenderObject
* object
= element
->renderer();
325 if (!object
|| !object
->isSVGText() || object
->isText())
328 RenderSVGText
* svgText
= static_cast<RenderSVGText
*>(object
);
330 // Find root inline box
331 SVGRootInlineBox
* rootBox
= static_cast<SVGRootInlineBox
*>(svgText
->firstRootBox());
333 // Layout is not sync yet!
334 /*FIXME khtml element->document()->updateLayoutIgnorePendingStylesheets();*/
335 rootBox
= static_cast<SVGRootInlineBox
*>(svgText
->firstRootBox());
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
);
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())
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
;
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
;
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
;
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
;
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
;
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
;
456 if (nchars
> numberOfChars
- charnum
)
457 nchars
= numberOfChars
- charnum
;
460 //khtml ASSERT(document()->frame());
462 /*FIXME SelectionController* controller = document()->frame()->selectionController();
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)
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");
491 if (SVGTests::parseMappedAttribute(attr
))
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);
500 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValueNowrap);
504 if (SVGExternalResourcesRequired::parseMappedAttribute(attr
))
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)