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