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