KHtml

SVGTextContentElement.cpp
1 /*
2  Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <[email protected]>
3  2004, 2005, 2006, 2007, 2008 Rob Buis <[email protected]>
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 "wtf/Platform.h"
22 
23 #if ENABLE(SVG)
24 #include "SVGTextContentElement.h"
25 
26 #include "cssvalues.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 
43 namespace WebCore
44 {
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, m_textLength)
61 ANIMATED_PROPERTY_DEFINITIONS(SVGTextContentElement, int, Enumeration, enumeration, LengthAdjust, lengthAdjust, SVGNames::lengthAdjustAttr, m_lengthAdjust)
62 
63 static inline float cumulativeCharacterRangeLength(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  if (!length) {
67  return 0.0f;
68  }
69 
70  float textLength = 0.0f;
71  RenderStyle *style = textBox->renderText()->style();
72 
73  bool usesFullRange = (startPosition == -1 && length == -1);
74 
75  for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
76  if (usesFullRange || (atCharacter >= startPosition && atCharacter <= startPosition + length)) {
77  unsigned int newOffset = textBox->start() + (it - start) + startOffset;
78 
79  // Take RTL text into account and pick right glyph width/height.
80  /*FIXME khtml if (textBox->direction() == RTL)
81  newOffset = textBox->start() + textBox->end() - newOffset;*/
82 
83  // FIXME: does this handle multichar glyphs ok? not sure
84  int charsConsumed = 0;
85  String glyphName;
86  if (isVerticalText) {
87  textLength += textBox->calculateGlyphHeight(style, newOffset, 0);
88  } else {
89  textLength += textBox->calculateGlyphWidth(style, newOffset, 0, charsConsumed, glyphName);
90  }
91  }
92 
93  if (!usesFullRange) {
94  if (atCharacter == startPosition + length - 1) {
95  break;
96  }
97 
98  atCharacter++;
99  }
100  }
101 
102  return textLength;
103 }
104 
105 // Helper class for querying certain glyph information
106 struct SVGInlineTextBoxQueryWalker {
107  typedef enum {
108  NumberOfCharacters,
109  TextLength,
110  SubStringLength,
111  StartPosition,
112  EndPosition,
113  Extent,
114  Rotation,
115  CharacterNumberAtPosition
116  } QueryMode;
117 
118  SVGInlineTextBoxQueryWalker(const SVGTextContentElement *reference, QueryMode mode)
119  : m_reference(reference)
120  , m_mode(mode)
121  , m_queryStartPosition(0)
122  , m_queryLength(0)
123  , m_queryPointInput()
124  , m_queryLongResult(0)
125  , m_queryFloatResult(0.0f)
126  , m_queryPointResult()
127  , m_queryRectResult()
128  , m_stopProcessing(true)
129  , m_atCharacter(0)
130  {
131  }
132 
133  void chunkPortionCallback(SVGInlineTextBox *textBox, int startOffset, const AffineTransform &chunkCtm,
134  const Vector<SVGChar>::iterator &start, const Vector<SVGChar>::iterator &end)
135  {
136  Q_UNUSED(chunkCtm);
137  RenderStyle *style = textBox->renderText()->style();
138  bool isVerticalText = style->svgStyle()->writingMode() == WM_TBRL || style->svgStyle()->writingMode() == WM_TB;
139 
140  switch (m_mode) {
141  case NumberOfCharacters: {
142  m_queryLongResult += (end - start);
143  m_stopProcessing = false;
144  return;
145  }
146  case TextLength: {
147  float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, -1, -1, isVerticalText, m_atCharacter);
148 
149  if (isVerticalText) {
150  m_queryFloatResult += textLength;
151  } else {
152  m_queryFloatResult += textLength;
153  }
154 
155  m_stopProcessing = false;
156  return;
157  }
158  case SubStringLength: {
159  long startPosition = m_queryStartPosition;
160  long length = m_queryLength;
161 
162  float textLength = cumulativeCharacterRangeLength(start, end, textBox, startOffset, startPosition, length, isVerticalText, m_atCharacter);
163 
164  if (isVerticalText) {
165  m_queryFloatResult += textLength;
166  } else {
167  m_queryFloatResult += textLength;
168  }
169 
170  if (m_atCharacter == startPosition + length) {
171  m_stopProcessing = true;
172  } else {
173  m_stopProcessing = false;
174  }
175 
176  return;
177  }
178  case StartPosition: {
179  for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
180  if (m_atCharacter == m_queryStartPosition) {
181  m_queryPointResult = FloatPoint(it->x, it->y);
182  m_stopProcessing = true;
183  return;
184  }
185 
186  m_atCharacter++;
187  }
188 
189  m_stopProcessing = false;
190  return;
191  }
192  case EndPosition: {
193  for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
194  if (m_atCharacter == m_queryStartPosition) {
195  unsigned int newOffset = textBox->start() + (it - start) + startOffset;
196 
197  // Take RTL text into account and pick right glyph width/height.
198  /*FIXME khtml if (textBox->direction() == RTL)
199  newOffset = textBox->start() + textBox->end() - newOffset;*/
200 
201  int charsConsumed;
202  String glyphName;
203  if (isVerticalText) {
204  m_queryPointResult.move(it->x, it->y + textBox->calculateGlyphHeight(style, newOffset, end - it));
205  } else {
206  m_queryPointResult.move(it->x + textBox->calculateGlyphWidth(style, newOffset, end - it, charsConsumed, glyphName), it->y);
207  }
208 
209  m_stopProcessing = true;
210  return;
211  }
212 
213  m_atCharacter++;
214  }
215 
216  m_stopProcessing = false;
217  return;
218  }
219  case Extent: {
220  for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
221  if (m_atCharacter == m_queryStartPosition) {
222  unsigned int newOffset = textBox->start() + (it - start) + startOffset;
223  m_queryRectResult = textBox->calculateGlyphBoundaries(style, newOffset, *it);
224  m_stopProcessing = true;
225  return;
226  }
227 
228  m_atCharacter++;
229  }
230 
231  m_stopProcessing = false;
232  return;
233  }
234  case Rotation: {
235  for (Vector<SVGChar>::iterator it = start; it != end; ++it) {
236  if (m_atCharacter == m_queryStartPosition) {
237  m_queryFloatResult = it->angle;
238  m_stopProcessing = true;
239  return;
240  }
241 
242  m_atCharacter++;
243  }
244 
245  m_stopProcessing = false;
246  return;
247  }
248  case CharacterNumberAtPosition: {
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 
257  m_atCharacter += end - start;
258  m_stopProcessing = false;
259  return;
260  }
261  default:
262  ASSERT_NOT_REACHED();
263  m_stopProcessing = true;
264  return;
265  }
266  }
267 
268  void setQueryInputParameters(long startPosition, long length, FloatPoint referencePoint)
269  {
270  m_queryStartPosition = startPosition;
271  m_queryLength = length;
272  m_queryPointInput = referencePoint;
273  }
274 
275  long longResult() const
276  {
277  return m_queryLongResult;
278  }
279  float floatResult() const
280  {
281  return m_queryFloatResult;
282  }
283  FloatPoint pointResult() const
284  {
285  return m_queryPointResult;
286  }
287  FloatRect rectResult() const
288  {
289  return m_queryRectResult;
290  }
291  bool stopProcessing() const
292  {
293  return m_stopProcessing;
294  }
295 
296 private:
297  const SVGTextContentElement *m_reference;
298  QueryMode m_mode;
299 
300  long m_queryStartPosition;
301  long m_queryLength;
302  FloatPoint m_queryPointInput;
303 
304  long m_queryLongResult;
305  float m_queryFloatResult;
306  FloatPoint m_queryPointResult;
307  FloatRect m_queryRectResult;
308 
309  bool m_stopProcessing;
310  long m_atCharacter;
311 };
312 
313 static Vector<SVGInlineTextBox *> findInlineTextBoxInTextChunks(const SVGTextContentElement *element, const Vector<SVGTextChunk> &chunks)
314 {
315  Vector<SVGTextChunk>::const_iterator it = chunks.begin();
316  const Vector<SVGTextChunk>::const_iterator end = chunks.end();
317 
318  Vector<SVGInlineTextBox *> boxes;
319 
320  for (; it != end; ++it) {
321  Vector<SVGInlineBoxCharacterRange>::const_iterator boxIt = it->boxes.begin();
322  const Vector<SVGInlineBoxCharacterRange>::const_iterator boxEnd = it->boxes.end();
323 
324  for (; boxIt != boxEnd; ++boxIt) {
325  SVGInlineTextBox *textBox = static_cast<SVGInlineTextBox *>(boxIt->box);
326 
327  Node *textElement = textBox->renderText()->parent()->element();
328  ASSERT(textElement);
329 
330  if (textElement == element || textElement->parent() == element) {
331  boxes.append(textBox);
332  }
333  }
334  }
335 
336  return boxes;
337 }
338 
339 static inline SVGRootInlineBox *rootInlineBoxForTextContentElement(const SVGTextContentElement *element)
340 {
341  RenderObject *object = element->renderer();
342 
343  if (!object || !object->isSVGText() || object->isText()) {
344  return nullptr;
345  }
346 
347  RenderSVGText *svgText = static_cast<RenderSVGText *>(object);
348 
349  // Find root inline box
350  SVGRootInlineBox *rootBox = static_cast<SVGRootInlineBox *>(svgText->firstRootBox());
351  if (!rootBox) {
352  // Layout is not sync yet!
353  /*FIXME khtml element->document()->updateLayoutIgnorePendingStylesheets();*/
354  rootBox = static_cast<SVGRootInlineBox *>(svgText->firstRootBox());
355  }
356 
357  ASSERT(rootBox);
358  return rootBox;
359 }
360 
361 static inline SVGInlineTextBoxQueryWalker executeTextQuery(const SVGTextContentElement *element, SVGInlineTextBoxQueryWalker::QueryMode mode,
362  long startPosition = 0, long length = 0, FloatPoint referencePoint = FloatPoint())
363 {
364  SVGRootInlineBox *rootBox = rootInlineBoxForTextContentElement(element);
365  if (!rootBox) {
366  return SVGInlineTextBoxQueryWalker(nullptr, mode);
367  }
368 
369  // Find all inline text box associated with our renderer
370  Vector<SVGInlineTextBox *> textBoxes = findInlineTextBoxInTextChunks(element, rootBox->svgTextChunks());
371 
372  // Walk text chunks to find chunks associated with our inline text box
373  SVGInlineTextBoxQueryWalker walkerCallback(element, mode);
374  walkerCallback.setQueryInputParameters(startPosition, length, referencePoint);
375 
376  SVGTextChunkWalker<SVGInlineTextBoxQueryWalker> walker(&walkerCallback, &SVGInlineTextBoxQueryWalker::chunkPortionCallback);
377 
378  Vector<SVGInlineTextBox *>::iterator it = textBoxes.begin();
379  Vector<SVGInlineTextBox *>::iterator end = textBoxes.end();
380 
381  for (; it != end; ++it) {
382  rootBox->walkTextChunks(&walker, *it);
383 
384  if (walkerCallback.stopProcessing()) {
385  break;
386  }
387  }
388 
389  return walkerCallback;
390 }
391 
392 long SVGTextContentElement::getNumberOfChars() const
393 {
394  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::NumberOfCharacters).longResult();
395 }
396 
397 float SVGTextContentElement::getComputedTextLength() const
398 {
399  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::TextLength).floatResult();
400 }
401 
402 float SVGTextContentElement::getSubStringLength(long charnum, long nchars, ExceptionCode &ec) const
403 {
404  // Differences to SVG 1.1 spec, as the spec is clearly wrong. TODO: Raise SVG WG issue!
405  // #1: We accept a 'long nchars' parameter instead of 'unsigned long nchars' to be able
406  // to catch cases where someone called us with a negative 'nchars' value - in those
407  // cases we'll just throw a 'INDEX_SIZE_ERR' (acid3 implicitly agrees with us)
408  //
409  // #2: We only throw if 'charnum + nchars' is greater than the number of characters, not
410  // if it's equal, as this really doesn't make any sense (no way to measure the last character!)
411  //
412  // #3: If 'charnum' is greater than or equal to 'numberOfChars', we're throwing an exception here
413  // as the result is undefined for every other value of 'nchars' than '0'.
414 
415  long numberOfChars = getNumberOfChars();
416  if (charnum < 0 || nchars < 0 || numberOfChars <= charnum || charnum + nchars > numberOfChars) {
417  ec = DOMException::INDEX_SIZE_ERR;
418  return 0.0f;
419  }
420 
421  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::SubStringLength, charnum, nchars).floatResult();
422 }
423 
424 FloatPoint SVGTextContentElement::getStartPositionOfChar(long charnum, ExceptionCode &ec) const
425 {
426  if (charnum < 0 || charnum > getNumberOfChars()) {
427  ec = DOMException::INDEX_SIZE_ERR;
428  return FloatPoint();
429  }
430 
431  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::StartPosition, charnum).pointResult();
432 }
433 
434 FloatPoint SVGTextContentElement::getEndPositionOfChar(long charnum, ExceptionCode &ec) const
435 {
436  if (charnum < 0 || charnum > getNumberOfChars()) {
437  ec = DOMException::INDEX_SIZE_ERR;
438  return FloatPoint();
439  }
440 
441  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::EndPosition, charnum).pointResult();
442 }
443 
444 FloatRect SVGTextContentElement::getExtentOfChar(long charnum, ExceptionCode &ec) const
445 {
446  if (charnum < 0 || charnum > getNumberOfChars()) {
447  ec = DOMException::INDEX_SIZE_ERR;
448  return FloatRect();
449  }
450 
451  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Extent, charnum).rectResult();
452 }
453 
454 float SVGTextContentElement::getRotationOfChar(long charnum, ExceptionCode &ec) const
455 {
456  if (charnum < 0 || charnum > getNumberOfChars()) {
457  ec = DOMException::INDEX_SIZE_ERR;
458  return 0.0f;
459  }
460 
461  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::Rotation, charnum).floatResult();
462 }
463 
464 long SVGTextContentElement::getCharNumAtPosition(const FloatPoint &point) const
465 {
466  return executeTextQuery(this, SVGInlineTextBoxQueryWalker::CharacterNumberAtPosition, 0.0f, 0.0f, point).longResult();
467 }
468 
469 void SVGTextContentElement::selectSubString(long charnum, long nchars, ExceptionCode &ec) const
470 {
471  long numberOfChars = getNumberOfChars();
472  if (charnum < 0 || nchars < 0 || charnum > numberOfChars) {
473  ec = DOMException::INDEX_SIZE_ERR;
474  return;
475  }
476 
477  if (nchars > numberOfChars - charnum) {
478  nchars = numberOfChars - charnum;
479  }
480 
481  ASSERT(document());
482  //khtml ASSERT(document()->frame());
483 
484  /*FIXME SelectionController* controller = document()->frame()->selectionController();
485  if (!controller)
486  return;
487 
488  // Find selection start
489  VisiblePosition start(const_cast<SVGTextContentElement*>(this), 0, SEL_DEFAULT_AFFINITY);
490  for (long i = 0; i < charnum; ++i)
491  start = start.next();
492 
493  // Find selection end
494  VisiblePosition end(start);
495  for (long i = 0; i < nchars; ++i)
496  end = end.next();
497 
498  controller->setSelection(Selection(start, end));*/
499 }
500 
501 void SVGTextContentElement::parseMappedAttribute(MappedAttribute *attr)
502 {
503  if (attr->name() == SVGNames::lengthAdjustAttr) {
504  if (attr->value() == "spacing") {
505  setLengthAdjustBaseValue(LENGTHADJUST_SPACING);
506  } else if (attr->value() == "spacingAndGlyphs") {
507  setLengthAdjustBaseValue(LENGTHADJUST_SPACINGANDGLYPHS);
508  }
509  } else if (attr->name() == SVGNames::textLengthAttr) {
510  setTextLengthBaseValue(SVGLength(this, LengthModeOther, attr->value()));
511  if (textLength().value() < 0.0) {
512  document()->accessSVGExtensions()->reportError("A negative value for text attribute <textLength> is not allowed");
513  }
514  } else {
515  if (SVGTests::parseMappedAttribute(attr)) {
516  return;
517  }
518  if (SVGLangSpace::parseMappedAttribute(attr)) {
519  if (attr->id() == ATTR_XML_SPACE) {
520  static const DOMString preserveString("preserve");
521 
522  if (attr->value() == preserveString) {
523  addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_PRE);
524  } else {
525  addCSSProperty(attr, CSS_PROP_WHITE_SPACE, CSS_VAL_NOWRAP);
526  }
527  }
528  return;
529  }
530  if (SVGExternalResourcesRequired::parseMappedAttribute(attr)) {
531  return;
532  }
533 
534  SVGStyledElement::parseMappedAttribute(attr);
535  }
536 }
537 
538 bool SVGTextContentElement::isKnownAttribute(const QualifiedName &attrName)
539 {
540  return (attrName.matches(SVGNames::lengthAdjustAttr) ||
541  attrName.matches(SVGNames::textLengthAttr) ||
542  SVGTests::isKnownAttribute(attrName) ||
543  SVGLangSpace::isKnownAttribute(attrName) ||
544  SVGExternalResourcesRequired::isKnownAttribute(attrName) ||
545  SVGStyledElement::isKnownAttribute(attrName));
546 }
547 
548 }
549 
550 #endif // ENABLE(SVG)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 16 2021 22:48:02 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.