Modernize the toRenderSVGResourceContainer() helper.
[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     const auto& resource = toRenderSVGResourceContainer(object);
480
481     if (resource.resourceType() == MaskerResourceType) {
482         const auto& masker = static_cast<const RenderSVGResourceMasker&>(resource);
483         writeNameValuePair(ts, "maskUnits", masker.maskUnits());
484         writeNameValuePair(ts, "maskContentUnits", masker.maskContentUnits());
485         ts << "\n";
486 #if ENABLE(FILTERS)
487     } else if (resource.resourceType() == FilterResourceType) {
488         const auto& filter = static_cast<const RenderSVGResourceFilter&>(resource);
489         writeNameValuePair(ts, "filterUnits", filter.filterUnits());
490         writeNameValuePair(ts, "primitiveUnits", filter.primitiveUnits());
491         ts << "\n";
492         // Creating a placeholder filter which is passed to the builder.
493         FloatRect dummyRect;
494         RefPtr<SVGFilter> dummyFilter = SVGFilter::create(AffineTransform(), dummyRect, dummyRect, dummyRect, true);
495         if (auto builder = filter.buildPrimitives(dummyFilter.get())) {
496             if (FilterEffect* lastEffect = builder->lastEffect())
497                 lastEffect->externalRepresentation(ts, indent + 1);
498         }
499 #endif
500     } else if (resource.resourceType() == ClipperResourceType) {
501         const auto& clipper = static_cast<const RenderSVGResourceClipper&>(resource);
502         writeNameValuePair(ts, "clipPathUnits", clipper.clipPathUnits());
503         ts << "\n";
504     } else if (resource.resourceType() == MarkerResourceType) {
505         const auto& marker = static_cast<const RenderSVGResourceMarker&>(resource);
506         writeNameValuePair(ts, "markerUnits", marker.markerUnits());
507         ts << " [ref at " << marker.referencePoint() << "]";
508         ts << " [angle=";
509         if (marker.angle() == -1)
510             ts << "auto" << "]\n";
511         else
512             ts << marker.angle() << "]\n";
513     } else if (resource.resourceType() == PatternResourceType) {
514         const auto& pattern = static_cast<const RenderSVGResourcePattern&>(resource);
515
516         // Dump final results that are used for rendering. No use in asking SVGPatternElement for its patternUnits(), as it may
517         // link to other patterns using xlink:href, we need to build the full inheritance chain, aka. collectPatternProperties()
518         PatternAttributes attributes;
519         pattern.patternElement().collectPatternAttributes(attributes);
520
521         writeNameValuePair(ts, "patternUnits", attributes.patternUnits());
522         writeNameValuePair(ts, "patternContentUnits", attributes.patternContentUnits());
523
524         AffineTransform transform = attributes.patternTransform();
525         if (!transform.isIdentity())
526             ts << " [patternTransform=" << transform << "]";
527         ts << "\n";
528     } else if (resource.resourceType() == LinearGradientResourceType) {
529         const auto& gradient = static_cast<const RenderSVGResourceLinearGradient&>(resource);
530
531         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
532         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
533         LinearGradientAttributes attributes;
534         gradient.linearGradientElement().collectGradientAttributes(attributes);
535         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
536
537         ts << " [start=" << gradient.startPoint(attributes) << "] [end=" << gradient.endPoint(attributes) << "]\n";
538     }  else if (resource.resourceType() == RadialGradientResourceType) {
539         const auto& gradient = static_cast<const RenderSVGResourceRadialGradient&>(resource);
540
541         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
542         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
543         RadialGradientAttributes attributes;
544         gradient.radialGradientElement().collectGradientAttributes(attributes);
545         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
546
547         FloatPoint focalPoint = gradient.focalPoint(attributes);
548         FloatPoint centerPoint = gradient.centerPoint(attributes);
549         float radius = gradient.radius(attributes);
550         float focalRadius = gradient.focalRadius(attributes);
551
552         ts << " [center=" << centerPoint << "] [focal=" << focalPoint << "] [radius=" << radius << "] [focalRadius=" << focalRadius << "]\n";
553     } else
554         ts << "\n";
555     writeChildren(ts, object, indent);
556 }
557
558 void writeSVGContainer(TextStream& ts, const RenderObject& container, int indent)
559 {
560     // Currently RenderSVGResourceFilterPrimitive has no meaningful output.
561     if (container.isSVGResourceFilterPrimitive())
562         return;
563     writeStandardPrefix(ts, container, indent);
564     writePositionAndStyle(ts, container);
565     ts << "\n";
566     writeResources(ts, container, indent);
567     writeChildren(ts, container, indent);
568 }
569
570 void write(TextStream& ts, const RenderSVGRoot& root, int indent)
571 {
572     writeStandardPrefix(ts, root, indent);
573     ts << root << "\n";
574     writeChildren(ts, root, indent);
575 }
576
577 void writeSVGText(TextStream& ts, const RenderSVGText& text, int indent)
578 {
579     writeStandardPrefix(ts, text, indent);
580     writeRenderSVGTextBox(ts, text);
581     ts << "\n";
582     writeResources(ts, text, indent);
583     writeChildren(ts, text, indent);
584 }
585
586 void writeSVGInlineText(TextStream& ts, const RenderSVGInlineText& text, int indent)
587 {
588     writeStandardPrefix(ts, text, indent);
589     ts << " " << enclosingIntRect(FloatRect(text.firstRunOrigin(), text.floatLinesBoundingBox().size())) << "\n";
590     writeResources(ts, text, indent);
591     writeSVGInlineTextBoxes(ts, text, indent);
592 }
593
594 void writeSVGImage(TextStream& ts, const RenderSVGImage& image, int indent)
595 {
596     writeStandardPrefix(ts, image, indent);
597     writePositionAndStyle(ts, image);
598     ts << "\n";
599     writeResources(ts, image, indent);
600 }
601
602 void write(TextStream& ts, const RenderSVGShape& shape, int indent)
603 {
604     writeStandardPrefix(ts, shape, indent);
605     ts << shape << "\n";
606     writeResources(ts, shape, indent);
607 }
608
609 void writeSVGGradientStop(TextStream& ts, const RenderSVGGradientStop& stop, int indent)
610 {
611     writeStandardPrefix(ts, stop, indent);
612
613     SVGStopElement* stopElement = toSVGStopElement(toSVGElement(stop.element()));
614     ASSERT(stopElement);
615
616     ts << " [offset=" << stopElement->offset() << "] [color=" << stopElement->stopColorIncludingOpacity() << "]\n";
617 }
618
619 void writeResources(TextStream& ts, const RenderObject& object, int indent)
620 {
621     const RenderStyle& style = object.style();
622     const SVGRenderStyle& svgStyle = style.svgStyle();
623
624     // FIXME: We want to use SVGResourcesCache to determine which resources are present, instead of quering the resource <-> id cache.
625     // For now leave the DRT output as is, but later on we should change this so cycles are properly ignored in the DRT output.
626     RenderObject& renderer = const_cast<RenderObject&>(object);
627     if (!svgStyle.maskerResource().isEmpty()) {
628         if (RenderSVGResourceMasker* masker = getRenderSVGResourceById<RenderSVGResourceMasker>(object.document(), svgStyle.maskerResource())) {
629             writeIndent(ts, indent);
630             ts << " ";
631             writeNameAndQuotedValue(ts, "masker", svgStyle.maskerResource());
632             ts << " ";
633             writeStandardPrefix(ts, *masker, 0);
634             ts << " " << masker->resourceBoundingBox(renderer) << "\n";
635         }
636     }
637     if (!svgStyle.clipperResource().isEmpty()) {
638         if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(object.document(), svgStyle.clipperResource())) {
639             writeIndent(ts, indent);
640             ts << " ";
641             writeNameAndQuotedValue(ts, "clipPath", svgStyle.clipperResource());
642             ts << " ";
643             writeStandardPrefix(ts, *clipper, 0);
644             ts << " " << clipper->resourceBoundingBox(renderer) << "\n";
645         }
646     }
647 #if ENABLE(FILTERS)
648     if (!svgStyle.filterResource().isEmpty()) {
649         if (RenderSVGResourceFilter* filter = getRenderSVGResourceById<RenderSVGResourceFilter>(object.document(), svgStyle.filterResource())) {
650             writeIndent(ts, indent);
651             ts << " ";
652             writeNameAndQuotedValue(ts, "filter", svgStyle.filterResource());
653             ts << " ";
654             writeStandardPrefix(ts, *filter, 0);
655             ts << " " << filter->resourceBoundingBox(renderer) << "\n";
656         }
657     }
658 #endif
659 }
660
661 } // namespace WebCore
662
663 #endif // ENABLE(SVG)