1 /*
2 * Copyright (C) 2004, 2005, 2007 Apple Inc. All rights reserved.
3 * (C) 2005 Rob Buis <buis@kde.org>
4 * (C) 2006 Alexander Kellett <lypanov@kde.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include "config.h"
29
30 #if ENABLE(SVG)
31 #include "SVGRenderTreeAsText.h"
32
33 #include "GraphicsTypes.h"
34 #include "InlineTextBox.h"
35 #include "HTMLNames.h"
36 #include "RenderSVGContainer.h"
37 #include "RenderSVGInlineText.h"
38 #include "RenderSVGText.h"
39 #include "RenderSVGRoot.h"
40 #include "RenderTreeAsText.h"
41 #include "SVGCharacterLayoutInfo.h"
42 #include "SVGInlineTextBox.h"
43 #include "SVGPaintServerGradient.h"
44 #include "SVGPaintServerPattern.h"
45 #include "SVGPaintServerSolid.h"
46 #include "SVGResourceClipper.h"
47 #include "SVGRootInlineBox.h"
48 #include "SVGStyledElement.h"
49 #include <math.h>
50
51 namespace WebCore {
52
53 /** class + iomanip to help streaming list separators, i.e. ", " in string "a, b, c, d"
54 * Can be used in cases where you don't know which item in the list is the first
55 * one to be printed, but still want to avoid strings like ", b, c".
56 */
57 class TextStreamSeparator {
58 public:
TextStreamSeparator(const String & s)59 TextStreamSeparator(const String& s)
60 : m_separator(s)
61 , m_needToSeparate(false)
62 {
63 }
64
65 private:
66 friend TextStream& operator<<(TextStream&, TextStreamSeparator&);
67
68 String m_separator;
69 bool m_needToSeparate;
70 };
71
operator <<(TextStream & ts,TextStreamSeparator & sep)72 TextStream& operator<<(TextStream& ts, TextStreamSeparator& sep)
73 {
74 if (sep.m_needToSeparate)
75 ts << sep.m_separator;
76 else
77 sep.m_needToSeparate = true;
78 return ts;
79 }
80
operator <<(TextStream & ts,const IntPoint & p)81 TextStream& operator<<(TextStream& ts, const IntPoint& p)
82 {
83 return ts << "(" << p.x() << "," << p.y() << ")";
84 }
85
operator <<(TextStream & ts,const IntRect & r)86 TextStream& operator<<(TextStream& ts, const IntRect& r)
87 {
88 return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height();
89 }
90
hasFractions(double val)91 static bool hasFractions(double val)
92 {
93 double epsilon = 0.0001;
94 int ival = static_cast<int>(val);
95 double dval = static_cast<double>(ival);
96 return fabs(val - dval) > epsilon;
97 }
98
operator <<(TextStream & ts,const FloatRect & r)99 TextStream& operator<<(TextStream& ts, const FloatRect &r)
100 {
101 ts << "at (";
102 if (hasFractions(r.x()))
103 ts << r.x();
104 else
105 ts << int(r.x());
106 ts << ",";
107 if (hasFractions(r.y()))
108 ts << r.y();
109 else
110 ts << int(r.y());
111 ts << ") size ";
112 if (hasFractions(r.width()))
113 ts << r.width();
114 else
115 ts << int(r.width());
116 ts << "x";
117 if (hasFractions(r.height()))
118 ts << r.height();
119 else
120 ts << int(r.height());
121 return ts;
122 }
123
operator <<(TextStream & ts,const FloatPoint & p)124 TextStream& operator<<(TextStream& ts, const FloatPoint& p)
125 {
126 ts << "(";
127 if (hasFractions(p.x()))
128 ts << p.x();
129 else
130 ts << int(p.x());
131 ts << ",";
132 if (hasFractions(p.y()))
133 ts << p.y();
134 else
135 ts << int(p.y());
136 return ts << ")";
137 }
138
operator <<(TextStream & ts,const FloatSize & s)139 TextStream& operator<<(TextStream& ts, const FloatSize& s)
140 {
141 ts << "width=";
142 if (hasFractions(s.width()))
143 ts << s.width();
144 else
145 ts << int(s.width());
146 ts << " height=";
147 if (hasFractions(s.height()))
148 ts << s.height();
149 else
150 ts << int(s.height());
151 return ts;
152 }
153
operator <<(TextStream & ts,const TransformationMatrix & transform)154 TextStream& operator<<(TextStream& ts, const TransformationMatrix& transform)
155 {
156 if (transform.isIdentity())
157 ts << "identity";
158 else
159 ts << "{m=(("
160 << transform.a() << "," << transform.b()
161 << ")("
162 << transform.c() << "," << transform.d()
163 << ")) t=("
164 << transform.e() << "," << transform.f()
165 << ")}";
166
167 return ts;
168 }
169
operator <<(TextStream & ts,const Color & c)170 TextStream& operator<<(TextStream& ts, const Color& c)
171 {
172 return ts << c.name();
173 }
174
writeIndent(TextStream & ts,int indent)175 static void writeIndent(TextStream& ts, int indent)
176 {
177 for (int i = 0; i != indent; ++i)
178 ts << " ";
179 }
180
181 // FIXME: Maybe this should be in KCanvasRenderingStyle.cpp
operator <<(TextStream & ts,const DashArray & a)182 static TextStream& operator<<(TextStream& ts, const DashArray& a)
183 {
184 ts << "{";
185 DashArray::const_iterator end = a.end();
186 for (DashArray::const_iterator it = a.begin(); it != end; ++it) {
187 if (it != a.begin())
188 ts << ", ";
189 ts << *it;
190 }
191 ts << "}";
192 return ts;
193 }
194
195 // FIXME: Maybe this should be in GraphicsTypes.cpp
operator <<(TextStream & ts,LineCap style)196 static TextStream& operator<<(TextStream& ts, LineCap style)
197 {
198 switch (style) {
199 case ButtCap:
200 ts << "BUTT";
201 break;
202 case RoundCap:
203 ts << "ROUND";
204 break;
205 case SquareCap:
206 ts << "SQUARE";
207 break;
208 }
209 return ts;
210 }
211
212 // FIXME: Maybe this should be in GraphicsTypes.cpp
operator <<(TextStream & ts,LineJoin style)213 static TextStream& operator<<(TextStream& ts, LineJoin style)
214 {
215 switch (style) {
216 case MiterJoin:
217 ts << "MITER";
218 break;
219 case RoundJoin:
220 ts << "ROUND";
221 break;
222 case BevelJoin:
223 ts << "BEVEL";
224 break;
225 }
226 return ts;
227 }
228
writeStyle(TextStream & ts,const RenderObject & object)229 static void writeStyle(TextStream& ts, const RenderObject& object)
230 {
231 const RenderStyle* style = object.style();
232 const SVGRenderStyle* svgStyle = style->svgStyle();
233
234 if (!object.localTransform().isIdentity())
235 ts << " [transform=" << object.localTransform() << "]";
236 if (svgStyle->imageRendering() != SVGRenderStyle::initialImageRendering()) {
237 unsigned imageRenderingAsInteger = svgStyle->imageRendering();
238 ts << " [image rendering=" << imageRenderingAsInteger << "]";
239 }
240 if (style->opacity() != RenderStyle::initialOpacity())
241 ts << " [opacity=" << style->opacity() << "]";
242 if (object.isRenderPath()) {
243 const RenderPath& path = static_cast<const RenderPath&>(object);
244 SVGPaintServer* strokePaintServer = SVGPaintServer::strokePaintServer(style, &path);
245 if (strokePaintServer) {
246 TextStreamSeparator s(" ");
247 ts << " [stroke={";
248 if (strokePaintServer)
249 ts << s << *strokePaintServer;
250
251 double dashOffset = SVGRenderStyle::cssPrimitiveToLength(&path, svgStyle->strokeDashOffset(), 0.0f);
252 const DashArray& dashArray = dashArrayFromRenderingStyle(style);
253 double strokeWidth = SVGRenderStyle::cssPrimitiveToLength(&path, svgStyle->strokeWidth(), 1.0f);
254
255 if (svgStyle->strokeOpacity() != 1.0f)
256 ts << s << "[opacity=" << svgStyle->strokeOpacity() << "]";
257 if (strokeWidth != 1.0f)
258 ts << s << "[stroke width=" << strokeWidth << "]";
259 if (svgStyle->strokeMiterLimit() != 4)
260 ts << s << "[miter limit=" << svgStyle->strokeMiterLimit() << "]";
261 if (svgStyle->capStyle() != 0)
262 ts << s << "[line cap=" << svgStyle->capStyle() << "]";
263 if (svgStyle->joinStyle() != 0)
264 ts << s << "[line join=" << svgStyle->joinStyle() << "]";
265 if (dashOffset != 0.0f)
266 ts << s << "[dash offset=" << dashOffset << "]";
267 if (!dashArray.isEmpty())
268 ts << s << "[dash array=" << dashArray << "]";
269 ts << "}]";
270 }
271 SVGPaintServer* fillPaintServer = SVGPaintServer::fillPaintServer(style, &path);
272 if (fillPaintServer) {
273 TextStreamSeparator s(" ");
274 ts << " [fill={";
275 if (fillPaintServer)
276 ts << s << *fillPaintServer;
277
278 if (style->svgStyle()->fillOpacity() != 1.0f)
279 ts << s << "[opacity=" << style->svgStyle()->fillOpacity() << "]";
280 if (style->svgStyle()->fillRule() != RULE_NONZERO)
281 ts << s << "[fill rule=" << style->svgStyle()->fillRule() << "]";
282 ts << "}]";
283 }
284 }
285 if (!svgStyle->clipPath().isEmpty())
286 ts << " [clip path=\"" << svgStyle->clipPath() << "\"]";
287 if (!svgStyle->startMarker().isEmpty())
288 ts << " [start marker=" << svgStyle->startMarker() << "]";
289 if (!svgStyle->midMarker().isEmpty())
290 ts << " [middle marker=" << svgStyle->midMarker() << "]";
291 if (!svgStyle->endMarker().isEmpty())
292 ts << " [end marker=" << svgStyle->endMarker() << "]";
293 if (!svgStyle->filter().isEmpty())
294 ts << " [filter=" << svgStyle->filter() << "]";
295 }
296
operator <<(TextStream & ts,const RenderPath & path)297 static TextStream& operator<<(TextStream& ts, const RenderPath& path)
298 {
299 ts << " " << path.absoluteTransform().mapRect(path.relativeBBox());
300
301 writeStyle(ts, path);
302
303 ts << " [data=\"" << path.path().debugString() << "\"]";
304
305 return ts;
306 }
307
operator <<(TextStream & ts,const RenderSVGContainer & container)308 static TextStream& operator<<(TextStream& ts, const RenderSVGContainer& container)
309 {
310 ts << " " << container.absoluteTransform().mapRect(container.relativeBBox());
311
312 writeStyle(ts, container);
313
314 return ts;
315 }
316
operator <<(TextStream & ts,const RenderSVGRoot & root)317 static TextStream& operator<<(TextStream& ts, const RenderSVGRoot& root)
318 {
319 ts << " " << root.absoluteTransform().mapRect(root.relativeBBox());
320
321 writeStyle(ts, root);
322
323 return ts;
324 }
325
operator <<(TextStream & ts,const RenderSVGText & text)326 static TextStream& operator<<(TextStream& ts, const RenderSVGText& text)
327 {
328 SVGRootInlineBox* box = static_cast<SVGRootInlineBox*>(text.firstRootBox());
329
330 if (!box)
331 return ts;
332
333 Vector<SVGTextChunk>& chunks = const_cast<Vector<SVGTextChunk>& >(box->svgTextChunks());
334 ts << " at (" << text.x() << "," << text.y() << ") size " << box->width() << "x" << box->height() << " contains " << chunks.size() << " chunk(s)";
335
336 if (text.parent() && (text.parent()->style()->color() != text.style()->color()))
337 ts << " [color=" << text.style()->color().name() << "]";
338
339 return ts;
340 }
341
containsInlineTextBox(SVGTextChunk & chunk,SVGInlineTextBox * box)342 static inline bool containsInlineTextBox(SVGTextChunk& chunk, SVGInlineTextBox* box)
343 {
344 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin();
345 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end();
346
347 bool found = false;
348 for (; boxIt != boxEnd; ++boxIt) {
349 SVGInlineBoxCharacterRange& range = *boxIt;
350
351 if (box == static_cast<SVGInlineTextBox*>(range.box)) {
352 found = true;
353 break;
354 }
355 }
356
357 return found;
358 }
359
writeSVGInlineTextBox(TextStream & ts,SVGInlineTextBox * textBox,int indent)360 static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent)
361 {
362 SVGRootInlineBox* rootBox = textBox->svgRootInlineBox();
363 if (!rootBox)
364 return;
365
366 Vector<SVGTextChunk>& chunks = const_cast<Vector<SVGTextChunk>& >(rootBox->svgTextChunks());
367
368 Vector<SVGTextChunk>::iterator it = chunks.begin();
369 Vector<SVGTextChunk>::iterator end = chunks.end();
370
371 // Write text chunks
372 unsigned int i = 1;
373 for (; it != end; ++it) {
374 SVGTextChunk& cur = *it;
375
376 // Write inline box character ranges
377 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = cur.boxes.begin();
378 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = cur.boxes.end();
379
380 if (!containsInlineTextBox(cur, textBox)) {
381 i++;
382 continue;
383 }
384
385 writeIndent(ts, indent + 1);
386
387 unsigned int j = 1;
388 ts << "chunk " << i << " ";
389
390 if (cur.anchor == TA_MIDDLE) {
391 ts << "(middle anchor";
392 if (cur.isVerticalText)
393 ts << ", vertical";
394 ts << ") ";
395 } else if (cur.anchor == TA_END) {
396 ts << "(end anchor";
397 if (cur.isVerticalText)
398 ts << ", vertical";
399 ts << ") ";
400 } else if (cur.isVerticalText)
401 ts << "(vertical) ";
402
403 unsigned int totalOffset = 0;
404
405 for (; boxIt != boxEnd; ++boxIt) {
406 SVGInlineBoxCharacterRange& range = *boxIt;
407
408 unsigned int offset = range.endOffset - range.startOffset;
409 ASSERT(cur.start + totalOffset <= cur.end);
410
411 totalOffset += offset;
412
413 if (textBox != static_cast<SVGInlineTextBox*>(range.box)) {
414 j++;
415 continue;
416 }
417
418 FloatPoint topLeft = topLeftPositionOfCharacterRange(cur.start + totalOffset - offset, cur.start + totalOffset);
419
420 ts << "text run " << j << " at (" << topLeft.x() << "," << topLeft.y() << ") ";
421 ts << "startOffset " << range.startOffset << " endOffset " << range.endOffset;
422
423 if (cur.isVerticalText)
424 ts << " height " << cummulatedHeightOfInlineBoxCharacterRange(range);
425 else
426 ts << " width " << cummulatedWidthOfInlineBoxCharacterRange(range);
427
428 if (textBox->direction() == RTL || textBox->m_dirOverride) {
429 ts << (textBox->direction() == RTL ? " RTL" : " LTR");
430
431 if (textBox->m_dirOverride)
432 ts << " override";
433 }
434
435 ts << ": " << quoteAndEscapeNonPrintables(String(textBox->textObject()->text()).substring(textBox->start() + range.startOffset, offset)) << "\n";
436
437 j++;
438 }
439
440 i++;
441 }
442 }
443
writeSVGInlineText(TextStream & ts,const RenderSVGInlineText & text,int indent)444 static inline void writeSVGInlineText(TextStream& ts, const RenderSVGInlineText& text, int indent)
445 {
446 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox())
447 writeSVGInlineTextBox(ts, static_cast<SVGInlineTextBox*>(box), indent);
448 }
449
getTagName(SVGStyledElement * elem)450 static String getTagName(SVGStyledElement* elem)
451 {
452 if (elem)
453 return elem->nodeName();
454 return "";
455 }
456
write(TextStream & ts,const RenderSVGContainer & container,int indent)457 void write(TextStream& ts, const RenderSVGContainer& container, int indent)
458 {
459 writeIndent(ts, indent);
460 ts << container.renderName();
461
462 if (container.element()) {
463 String tagName = getTagName(static_cast<SVGStyledElement*>(container.element()));
464 if (!tagName.isEmpty())
465 ts << " {" << tagName << "}";
466 }
467
468 ts << container << "\n";
469
470 for (RenderObject* child = container.firstChild(); child; child = child->nextSibling())
471 write(ts, *child, indent + 1);
472 }
473
write(TextStream & ts,const RenderSVGRoot & root,int indent)474 void write(TextStream& ts, const RenderSVGRoot& root, int indent)
475 {
476 writeIndent(ts, indent);
477 ts << root.renderName();
478
479 if (root.element()) {
480 String tagName = getTagName(static_cast<SVGStyledElement*>(root.element()));
481 if (!tagName.isEmpty())
482 ts << " {" << tagName << "}";
483 }
484
485 ts << root << "\n";
486
487 for (RenderObject* child = root.firstChild(); child; child = child->nextSibling())
488 write(ts, *child, indent + 1);
489 }
490
write(TextStream & ts,const RenderSVGText & text,int indent)491 void write(TextStream& ts, const RenderSVGText& text, int indent)
492 {
493 writeIndent(ts, indent);
494 ts << text.renderName();
495
496 if (text.element()) {
497 String tagName = getTagName(static_cast<SVGStyledElement*>(text.element()));
498 if (!tagName.isEmpty())
499 ts << " {" << tagName << "}";
500 }
501
502 ts << text << "\n";
503
504 for (RenderObject* child = text.firstChild(); child; child = child->nextSibling())
505 write(ts, *child, indent + 1);
506 }
507
write(TextStream & ts,const RenderSVGInlineText & text,int indent)508 void write(TextStream& ts, const RenderSVGInlineText& text, int indent)
509 {
510 writeIndent(ts, indent);
511 ts << text.renderName();
512
513 if (text.element()) {
514 String tagName = getTagName(static_cast<SVGStyledElement*>(text.element()));
515 if (!tagName.isEmpty())
516 ts << " {" << tagName << "}";
517 }
518
519 IntRect linesBox = text.linesBoundingBox();
520
521 ts << " at (" << text.firstRunX() << "," << text.firstRunY() << ") size " << linesBox.width() << "x" << linesBox.height() << "\n";
522 writeSVGInlineText(ts, text, indent);
523 }
524
write(TextStream & ts,const RenderPath & path,int indent)525 void write(TextStream& ts, const RenderPath& path, int indent)
526 {
527 writeIndent(ts, indent);
528 ts << path.renderName();
529
530 if (path.element()) {
531 String tagName = getTagName(static_cast<SVGStyledElement*>(path.element()));
532 if (!tagName.isEmpty())
533 ts << " {" << tagName << "}";
534 }
535
536 ts << path << "\n";
537 }
538
writeRenderResources(TextStream & ts,Node * parent)539 void writeRenderResources(TextStream& ts, Node* parent)
540 {
541 ASSERT(parent);
542 Node* node = parent;
543 do {
544 if (!node->isSVGElement())
545 continue;
546 SVGElement* svgElement = static_cast<SVGElement*>(node);
547 if (!svgElement->isStyled())
548 continue;
549
550 SVGStyledElement* styled = static_cast<SVGStyledElement*>(svgElement);
551 RefPtr<SVGResource> resource(styled->canvasResource());
552 if (!resource)
553 continue;
554
555 String elementId = svgElement->getAttribute(HTMLNames::idAttr);
556 if (resource->isPaintServer()) {
557 RefPtr<SVGPaintServer> paintServer = WTF::static_pointer_cast<SVGPaintServer>(resource);
558 ts << "KRenderingPaintServer {id=\"" << elementId << "\" " << *paintServer << "}" << "\n";
559 } else
560 ts << "KCanvasResource {id=\"" << elementId << "\" " << *resource << "}" << "\n";
561 } while ((node = node->traverseNextNode(parent)));
562 }
563
564 } // namespace WebCore
565
566 #endif // ENABLE(SVG)
567