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