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 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:
TextStreamSeparator(const String & s)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 
operator <<(TextStream & ts,TextStreamSeparator & sep)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>
writeNameValuePair(TextStream & ts,const char * name,ValueType value)110 static void writeNameValuePair(TextStream& ts, const char* name, ValueType value)
111 {
112     ts << " [" << name << "=" << value << "]";
113 }
114 
115 template<typename ValueType>
writeNameAndQuotedValue(TextStream & ts,const char * name,ValueType value)116 static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value)
117 {
118     ts << " [" << name << "=\"" << value << "\"]";
119 }
120 
writeIfNotEmpty(TextStream & ts,const char * name,const String & value)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>
writeIfNotDefault(TextStream & ts,const char * name,ValueType value,ValueType defaultValue)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 
operator <<(TextStream & ts,const FloatRect & r)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 
operator <<(TextStream & ts,const AffineTransform & transform)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 
operator <<(TextStream & ts,const WindRule rule)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 
operator <<(TextStream & ts,const SVGUnitTypes::SVGUnitType & unitType)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 
operator <<(TextStream & ts,const SVGMarkerElement::SVGMarkerUnitsType & markerUnit)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 
operator <<(TextStream & ts,const Color & c)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
operator <<(TextStream & ts,const DashArray & a)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
operator <<(TextStream & ts,LineCap style)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
operator <<(TextStream & ts,LineJoin style)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
operator <<(TextStream & ts,GradientSpreadMethod mode)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 
writeSVGPaintingResource(TextStream & ts,RenderSVGResource * resource)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 
writeStyle(TextStream & ts,const RenderObject & object)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 
writePositionAndStyle(TextStream & ts,const RenderObject & object)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 
operator <<(TextStream & ts,const RenderSVGPath & path)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 
operator <<(TextStream & ts,const RenderSVGRoot & root)427 static TextStream& operator<<(TextStream& ts, const RenderSVGRoot& root)
428 {
429     return writePositionAndStyle(ts, root);
430 }
431 
writeRenderSVGTextBox(TextStream & ts,const RenderBlock & text)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 
writeSVGInlineTextBox(TextStream & ts,SVGInlineTextBox * textBox,int indent)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 
writeSVGInlineTextBoxes(TextStream & ts,const RenderText & text,int indent)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 
writeStandardPrefix(TextStream & ts,const RenderObject & object,int indent)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 
writeChildren(TextStream & ts,const RenderObject & object,int indent)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 
boundingBoxModeString(bool boundingBoxMode)532 static inline String boundingBoxModeString(bool boundingBoxMode)
533 {
534     return boundingBoxMode ? "objectBoundingBox" : "userSpaceOnUse";
535 }
536 
writeCommonGradientProperties(TextStream & ts,GradientSpreadMethod spreadMethod,const AffineTransform & gradientTransform,bool boundingBoxMode)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 
writeSVGResourceContainer(TextStream & ts,const RenderObject & object,int indent)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 
writeSVGContainer(TextStream & ts,const RenderObject & container,int indent)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 
write(TextStream & ts,const RenderSVGRoot & root,int indent)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 
writeSVGText(TextStream & ts,const RenderBlock & text,int indent)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 
writeSVGInlineText(TextStream & ts,const RenderText & text,int indent)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 
writeSVGImage(TextStream & ts,const RenderSVGImage & image,int indent)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 
write(TextStream & ts,const RenderSVGPath & path,int indent)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 
writeSVGGradientStop(TextStream & ts,const RenderSVGGradientStop & stop,int indent)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 
writeResources(TextStream & ts,const RenderObject & object,int indent)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