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