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