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