a93afd01ed919a6e97d662545ab65cbe5ee84824
[WebKit-https.git] / Source / WebCore / rendering / svg / SVGRenderTreeAsText.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2007, 2009 Apple Inc. All rights reserved.
3  *           (C) 2005 Rob Buis <buis@kde.org>
4  *           (C) 2006 Alexander Kellett <lypanov@kde.org>
5  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30
31 #if ENABLE(SVG)
32 #include "SVGRenderTreeAsText.h"
33
34 #include "GraphicsTypes.h"
35 #include "HTMLNames.h"
36 #include "InlineTextBox.h"
37 #include "LinearGradientAttributes.h"
38 #include "NodeRenderStyle.h"
39 #include "Path.h"
40 #include "PatternAttributes.h"
41 #include "RadialGradientAttributes.h"
42 #include "RenderImage.h"
43 #include "RenderSVGContainer.h"
44 #include "RenderSVGGradientStop.h"
45 #include "RenderSVGImage.h"
46 #include "RenderSVGInlineText.h"
47 #include "RenderSVGPath.h"
48 #include "RenderSVGResourceClipper.h"
49 #include "RenderSVGResourceFilter.h"
50 #include "RenderSVGResourceGradient.h"
51 #include "RenderSVGResourceLinearGradient.h"
52 #include "RenderSVGResourceMarker.h"
53 #include "RenderSVGResourceMasker.h"
54 #include "RenderSVGResourcePattern.h"
55 #include "RenderSVGResourceRadialGradient.h"
56 #include "RenderSVGResourceSolidColor.h"
57 #include "RenderSVGRoot.h"
58 #include "RenderSVGShape.h"
59 #include "RenderSVGText.h"
60 #include "RenderTreeAsText.h"
61 #include "SVGCircleElement.h"
62 #include "SVGEllipseElement.h"
63 #include "SVGInlineTextBox.h"
64 #include "SVGLineElement.h"
65 #include "SVGLinearGradientElement.h"
66 #include "SVGNames.h"
67 #include "SVGPathElement.h"
68 #include "SVGPathUtilities.h"
69 #include "SVGPatternElement.h"
70 #include "SVGPointList.h"
71 #include "SVGPolyElement.h"
72 #include "SVGRadialGradientElement.h"
73 #include "SVGRectElement.h"
74 #include "SVGRootInlineBox.h"
75 #include "SVGStopElement.h"
76 #include "SVGStyledElement.h"
77
78 #include <math.h>
79
80 namespace WebCore {
81
82 /** class + iomanip to help streaming list separators, i.e. ", " in string "a, b, c, d"
83  * Can be used in cases where you don't know which item in the list is the first
84  * one to be printed, but still want to avoid strings like ", b, c".
85  */
86 class TextStreamSeparator {
87 public:
88     TextStreamSeparator(const String& s)
89         : m_separator(s)
90         , m_needToSeparate(false)
91     {
92     }
93
94 private:
95     friend TextStream& operator<<(TextStream&, TextStreamSeparator&);
96
97     String m_separator;
98     bool m_needToSeparate;
99 };
100
101 TextStream& operator<<(TextStream& ts, TextStreamSeparator& sep)
102 {
103     if (sep.m_needToSeparate)
104         ts << sep.m_separator;
105     else
106         sep.m_needToSeparate = true;
107     return ts;
108 }
109
110 template<typename ValueType>
111 static void writeNameValuePair(TextStream& ts, const char* name, ValueType value)
112 {
113     ts << " [" << name << "=" << value << "]";
114 }
115
116 template<typename ValueType>
117 static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value)
118 {
119     ts << " [" << name << "=\"" << value << "\"]";
120 }
121
122 static void writeIfNotEmpty(TextStream& ts, const char* name, const String& value)
123 {
124     if (!value.isEmpty())
125         writeNameValuePair(ts, name, value);
126 }
127
128 template<typename ValueType>
129 static void writeIfNotDefault(TextStream& ts, const char* name, ValueType value, ValueType defaultValue)
130 {
131     if (value != defaultValue)
132         writeNameValuePair(ts, name, value);
133 }
134
135 TextStream& operator<<(TextStream& ts, const FloatRect& r)
136 {
137     ts << "at (" << TextStream::FormatNumberRespectingIntegers(r.x());
138     ts << "," << TextStream::FormatNumberRespectingIntegers(r.y());
139     ts << ") size " << TextStream::FormatNumberRespectingIntegers(r.width());
140     ts << "x" << TextStream::FormatNumberRespectingIntegers(r.height());
141     return ts;
142 }
143
144 TextStream& operator<<(TextStream& ts, const AffineTransform& transform)
145 {
146     if (transform.isIdentity())
147         ts << "identity";
148     else
149         ts << "{m=(("
150            << transform.a() << "," << transform.b()
151            << ")("
152            << transform.c() << "," << transform.d()
153            << ")) t=("
154            << transform.e() << "," << transform.f()
155            << ")}";
156
157     return ts;
158 }
159
160 static TextStream& operator<<(TextStream& ts, const WindRule rule)
161 {
162     switch (rule) {
163     case RULE_NONZERO:
164         ts << "NON-ZERO";
165         break;
166     case RULE_EVENODD:
167         ts << "EVEN-ODD";
168         break;
169     }
170
171     return ts;
172 }
173
174 static TextStream& operator<<(TextStream& ts, const SVGUnitTypes::SVGUnitType& unitType)
175 {
176     ts << SVGPropertyTraits<SVGUnitTypes::SVGUnitType>::toString(unitType);
177     return ts;
178 }
179
180 static TextStream& operator<<(TextStream& ts, const SVGMarkerUnitsType& markerUnit)
181 {
182     ts << SVGPropertyTraits<SVGMarkerUnitsType>::toString(markerUnit);
183     return ts;
184 }
185
186 TextStream& operator<<(TextStream& ts, const Color& c)
187 {
188     return ts << c.nameForRenderTreeAsText();
189 }
190
191 // FIXME: Maybe this should be in KCanvasRenderingStyle.cpp
192 static TextStream& operator<<(TextStream& ts, const DashArray& a)
193 {
194     ts << "{";
195     DashArray::const_iterator end = a.end();
196     for (DashArray::const_iterator it = a.begin(); it != end; ++it) {
197         if (it != a.begin())
198             ts << ", ";
199         ts << *it;
200     }
201     ts << "}";
202     return ts;
203 }
204
205 // FIXME: Maybe this should be in GraphicsTypes.cpp
206 static TextStream& operator<<(TextStream& ts, LineCap style)
207 {
208     switch (style) {
209     case ButtCap:
210         ts << "BUTT";
211         break;
212     case RoundCap:
213         ts << "ROUND";
214         break;
215     case SquareCap:
216         ts << "SQUARE";
217         break;
218     }
219     return ts;
220 }
221
222 // FIXME: Maybe this should be in GraphicsTypes.cpp
223 static TextStream& operator<<(TextStream& ts, LineJoin style)
224 {
225     switch (style) {
226     case MiterJoin:
227         ts << "MITER";
228         break;
229     case RoundJoin:
230         ts << "ROUND";
231         break;
232     case BevelJoin:
233         ts << "BEVEL";
234         break;
235     }
236     return ts;
237 }
238
239 static TextStream& operator<<(TextStream& ts, const SVGSpreadMethodType& type)
240 {
241     ts << SVGPropertyTraits<SVGSpreadMethodType>::toString(type).upper();
242     return ts;
243 }
244
245 static void writeSVGPaintingResource(TextStream& ts, RenderSVGResource* resource)
246 {
247     if (resource->resourceType() == SolidColorResourceType) {
248         ts << "[type=SOLID] [color=" << static_cast<RenderSVGResourceSolidColor*>(resource)->color() << "]";
249         return;
250     }
251
252     // All other resources derive from RenderSVGResourceContainer
253     RenderSVGResourceContainer* container = static_cast<RenderSVGResourceContainer*>(resource);
254     Node* node = container->node();
255     ASSERT(node);
256     ASSERT(node->isSVGElement());
257
258     if (resource->resourceType() == PatternResourceType)
259         ts << "[type=PATTERN]";
260     else if (resource->resourceType() == LinearGradientResourceType)
261         ts << "[type=LINEAR-GRADIENT]";
262     else if (resource->resourceType() == RadialGradientResourceType)
263         ts << "[type=RADIAL-GRADIENT]";
264
265     ts << " [id=\"" << toSVGElement(node)->getIdAttribute() << "\"]";
266 }
267
268 static void writeStyle(TextStream& ts, const RenderObject& object)
269 {
270     const RenderStyle* style = object.style();
271     const SVGRenderStyle* svgStyle = style->svgStyle();
272
273     if (!object.localTransform().isIdentity())
274         writeNameValuePair(ts, "transform", object.localTransform());
275     writeIfNotDefault(ts, "image rendering", style->imageRendering(), RenderStyle::initialImageRendering());
276     writeIfNotDefault(ts, "opacity", style->opacity(), RenderStyle::initialOpacity());
277     if (object.isSVGShape()) {
278         const RenderSVGShape& shape = static_cast<const RenderSVGShape&>(object);
279         ASSERT(shape.node());
280         ASSERT(shape.node()->isSVGElement());
281
282         Color fallbackColor;
283         if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(const_cast<RenderSVGShape*>(&shape), shape.style(), fallbackColor)) {
284             TextStreamSeparator s(" ");
285             ts << " [stroke={" << s;
286             writeSVGPaintingResource(ts, strokePaintingResource);
287
288             SVGLengthContext lengthContext(toSVGElement(shape.node()));
289             double dashOffset = svgStyle->strokeDashOffset().value(lengthContext);
290             double strokeWidth = svgStyle->strokeWidth().value(lengthContext);
291             const Vector<SVGLength>& dashes = svgStyle->strokeDashArray();
292
293             DashArray dashArray;
294             const Vector<SVGLength>::const_iterator end = dashes.end();
295             for (Vector<SVGLength>::const_iterator it = dashes.begin(); it != end; ++it)
296                 dashArray.append((*it).value(lengthContext));
297
298             writeIfNotDefault(ts, "opacity", svgStyle->strokeOpacity(), 1.0f);
299             writeIfNotDefault(ts, "stroke width", strokeWidth, 1.0);
300             writeIfNotDefault(ts, "miter limit", svgStyle->strokeMiterLimit(), 4.0f);
301             writeIfNotDefault(ts, "line cap", svgStyle->capStyle(), ButtCap);
302             writeIfNotDefault(ts, "line join", svgStyle->joinStyle(), MiterJoin);
303             writeIfNotDefault(ts, "dash offset", dashOffset, 0.0);
304             if (!dashArray.isEmpty())
305                 writeNameValuePair(ts, "dash array", dashArray);
306
307             ts << "}]";
308         }
309
310         if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(const_cast<RenderSVGShape*>(&shape), shape.style(), fallbackColor)) {
311             TextStreamSeparator s(" ");
312             ts << " [fill={" << s;
313             writeSVGPaintingResource(ts, fillPaintingResource);
314
315             writeIfNotDefault(ts, "opacity", svgStyle->fillOpacity(), 1.0f);
316             writeIfNotDefault(ts, "fill rule", svgStyle->fillRule(), RULE_NONZERO);
317             ts << "}]";
318         }
319         writeIfNotDefault(ts, "clip rule", svgStyle->clipRule(), RULE_NONZERO);
320     }
321
322     writeIfNotEmpty(ts, "start marker", svgStyle->markerStartResource());
323     writeIfNotEmpty(ts, "middle marker", svgStyle->markerMidResource());
324     writeIfNotEmpty(ts, "end marker", svgStyle->markerEndResource());
325 }
326
327 static TextStream& writePositionAndStyle(TextStream& ts, const RenderObject& object)
328 {
329     ts << " " << enclosingIntRect(const_cast<RenderObject&>(object).absoluteClippedOverflowRect());
330     writeStyle(ts, object);
331     return ts;
332 }
333
334 static TextStream& operator<<(TextStream& ts, const RenderSVGShape& shape)
335 {
336     writePositionAndStyle(ts, shape);
337
338     ASSERT(shape.node()->isSVGElement());
339     SVGElement* svgElement = toSVGElement(shape.node());
340     SVGLengthContext lengthContext(svgElement);
341
342     if (svgElement->hasTagName(SVGNames::rectTag)) {
343         SVGRectElement* element = static_cast<SVGRectElement*>(svgElement);
344         writeNameValuePair(ts, "x", element->x().value(lengthContext));
345         writeNameValuePair(ts, "y", element->y().value(lengthContext));
346         writeNameValuePair(ts, "width", element->width().value(lengthContext));
347         writeNameValuePair(ts, "height", element->height().value(lengthContext));
348     } else if (svgElement->hasTagName(SVGNames::lineTag)) {
349         SVGLineElement* element = static_cast<SVGLineElement*>(svgElement);
350         writeNameValuePair(ts, "x1", element->x1().value(lengthContext));
351         writeNameValuePair(ts, "y1", element->y1().value(lengthContext));
352         writeNameValuePair(ts, "x2", element->x2().value(lengthContext));
353         writeNameValuePair(ts, "y2", element->y2().value(lengthContext));
354     } else if (svgElement->hasTagName(SVGNames::ellipseTag)) {
355         SVGEllipseElement* element = static_cast<SVGEllipseElement*>(svgElement);
356         writeNameValuePair(ts, "cx", element->cx().value(lengthContext));
357         writeNameValuePair(ts, "cy", element->cy().value(lengthContext));
358         writeNameValuePair(ts, "rx", element->rx().value(lengthContext));
359         writeNameValuePair(ts, "ry", element->ry().value(lengthContext));
360     } else if (svgElement->hasTagName(SVGNames::circleTag)) {
361         SVGCircleElement* element = static_cast<SVGCircleElement*>(svgElement);
362         writeNameValuePair(ts, "cx", element->cx().value(lengthContext));
363         writeNameValuePair(ts, "cy", element->cy().value(lengthContext));
364         writeNameValuePair(ts, "r", element->r().value(lengthContext));
365     } else if (svgElement->hasTagName(SVGNames::polygonTag) || svgElement->hasTagName(SVGNames::polylineTag)) {
366         SVGPolyElement* element = static_cast<SVGPolyElement*>(svgElement);
367         writeNameAndQuotedValue(ts, "points", element->pointList().valueAsString());
368     } else if (svgElement->hasTagName(SVGNames::pathTag)) {
369         SVGPathElement* element = static_cast<SVGPathElement*>(svgElement);
370         String pathString;
371         // FIXME: We should switch to UnalteredParsing here - this will affect the path dumping output of dozens of tests.
372         buildStringFromByteStream(element->pathByteStream(), pathString, NormalizedParsing);
373         writeNameAndQuotedValue(ts, "data", pathString);
374     } else
375         ASSERT_NOT_REACHED();
376     return ts;
377 }
378
379 static TextStream& operator<<(TextStream& ts, const RenderSVGRoot& root)
380 {
381     return writePositionAndStyle(ts, root);
382 }
383
384 static void writeRenderSVGTextBox(TextStream& ts, const RenderSVGText& text)
385 {
386     SVGRootInlineBox* box = static_cast<SVGRootInlineBox*>(text.firstRootBox());
387     if (!box)
388         return;
389
390     ts << " " << enclosingIntRect(FloatRect(text.location(), FloatSize(box->logicalWidth(), box->logicalHeight())));
391     
392     // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now.
393     ts << " contains 1 chunk(s)";
394
395     if (text.parent() && (text.parent()->style()->visitedDependentColor(CSSPropertyColor) != text.style()->visitedDependentColor(CSSPropertyColor)))
396         writeNameValuePair(ts, "color", text.style()->visitedDependentColor(CSSPropertyColor).nameForRenderTreeAsText());
397 }
398
399 static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent)
400 {
401     Vector<SVGTextFragment>& fragments = textBox->textFragments();
402     if (fragments.isEmpty())
403         return;
404
405     RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer());
406     ASSERT(textRenderer);
407
408     const SVGRenderStyle* svgStyle = textRenderer->style()->svgStyle();
409     String text = textBox->textRenderer()->text();
410
411     unsigned fragmentsSize = fragments.size();
412     for (unsigned i = 0; i < fragmentsSize; ++i) {
413         SVGTextFragment& fragment = fragments.at(i);
414         writeIndent(ts, indent + 1);
415
416         unsigned startOffset = fragment.characterOffset;
417         unsigned endOffset = fragment.characterOffset + fragment.length;
418
419         // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now.
420         ts << "chunk 1 ";
421         ETextAnchor anchor = svgStyle->textAnchor();
422         bool isVerticalText = svgStyle->isVerticalWritingMode();
423         if (anchor == TA_MIDDLE) {
424             ts << "(middle anchor";
425             if (isVerticalText)
426                 ts << ", vertical";
427             ts << ") ";
428         } else if (anchor == TA_END) {
429             ts << "(end anchor";
430             if (isVerticalText)
431                 ts << ", vertical";
432             ts << ") ";
433         } else if (isVerticalText)
434             ts << "(vertical) ";
435         startOffset -= textBox->start();
436         endOffset -= textBox->start();
437         // </hack>
438
439         ts << "text run " << i + 1 << " at (" << fragment.x << "," << fragment.y << ")";
440         ts << " startOffset " << startOffset << " endOffset " << endOffset;
441         if (isVerticalText)
442             ts << " height " << fragment.height;
443         else
444             ts << " width " << fragment.width;
445
446         if (!textBox->isLeftToRightDirection() || textBox->dirOverride()) {
447             ts << (textBox->isLeftToRightDirection() ? " LTR" : " RTL");
448             if (textBox->dirOverride())
449                 ts << " override";
450         }
451
452         ts << ": " << quoteAndEscapeNonPrintables(text.substring(fragment.characterOffset, fragment.length)) << "\n";
453     }
454 }
455
456 static inline void writeSVGInlineTextBoxes(TextStream& ts, const RenderText& text, int indent)
457 {
458     for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
459         if (!box->isSVGInlineTextBox())
460             continue;
461
462         writeSVGInlineTextBox(ts, static_cast<SVGInlineTextBox*>(box), indent);
463     }
464 }
465
466 static void writeStandardPrefix(TextStream& ts, const RenderObject& object, int indent)
467 {
468     writeIndent(ts, indent);
469     ts << object.renderName();
470
471     if (object.node())
472         ts << " {" << object.node()->nodeName() << "}";
473 }
474
475 static void writeChildren(TextStream& ts, const RenderObject& object, int indent)
476 {
477     for (RenderObject* child = object.firstChild(); child; child = child->nextSibling())
478         write(ts, *child, indent + 1);
479 }
480
481 static inline void writeCommonGradientProperties(TextStream& ts, SVGSpreadMethodType spreadMethod, const AffineTransform& gradientTransform, SVGUnitTypes::SVGUnitType gradientUnits)
482 {
483     writeNameValuePair(ts, "gradientUnits", gradientUnits);
484
485     if (spreadMethod != SVGSpreadMethodPad)
486         ts << " [spreadMethod=" << spreadMethod << "]";
487
488     if (!gradientTransform.isIdentity())
489         ts << " [gradientTransform=" << gradientTransform << "]";
490 }
491
492 void writeSVGResourceContainer(TextStream& ts, const RenderObject& object, int indent)
493 {
494     writeStandardPrefix(ts, object, indent);
495
496     Element* element = static_cast<Element*>(object.node());
497     const AtomicString& id = element->getIdAttribute();
498     writeNameAndQuotedValue(ts, "id", id);    
499
500     RenderSVGResourceContainer* resource = const_cast<RenderObject&>(object).toRenderSVGResourceContainer();
501     ASSERT(resource);
502
503     if (resource->resourceType() == MaskerResourceType) {
504         RenderSVGResourceMasker* masker = static_cast<RenderSVGResourceMasker*>(resource);
505         writeNameValuePair(ts, "maskUnits", masker->maskUnits());
506         writeNameValuePair(ts, "maskContentUnits", masker->maskContentUnits());
507         ts << "\n";
508 #if ENABLE(FILTERS)
509     } else if (resource->resourceType() == FilterResourceType) {
510         RenderSVGResourceFilter* filter = static_cast<RenderSVGResourceFilter*>(resource);
511         writeNameValuePair(ts, "filterUnits", filter->filterUnits());
512         writeNameValuePair(ts, "primitiveUnits", filter->primitiveUnits());
513         ts << "\n";
514         // Creating a placeholder filter which is passed to the builder.
515         FloatRect dummyRect;
516         RefPtr<SVGFilter> dummyFilter = SVGFilter::create(AffineTransform(), dummyRect, dummyRect, dummyRect, true);
517         if (RefPtr<SVGFilterBuilder> builder = filter->buildPrimitives(dummyFilter.get())) {
518             if (FilterEffect* lastEffect = builder->lastEffect())
519                 lastEffect->externalRepresentation(ts, indent + 1);
520         }
521 #endif
522     } else if (resource->resourceType() == ClipperResourceType) {
523         RenderSVGResourceClipper* clipper = static_cast<RenderSVGResourceClipper*>(resource);
524         writeNameValuePair(ts, "clipPathUnits", clipper->clipPathUnits());
525         ts << "\n";
526     } else if (resource->resourceType() == MarkerResourceType) {
527         RenderSVGResourceMarker* marker = static_cast<RenderSVGResourceMarker*>(resource);
528         writeNameValuePair(ts, "markerUnits", marker->markerUnits());
529         ts << " [ref at " << marker->referencePoint() << "]";
530         ts << " [angle=";
531         if (marker->angle() == -1)
532             ts << "auto" << "]\n";
533         else
534             ts << marker->angle() << "]\n";
535     } else if (resource->resourceType() == PatternResourceType) {
536         RenderSVGResourcePattern* pattern = static_cast<RenderSVGResourcePattern*>(resource);
537
538         // Dump final results that are used for rendering. No use in asking SVGPatternElement for its patternUnits(), as it may
539         // link to other patterns using xlink:href, we need to build the full inheritance chain, aka. collectPatternProperties()
540         PatternAttributes attributes;
541         static_cast<SVGPatternElement*>(pattern->node())->collectPatternAttributes(attributes);
542
543         writeNameValuePair(ts, "patternUnits", attributes.patternUnits());
544         writeNameValuePair(ts, "patternContentUnits", attributes.patternContentUnits());
545
546         AffineTransform transform = attributes.patternTransform();
547         if (!transform.isIdentity())
548             ts << " [patternTransform=" << transform << "]";
549         ts << "\n";
550     } else if (resource->resourceType() == LinearGradientResourceType) {
551         RenderSVGResourceLinearGradient* gradient = static_cast<RenderSVGResourceLinearGradient*>(resource);
552
553         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
554         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
555         SVGLinearGradientElement* linearGradientElement = static_cast<SVGLinearGradientElement*>(gradient->node());
556
557         LinearGradientAttributes attributes;
558         linearGradientElement->collectGradientAttributes(attributes);
559         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
560
561         ts << " [start=" << gradient->startPoint(attributes) << "] [end=" << gradient->endPoint(attributes) << "]\n";
562     }  else if (resource->resourceType() == RadialGradientResourceType) {
563         RenderSVGResourceRadialGradient* gradient = static_cast<RenderSVGResourceRadialGradient*>(resource);
564
565         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
566         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
567         SVGRadialGradientElement* radialGradientElement = static_cast<SVGRadialGradientElement*>(gradient->node());
568
569         RadialGradientAttributes attributes;
570         radialGradientElement->collectGradientAttributes(attributes);
571         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
572
573         FloatPoint focalPoint = gradient->focalPoint(attributes);
574         FloatPoint centerPoint = gradient->centerPoint(attributes);
575         float radius = gradient->radius(attributes);
576         float focalRadius = gradient->focalRadius(attributes);
577
578         ts << " [center=" << centerPoint << "] [focal=" << focalPoint << "] [radius=" << radius << "] [focalRadius=" << focalRadius << "]\n";
579     } else
580         ts << "\n";
581     writeChildren(ts, object, indent);
582 }
583
584 void writeSVGContainer(TextStream& ts, const RenderObject& container, int indent)
585 {
586     // Currently RenderSVGResourceFilterPrimitive has no meaningful output.
587     if (container.isSVGResourceFilterPrimitive())
588         return;
589     writeStandardPrefix(ts, container, indent);
590     writePositionAndStyle(ts, container);
591     ts << "\n";
592     writeResources(ts, container, indent);
593     writeChildren(ts, container, indent);
594 }
595
596 void write(TextStream& ts, const RenderSVGRoot& root, int indent)
597 {
598     writeStandardPrefix(ts, root, indent);
599     ts << root << "\n";
600     writeChildren(ts, root, indent);
601 }
602
603 void writeSVGText(TextStream& ts, const RenderSVGText& text, int indent)
604 {
605     writeStandardPrefix(ts, text, indent);
606     writeRenderSVGTextBox(ts, text);
607     ts << "\n";
608     writeResources(ts, text, indent);
609     writeChildren(ts, text, indent);
610 }
611
612 void writeSVGInlineText(TextStream& ts, const RenderSVGInlineText& text, int indent)
613 {
614     writeStandardPrefix(ts, text, indent);
615     ts << " " << enclosingIntRect(FloatRect(text.firstRunOrigin(), text.floatLinesBoundingBox().size())) << "\n";
616     writeResources(ts, text, indent);
617     writeSVGInlineTextBoxes(ts, text, indent);
618 }
619
620 void writeSVGImage(TextStream& ts, const RenderSVGImage& image, int indent)
621 {
622     writeStandardPrefix(ts, image, indent);
623     writePositionAndStyle(ts, image);
624     ts << "\n";
625     writeResources(ts, image, indent);
626 }
627
628 void write(TextStream& ts, const RenderSVGShape& shape, int indent)
629 {
630     writeStandardPrefix(ts, shape, indent);
631     ts << shape << "\n";
632     writeResources(ts, shape, indent);
633 }
634
635 void writeSVGGradientStop(TextStream& ts, const RenderSVGGradientStop& stop, int indent)
636 {
637     writeStandardPrefix(ts, stop, indent);
638
639     SVGStopElement* stopElement = static_cast<SVGStopElement*>(stop.node());
640     ASSERT(stopElement);
641
642     RenderStyle* style = stop.style();
643     if (!style)
644         return;
645
646     ts << " [offset=" << stopElement->offset() << "] [color=" << stopElement->stopColorIncludingOpacity() << "]\n";
647 }
648
649 void writeResources(TextStream& ts, const RenderObject& object, int indent)
650 {
651     const RenderStyle* style = object.style();
652     const SVGRenderStyle* svgStyle = style->svgStyle();
653
654     // FIXME: We want to use SVGResourcesCache to determine which resources are present, instead of quering the resource <-> id cache.
655     // For now leave the DRT output as is, but later on we should change this so cycles are properly ignored in the DRT output.
656     RenderObject& renderer = const_cast<RenderObject&>(object);
657     if (!svgStyle->maskerResource().isEmpty()) {
658         if (RenderSVGResourceMasker* masker = getRenderSVGResourceById<RenderSVGResourceMasker>(object.document(), svgStyle->maskerResource())) {
659             writeIndent(ts, indent);
660             ts << " ";
661             writeNameAndQuotedValue(ts, "masker", svgStyle->maskerResource());
662             ts << " ";
663             writeStandardPrefix(ts, *masker, 0);
664             ts << " " << masker->resourceBoundingBox(&renderer) << "\n";
665         }
666     }
667     if (!svgStyle->clipperResource().isEmpty()) {
668         if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(object.document(), svgStyle->clipperResource())) {
669             writeIndent(ts, indent);
670             ts << " ";
671             writeNameAndQuotedValue(ts, "clipPath", svgStyle->clipperResource());
672             ts << " ";
673             writeStandardPrefix(ts, *clipper, 0);
674             ts << " " << clipper->resourceBoundingBox(&renderer) << "\n";
675         }
676     }
677 #if ENABLE(FILTERS)
678     if (!svgStyle->filterResource().isEmpty()) {
679         if (RenderSVGResourceFilter* filter = getRenderSVGResourceById<RenderSVGResourceFilter>(object.document(), svgStyle->filterResource())) {
680             writeIndent(ts, indent);
681             ts << " ";
682             writeNameAndQuotedValue(ts, "filter", svgStyle->filterResource());
683             ts << " ";
684             writeStandardPrefix(ts, *filter, 0);
685             ts << " " << filter->resourceBoundingBox(&renderer) << "\n";
686         }
687     }
688 #endif
689 }
690
691 } // namespace WebCore
692
693 #endif // ENABLE(SVG)