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 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 | |
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 | 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 | |