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