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
62namespace 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 */
68class TextStreamSeparator {
69public:
70 TextStreamSeparator(const String& s)
71 : m_separator(s)
72 , m_needToSeparate(false)
73 {
74 }
75
76private:
77 friend TextStream& operator<<(TextStream&, TextStreamSeparator&);
78
79 String m_separator;
80 bool m_needToSeparate;
81};
82
83TextStream& 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
92static 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
105template<typename ValueType>
106static void writeNameValuePair(TextStream& ts, const char* name, ValueType value)
107{
108 ts << " [" << name << "=" << value << "]";
109}
110
111template<typename ValueType>
112static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value)
113{
114 ts << " [" << name << "=\"" << value << "\"]";
115}
116
117static void writeIfNotEmpty(TextStream& ts, const char* name, const String& value)
118{
119 if (!value.isEmpty())
120 writeNameValuePair(ts, name, value);
121}
122
123template<typename ValueType>
124static void writeIfNotDefault(TextStream& ts, const char* name, ValueType value, ValueType defaultValue)
125{
126 if (value != defaultValue)
127 writeNameValuePair(ts, name, value);
128}
129
130static TextStream& operator<<(TextStream& ts, const SVGUnitTypes::SVGUnitType& unitType)
131{
132 ts << SVGPropertyTraits<SVGUnitTypes::SVGUnitType>::toString(unitType);
133 return ts;
134}
135
136static TextStream& operator<<(TextStream& ts, const SVGMarkerUnitsType& markerUnit)
137{
138 ts << SVGPropertyTraits<SVGMarkerUnitsType>::toString(markerUnit);
139 return ts;
140}
141
142static TextStream& operator<<(TextStream& ts, const SVGSpreadMethodType& type)
143{
144 ts << SVGPropertyTraits<SVGSpreadMethodType>::toString(type).convertToASCIIUppercase();
145 return ts;
146}
147
148static 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
169static 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
225static 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
242static 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
286static 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
301static 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
357static 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
367enum class WriteIndentOrNot {
368 No,
369 Yes
370};
371
372static 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
388static 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
396static 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
407void writeSVGResourceContainer(TextStream& ts, const RenderSVGResourceContainer& resource, OptionSet<RenderAsTextFlag> behavior)
408{
409 writeStandardPrefix(ts, resource, behavior);
410
411 const AtomicString& 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
491void 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
503void 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
511void 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
520void 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
528void 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
536void 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
543void 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
550void 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 AtomicString 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
595