[CSS Parser] Unprefix -webkit-writing-mode
[WebKit-https.git] / Source / WebCore / rendering / svg / SVGRenderTreeAsText.cpp
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 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 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 #include "SVGRenderTreeAsText.h"
31
32 #include "GraphicsTypes.h"
33 #include "HTMLNames.h"
34 #include "NodeRenderStyle.h"
35 #include "RenderImage.h"
36 #include "RenderIterator.h"
37 #include "RenderSVGGradientStop.h"
38 #include "RenderSVGImage.h"
39 #include "RenderSVGPath.h"
40 #include "RenderSVGResourceClipper.h"
41 #include "RenderSVGResourceFilter.h"
42 #include "RenderSVGResourceLinearGradient.h"
43 #include "RenderSVGResourceMarker.h"
44 #include "RenderSVGResourceMasker.h"
45 #include "RenderSVGResourcePattern.h"
46 #include "RenderSVGResourceRadialGradient.h"
47 #include "RenderSVGResourceSolidColor.h"
48 #include "RenderSVGRoot.h"
49 #include "RenderSVGText.h"
50 #include "SVGCircleElement.h"
51 #include "SVGEllipseElement.h"
52 #include "SVGInlineTextBox.h"
53 #include "SVGLineElement.h"
54 #include "SVGNames.h"
55 #include "SVGPathElement.h"
56 #include "SVGPathUtilities.h"
57 #include "SVGPointList.h"
58 #include "SVGPolyElement.h"
59 #include "SVGRectElement.h"
60 #include "SVGRootInlineBox.h"
61 #include "SVGStopElement.h"
62
63 #include <math.h>
64
65 namespace WebCore {
66
67 /** class + iomanip to help streaming list separators, i.e. ", " in string "a, b, c, d"
68  * Can be used in cases where you don't know which item in the list is the first
69  * one to be printed, but still want to avoid strings like ", b, c".
70  */
71 class TextStreamSeparator {
72 public:
73     TextStreamSeparator(const String& s)
74         : m_separator(s)
75         , m_needToSeparate(false)
76     {
77     }
78
79 private:
80     friend TextStream& operator<<(TextStream&, TextStreamSeparator&);
81
82     String m_separator;
83     bool m_needToSeparate;
84 };
85
86 TextStream& operator<<(TextStream& ts, TextStreamSeparator& sep)
87 {
88     if (sep.m_needToSeparate)
89         ts << sep.m_separator;
90     else
91         sep.m_needToSeparate = true;
92     return ts;
93 }
94
95 template<typename ValueType>
96 static void writeNameValuePair(TextStream& ts, const char* name, ValueType value)
97 {
98     ts << " [" << name << "=" << value << "]";
99 }
100
101 template<typename ValueType>
102 static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value)
103 {
104     ts << " [" << name << "=\"" << value << "\"]";
105 }
106
107 static void writeIfNotEmpty(TextStream& ts, const char* name, const String& value)
108 {
109     if (!value.isEmpty())
110         writeNameValuePair(ts, name, value);
111 }
112
113 template<typename ValueType>
114 static void writeIfNotDefault(TextStream& ts, const char* name, ValueType value, ValueType defaultValue)
115 {
116     if (value != defaultValue)
117         writeNameValuePair(ts, name, value);
118 }
119
120 static TextStream& operator<<(TextStream& ts, const SVGUnitTypes::SVGUnitType& unitType)
121 {
122     ts << SVGPropertyTraits<SVGUnitTypes::SVGUnitType>::toString(unitType);
123     return ts;
124 }
125
126 static TextStream& operator<<(TextStream& ts, const SVGMarkerUnitsType& markerUnit)
127 {
128     ts << SVGPropertyTraits<SVGMarkerUnitsType>::toString(markerUnit);
129     return ts;
130 }
131
132 // FIXME: Maybe this should be in KCanvasRenderingStyle.cpp
133 static TextStream& operator<<(TextStream& ts, const DashArray& a)
134 {
135     ts << "{";
136     DashArray::const_iterator end = a.end();
137     for (DashArray::const_iterator it = a.begin(); it != end; ++it) {
138         if (it != a.begin())
139             ts << ", ";
140         ts << *it;
141     }
142     ts << "}";
143     return ts;
144 }
145
146 static TextStream& operator<<(TextStream& ts, const SVGSpreadMethodType& type)
147 {
148     ts << SVGPropertyTraits<SVGSpreadMethodType>::toString(type).convertToASCIIUppercase();
149     return ts;
150 }
151
152 static void writeSVGPaintingResource(TextStream& ts, RenderSVGResource* resource)
153 {
154     if (resource->resourceType() == SolidColorResourceType) {
155         ts << "[type=SOLID] [color=" << static_cast<RenderSVGResourceSolidColor*>(resource)->color() << "]";
156         return;
157     }
158
159     // All other resources derive from RenderSVGResourceContainer
160     RenderSVGResourceContainer* container = static_cast<RenderSVGResourceContainer*>(resource);
161     SVGElement& element = container->element();
162
163     if (resource->resourceType() == PatternResourceType)
164         ts << "[type=PATTERN]";
165     else if (resource->resourceType() == LinearGradientResourceType)
166         ts << "[type=LINEAR-GRADIENT]";
167     else if (resource->resourceType() == RadialGradientResourceType)
168         ts << "[type=RADIAL-GRADIENT]";
169
170     ts << " [id=\"" << element.getIdAttribute() << "\"]";
171 }
172
173 static void writeStyle(TextStream& ts, const RenderElement& renderer)
174 {
175     const RenderStyle& style = renderer.style();
176     const SVGRenderStyle& svgStyle = style.svgStyle();
177
178     if (!renderer.localTransform().isIdentity())
179         writeNameValuePair(ts, "transform", renderer.localTransform());
180     writeIfNotDefault(ts, "image rendering", style.imageRendering(), RenderStyle::initialImageRendering());
181     writeIfNotDefault(ts, "opacity", style.opacity(), RenderStyle::initialOpacity());
182     if (is<RenderSVGShape>(renderer)) {
183         const auto& shape = downcast<RenderSVGShape>(renderer);
184
185         Color fallbackColor;
186         if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(const_cast<RenderSVGShape&>(shape), shape.style(), fallbackColor)) {
187             TextStreamSeparator s(" ");
188             ts << " [stroke={" << s;
189             writeSVGPaintingResource(ts, strokePaintingResource);
190
191             SVGLengthContext lengthContext(&shape.graphicsElement());
192             double dashOffset = lengthContext.valueForLength(svgStyle.strokeDashOffset());
193             double strokeWidth = lengthContext.valueForLength(svgStyle.strokeWidth());
194             const Vector<SVGLength>& dashes = svgStyle.strokeDashArray();
195
196             DashArray dashArray;
197             const Vector<SVGLength>::const_iterator end = dashes.end();
198             for (Vector<SVGLength>::const_iterator it = dashes.begin(); it != end; ++it)
199                 dashArray.append((*it).value(lengthContext));
200
201             writeIfNotDefault(ts, "opacity", svgStyle.strokeOpacity(), 1.0f);
202             writeIfNotDefault(ts, "stroke width", strokeWidth, 1.0);
203             writeIfNotDefault(ts, "miter limit", svgStyle.strokeMiterLimit(), 4.0f);
204             writeIfNotDefault(ts, "line cap", svgStyle.capStyle(), ButtCap);
205             writeIfNotDefault(ts, "line join", svgStyle.joinStyle(), MiterJoin);
206             writeIfNotDefault(ts, "dash offset", dashOffset, 0.0);
207             if (!dashArray.isEmpty())
208                 writeNameValuePair(ts, "dash array", dashArray);
209
210             ts << "}]";
211         }
212
213         if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(const_cast<RenderSVGShape&>(shape), shape.style(), fallbackColor)) {
214             TextStreamSeparator s(" ");
215             ts << " [fill={" << s;
216             writeSVGPaintingResource(ts, fillPaintingResource);
217
218             writeIfNotDefault(ts, "opacity", svgStyle.fillOpacity(), 1.0f);
219             writeIfNotDefault(ts, "fill rule", svgStyle.fillRule(), RULE_NONZERO);
220             ts << "}]";
221         }
222         writeIfNotDefault(ts, "clip rule", svgStyle.clipRule(), RULE_NONZERO);
223     }
224
225     writeIfNotEmpty(ts, "start marker", svgStyle.markerStartResource());
226     writeIfNotEmpty(ts, "middle marker", svgStyle.markerMidResource());
227     writeIfNotEmpty(ts, "end marker", svgStyle.markerEndResource());
228 }
229
230 static TextStream& writePositionAndStyle(TextStream& ts, const RenderElement& renderer, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal)
231 {
232     if (behavior & RenderAsTextShowSVGGeometry) {
233         if (is<RenderBox>(renderer)) {
234             LayoutRect r = downcast<RenderBox>(renderer).frameRect();
235             ts << " " << enclosingIntRect(r);
236         }
237         
238         ts << " clipped";
239     }
240
241     ts << " " << enclosingIntRect(renderer.absoluteClippedOverflowRect());
242
243     writeStyle(ts, renderer);
244     return ts;
245 }
246
247 static TextStream& operator<<(TextStream& ts, const RenderSVGShape& shape)
248 {
249     writePositionAndStyle(ts, shape);
250
251     SVGGraphicsElement& svgElement = shape.graphicsElement();
252     SVGLengthContext lengthContext(&svgElement);
253
254     if (is<SVGRectElement>(svgElement)) {
255         const SVGRectElement& element = downcast<SVGRectElement>(svgElement);
256         writeNameValuePair(ts, "x", element.x().value(lengthContext));
257         writeNameValuePair(ts, "y", element.y().value(lengthContext));
258         writeNameValuePair(ts, "width", element.width().value(lengthContext));
259         writeNameValuePair(ts, "height", element.height().value(lengthContext));
260     } else if (is<SVGLineElement>(svgElement)) {
261         const SVGLineElement& element = downcast<SVGLineElement>(svgElement);
262         writeNameValuePair(ts, "x1", element.x1().value(lengthContext));
263         writeNameValuePair(ts, "y1", element.y1().value(lengthContext));
264         writeNameValuePair(ts, "x2", element.x2().value(lengthContext));
265         writeNameValuePair(ts, "y2", element.y2().value(lengthContext));
266     } else if (is<SVGEllipseElement>(svgElement)) {
267         const SVGEllipseElement& element = downcast<SVGEllipseElement>(svgElement);
268         writeNameValuePair(ts, "cx", element.cx().value(lengthContext));
269         writeNameValuePair(ts, "cy", element.cy().value(lengthContext));
270         writeNameValuePair(ts, "rx", element.rx().value(lengthContext));
271         writeNameValuePair(ts, "ry", element.ry().value(lengthContext));
272     } else if (is<SVGCircleElement>(svgElement)) {
273         const SVGCircleElement& element = downcast<SVGCircleElement>(svgElement);
274         writeNameValuePair(ts, "cx", element.cx().value(lengthContext));
275         writeNameValuePair(ts, "cy", element.cy().value(lengthContext));
276         writeNameValuePair(ts, "r", element.r().value(lengthContext));
277     } else if (is<SVGPolyElement>(svgElement)) {
278         const SVGPolyElement& element = downcast<SVGPolyElement>(svgElement);
279         writeNameAndQuotedValue(ts, "points", element.pointList().valueAsString());
280     } else if (is<SVGPathElement>(svgElement)) {
281         const SVGPathElement& element = downcast<SVGPathElement>(svgElement);
282         String pathString;
283         // FIXME: We should switch to UnalteredParsing here - this will affect the path dumping output of dozens of tests.
284         buildStringFromByteStream(element.pathByteStream(), pathString, NormalizedParsing);
285         writeNameAndQuotedValue(ts, "data", pathString);
286     } else
287         ASSERT_NOT_REACHED();
288     return ts;
289 }
290
291 static void writeRenderSVGTextBox(TextStream& ts, const RenderSVGText& text)
292 {
293     auto* box = downcast<SVGRootInlineBox>(text.firstRootBox());
294     if (!box)
295         return;
296
297     ts << " " << enclosingIntRect(FloatRect(text.location(), FloatSize(box->logicalWidth(), box->logicalHeight())));
298     
299     // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now.
300     ts << " contains 1 chunk(s)";
301
302     if (text.parent() && (text.parent()->style().visitedDependentColor(CSSPropertyColor) != text.style().visitedDependentColor(CSSPropertyColor)))
303         writeNameValuePair(ts, "color", text.style().visitedDependentColor(CSSPropertyColor).nameForRenderTreeAsText());
304 }
305
306 static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent)
307 {
308     Vector<SVGTextFragment>& fragments = textBox->textFragments();
309     if (fragments.isEmpty())
310         return;
311
312     const SVGRenderStyle& svgStyle = textBox->renderer().style().svgStyle();
313     String text = textBox->renderer().text();
314
315     unsigned fragmentsSize = fragments.size();
316     for (unsigned i = 0; i < fragmentsSize; ++i) {
317         SVGTextFragment& fragment = fragments.at(i);
318         writeIndent(ts, indent + 1);
319
320         unsigned startOffset = fragment.characterOffset;
321         unsigned endOffset = fragment.characterOffset + fragment.length;
322
323         // FIXME: Remove this hack, once the new text layout engine is completly landed. We want to preserve the old layout test results for now.
324         ts << "chunk 1 ";
325         ETextAnchor anchor = svgStyle.textAnchor();
326         bool isVerticalText = textBox->renderer().style().isVerticalWritingMode();
327         if (anchor == TA_MIDDLE) {
328             ts << "(middle anchor";
329             if (isVerticalText)
330                 ts << ", vertical";
331             ts << ") ";
332         } else if (anchor == TA_END) {
333             ts << "(end anchor";
334             if (isVerticalText)
335                 ts << ", vertical";
336             ts << ") ";
337         } else if (isVerticalText)
338             ts << "(vertical) ";
339         startOffset -= textBox->start();
340         endOffset -= textBox->start();
341         // </hack>
342
343         ts << "text run " << i + 1 << " at (" << fragment.x << "," << fragment.y << ")";
344         ts << " startOffset " << startOffset << " endOffset " << endOffset;
345         if (isVerticalText)
346             ts << " height " << fragment.height;
347         else
348             ts << " width " << fragment.width;
349
350         if (!textBox->isLeftToRightDirection() || textBox->dirOverride()) {
351             ts << (textBox->isLeftToRightDirection() ? " LTR" : " RTL");
352             if (textBox->dirOverride())
353                 ts << " override";
354         }
355
356         ts << ": " << quoteAndEscapeNonPrintables(text.substring(fragment.characterOffset, fragment.length)) << "\n";
357     }
358 }
359
360 static inline void writeSVGInlineTextBoxes(TextStream& ts, const RenderText& text, int indent)
361 {
362     for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) {
363         if (!is<SVGInlineTextBox>(*box))
364             continue;
365
366         writeSVGInlineTextBox(ts, downcast<SVGInlineTextBox>(box), indent);
367     }
368 }
369
370 static void writeStandardPrefix(TextStream& ts, const RenderObject& object, int indent, RenderAsTextBehavior behavior)
371 {
372     writeIndent(ts, indent);
373     ts << object.renderName();
374
375     if (behavior & RenderAsTextShowAddresses)
376         ts << " " << static_cast<const void*>(&object);
377
378     if (object.node())
379         ts << " {" << object.node()->nodeName() << "}";
380
381     writeDebugInfo(ts, object, behavior);
382 }
383
384 static void writeChildren(TextStream& ts, const RenderElement& parent, int indent, RenderAsTextBehavior behavior)
385 {
386     for (const auto& child : childrenOfType<RenderObject>(parent))
387         write(ts, child, indent + 1, behavior);
388 }
389
390 static inline void writeCommonGradientProperties(TextStream& ts, SVGSpreadMethodType spreadMethod, const AffineTransform& gradientTransform, SVGUnitTypes::SVGUnitType gradientUnits)
391 {
392     writeNameValuePair(ts, "gradientUnits", gradientUnits);
393
394     if (spreadMethod != SVGSpreadMethodPad)
395         ts << " [spreadMethod=" << spreadMethod << "]";
396
397     if (!gradientTransform.isIdentity())
398         ts << " [gradientTransform=" << gradientTransform << "]";
399 }
400
401 void writeSVGResourceContainer(TextStream& ts, const RenderSVGResourceContainer& resource, int indent, RenderAsTextBehavior behavior)
402 {
403     writeStandardPrefix(ts, resource, indent, behavior);
404
405     const AtomicString& id = resource.element().getIdAttribute();
406     writeNameAndQuotedValue(ts, "id", id);    
407
408     if (resource.resourceType() == MaskerResourceType) {
409         const auto& masker = static_cast<const RenderSVGResourceMasker&>(resource);
410         writeNameValuePair(ts, "maskUnits", masker.maskUnits());
411         writeNameValuePair(ts, "maskContentUnits", masker.maskContentUnits());
412         ts << "\n";
413     } else if (resource.resourceType() == FilterResourceType) {
414         const auto& filter = static_cast<const RenderSVGResourceFilter&>(resource);
415         writeNameValuePair(ts, "filterUnits", filter.filterUnits());
416         writeNameValuePair(ts, "primitiveUnits", filter.primitiveUnits());
417         ts << "\n";
418         // Creating a placeholder filter which is passed to the builder.
419         FloatRect dummyRect;
420         RefPtr<SVGFilter> dummyFilter = SVGFilter::create(AffineTransform(), dummyRect, dummyRect, dummyRect, true);
421         if (auto builder = filter.buildPrimitives(*dummyFilter)) {
422             if (FilterEffect* lastEffect = builder->lastEffect())
423                 lastEffect->externalRepresentation(ts, indent + 1);
424         }
425     } else if (resource.resourceType() == ClipperResourceType) {
426         const auto& clipper = static_cast<const RenderSVGResourceClipper&>(resource);
427         writeNameValuePair(ts, "clipPathUnits", clipper.clipPathUnits());
428         ts << "\n";
429     } else if (resource.resourceType() == MarkerResourceType) {
430         const auto& marker = static_cast<const RenderSVGResourceMarker&>(resource);
431         writeNameValuePair(ts, "markerUnits", marker.markerUnits());
432         ts << " [ref at " << marker.referencePoint() << "]";
433         ts << " [angle=";
434         if (marker.angle() == -1)
435             ts << "auto" << "]\n";
436         else
437             ts << marker.angle() << "]\n";
438     } else if (resource.resourceType() == PatternResourceType) {
439         const auto& pattern = static_cast<const RenderSVGResourcePattern&>(resource);
440
441         // Dump final results that are used for rendering. No use in asking SVGPatternElement for its patternUnits(), as it may
442         // link to other patterns using xlink:href, we need to build the full inheritance chain, aka. collectPatternProperties()
443         PatternAttributes attributes;
444         pattern.collectPatternAttributes(attributes);
445
446         writeNameValuePair(ts, "patternUnits", attributes.patternUnits());
447         writeNameValuePair(ts, "patternContentUnits", attributes.patternContentUnits());
448
449         AffineTransform transform = attributes.patternTransform();
450         if (!transform.isIdentity())
451             ts << " [patternTransform=" << transform << "]";
452         ts << "\n";
453     } else if (resource.resourceType() == LinearGradientResourceType) {
454         const auto& gradient = static_cast<const RenderSVGResourceLinearGradient&>(resource);
455
456         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
457         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
458         LinearGradientAttributes attributes;
459         gradient.linearGradientElement().collectGradientAttributes(attributes);
460         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
461
462         ts << " [start=" << gradient.startPoint(attributes) << "] [end=" << gradient.endPoint(attributes) << "]\n";
463     }  else if (resource.resourceType() == RadialGradientResourceType) {
464         const auto& gradient = static_cast<const RenderSVGResourceRadialGradient&>(resource);
465
466         // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may
467         // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties()
468         RadialGradientAttributes attributes;
469         gradient.radialGradientElement().collectGradientAttributes(attributes);
470         writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.gradientUnits());
471
472         FloatPoint focalPoint = gradient.focalPoint(attributes);
473         FloatPoint centerPoint = gradient.centerPoint(attributes);
474         float radius = gradient.radius(attributes);
475         float focalRadius = gradient.focalRadius(attributes);
476
477         ts << " [center=" << centerPoint << "] [focal=" << focalPoint << "] [radius=" << radius << "] [focalRadius=" << focalRadius << "]\n";
478     } else
479         ts << "\n";
480     writeChildren(ts, resource, indent, behavior);
481 }
482
483 void writeSVGContainer(TextStream& ts, const RenderSVGContainer& container, int indent, RenderAsTextBehavior behavior)
484 {
485     // Currently RenderSVGResourceFilterPrimitive has no meaningful output.
486     if (container.isSVGResourceFilterPrimitive())
487         return;
488     writeStandardPrefix(ts, container, indent, behavior);
489     writePositionAndStyle(ts, container, behavior);
490     ts << "\n";
491     writeResources(ts, container, indent, behavior);
492     writeChildren(ts, container, indent, behavior);
493 }
494
495 void write(TextStream& ts, const RenderSVGRoot& root, int indent, RenderAsTextBehavior behavior)
496 {
497     writeStandardPrefix(ts, root, indent, behavior);
498     writePositionAndStyle(ts, root, behavior);
499     ts << "\n";
500     writeChildren(ts, root, indent, behavior);
501 }
502
503 void writeSVGText(TextStream& ts, const RenderSVGText& text, int indent, RenderAsTextBehavior behavior)
504 {
505     writeStandardPrefix(ts, text, indent, behavior);
506     writeRenderSVGTextBox(ts, text);
507     ts << "\n";
508     writeResources(ts, text, indent, behavior);
509     writeChildren(ts, text, indent, behavior);
510 }
511
512 void writeSVGInlineText(TextStream& ts, const RenderSVGInlineText& text, int indent, RenderAsTextBehavior behavior)
513 {
514     writeStandardPrefix(ts, text, indent, behavior);
515     ts << " " << enclosingIntRect(FloatRect(text.firstRunLocation(), text.floatLinesBoundingBox().size())) << "\n";
516     writeResources(ts, text, indent, behavior);
517     writeSVGInlineTextBoxes(ts, text, indent);
518 }
519
520 void writeSVGImage(TextStream& ts, const RenderSVGImage& image, int indent, RenderAsTextBehavior behavior)
521 {
522     writeStandardPrefix(ts, image, indent, behavior);
523     writePositionAndStyle(ts, image, behavior);
524     ts << "\n";
525     writeResources(ts, image, indent, behavior);
526 }
527
528 void write(TextStream& ts, const RenderSVGShape& shape, int indent, RenderAsTextBehavior behavior)
529 {
530     writeStandardPrefix(ts, shape, indent, behavior);
531     ts << shape << "\n";
532     writeResources(ts, shape, indent, behavior);
533 }
534
535 void writeSVGGradientStop(TextStream& ts, const RenderSVGGradientStop& stop, int indent, RenderAsTextBehavior behavior)
536 {
537     writeStandardPrefix(ts, stop, indent, behavior);
538
539     ts << " [offset=" << stop.element().offset() << "] [color=" << stop.element().stopColorIncludingOpacity() << "]\n";
540 }
541
542 void writeResources(TextStream& ts, const RenderObject& renderer, int indent, RenderAsTextBehavior behavior)
543 {
544     const RenderStyle& style = renderer.style();
545     const SVGRenderStyle& svgStyle = style.svgStyle();
546
547     // FIXME: We want to use SVGResourcesCache to determine which resources are present, instead of quering the resource <-> id cache.
548     // For now leave the DRT output as is, but later on we should change this so cycles are properly ignored in the DRT output.
549     if (!svgStyle.maskerResource().isEmpty()) {
550         if (RenderSVGResourceMasker* masker = getRenderSVGResourceById<RenderSVGResourceMasker>(renderer.document(), svgStyle.maskerResource())) {
551             writeIndent(ts, indent);
552             ts << " ";
553             writeNameAndQuotedValue(ts, "masker", svgStyle.maskerResource());
554             ts << " ";
555             writeStandardPrefix(ts, *masker, 0, behavior);
556             ts << " " << masker->resourceBoundingBox(renderer) << "\n";
557         }
558     }
559     if (!svgStyle.clipperResource().isEmpty()) {
560         if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(renderer.document(), svgStyle.clipperResource())) {
561             writeIndent(ts, indent);
562             ts << " ";
563             writeNameAndQuotedValue(ts, "clipPath", svgStyle.clipperResource());
564             ts << " ";
565             writeStandardPrefix(ts, *clipper, 0, behavior);
566             ts << " " << clipper->resourceBoundingBox(renderer) << "\n";
567         }
568     }
569     if (style.hasFilter()) {
570         const FilterOperations& filterOperations = style.filter();
571         if (filterOperations.size() == 1) {
572             const FilterOperation& filterOperation = *filterOperations.at(0);
573             if (filterOperation.type() == FilterOperation::REFERENCE) {
574                 const auto& referenceFilterOperation = downcast<ReferenceFilterOperation>(filterOperation);
575                 AtomicString id = SVGURIReference::fragmentIdentifierFromIRIString(referenceFilterOperation.url(), renderer.document());
576                 if (RenderSVGResourceFilter* filter = getRenderSVGResourceById<RenderSVGResourceFilter>(renderer.document(), id)) {
577                     writeIndent(ts, indent);
578                     ts << " ";
579                     writeNameAndQuotedValue(ts, "filter", id);
580                     ts << " ";
581                     writeStandardPrefix(ts, *filter, 0, behavior);
582                     ts << " " << filter->resourceBoundingBox(renderer) << "\n";
583                 }
584             }
585         }
586     }
587 }
588
589 } // namespace WebCore