Home | History | Annotate | Download | only in svg
      1 /*
      2  * Copyright (C) 2004, 2005, 2007, 2009 Apple Inc. All rights reserved.
      3  *           (C) 2005 Rob Buis <buis (at) kde.org>
      4  *           (C) 2006 Alexander Kellett <lypanov (at) 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 
     72 namespace 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  */
     78 class TextStreamSeparator {
     79 public:
     80     TextStreamSeparator(const String& s)
     81         : m_separator(s)
     82         , m_needToSeparate(false)
     83     {
     84     }
     85 
     86 private:
     87     friend TextStream& operator<<(TextStream&, TextStreamSeparator&);
     88 
     89     String m_separator;
     90     bool m_needToSeparate;
     91 };
     92 
     93 TextStream& 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 
    102 template<typename ValueType>
    103 static void writeNameValuePair(TextStream& ts, const char* name, ValueType value)
    104 {
    105     ts << " [" << name << "=" << value << "]";
    106 }
    107 
    108 template<typename ValueType>
    109 static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value)
    110 {
    111     ts << " [" << name << "=\"" << value << "\"]";
    112 }
    113 
    114 static void writeIfNotEmpty(TextStream& ts, const char* name, const String& value)
    115 {
    116     if (!value.isEmpty())
    117         writeNameValuePair(ts, name, value);
    118 }
    119 
    120 template<typename ValueType>
    121 static void writeIfNotDefault(TextStream& ts, const char* name, ValueType value, ValueType defaultValue)
    122 {
    123     if (value != defaultValue)
    124         writeNameValuePair(ts, name, value);
    125 }
    126 
    127 TextStream& 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 
    143 static 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 
    157 namespace {
    158 
    159 template<typename Enum>
    160 String 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 
    177 static TextStream& operator<<(TextStream& ts, const SVGUnitTypes::SVGUnitType& unitType)
    178 {
    179     ts << SVGEnumerationToString<SVGUnitTypes::SVGUnitType>(unitType);
    180     return ts;
    181 }
    182 
    183 static TextStream& operator<<(TextStream& ts, const SVGMarkerUnitsType& markerUnit)
    184 {
    185     ts << SVGEnumerationToString<SVGMarkerUnitsType>(markerUnit);
    186     return ts;
    187 }
    188 
    189 static 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
    196 static 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
    210 static 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
    227 static 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 
    243 static TextStream& operator<<(TextStream& ts, const SVGSpreadMethodType& type)
    244 {
    245     ts << SVGEnumerationToString<SVGSpreadMethodType>(type).upper();
    246     return ts;
    247 }
    248 
    249 static 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 
    271 static 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 
    330 static 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 
    337 static 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 
    380 static TextStream& operator<<(TextStream& ts, const RenderSVGRoot& root)
    381 {
    382     return writePositionAndStyle(ts, root);
    383 }
    384 
    385 static 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 
    400 static 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 
    456 static 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 
    466 static 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 
    475 static 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 
    481 static 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 
    492 void 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 
    578 void 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 
    590 void 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 
    597 void 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 
    606 void 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 
    614 void 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 
    622 void 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 
    629 void 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 
    643 void 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