|
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 "RenderPath.h" |
|
44 #include "RenderSVGContainer.h" |
|
45 #include "RenderSVGGradientStop.h" |
|
46 #include "RenderSVGInlineText.h" |
|
47 #include "RenderSVGResourceClipper.h" |
|
48 #include "RenderSVGResourceFilter.h" |
|
49 #include "RenderSVGResourceGradient.h" |
|
50 #include "RenderSVGResourceLinearGradient.h" |
|
51 #include "RenderSVGResourceMarker.h" |
|
52 #include "RenderSVGResourceMasker.h" |
|
53 #include "RenderSVGResourcePattern.h" |
|
54 #include "RenderSVGResourceRadialGradient.h" |
|
55 #include "RenderSVGResourceSolidColor.h" |
|
56 #include "RenderSVGRoot.h" |
|
57 #include "RenderSVGText.h" |
|
58 #include "RenderTreeAsText.h" |
|
59 #include "SVGCharacterLayoutInfo.h" |
|
60 #include "SVGInlineTextBox.h" |
|
61 #include "SVGLinearGradientElement.h" |
|
62 #include "SVGPatternElement.h" |
|
63 #include "SVGRadialGradientElement.h" |
|
64 #include "SVGRootInlineBox.h" |
|
65 #include "SVGStopElement.h" |
|
66 #include "SVGStyledElement.h" |
|
67 #include "SVGTextLayoutUtilities.h" |
|
68 |
|
69 #include <math.h> |
|
70 |
|
71 namespace WebCore { |
|
72 |
|
73 /** class + iomanip to help streaming list separators, i.e. ", " in string "a, b, c, d" |
|
74 * Can be used in cases where you don't know which item in the list is the first |
|
75 * one to be printed, but still want to avoid strings like ", b, c". |
|
76 */ |
|
77 class TextStreamSeparator { |
|
78 public: |
|
79 TextStreamSeparator(const String& s) |
|
80 : m_separator(s) |
|
81 , m_needToSeparate(false) |
|
82 { |
|
83 } |
|
84 |
|
85 private: |
|
86 friend TextStream& operator<<(TextStream&, TextStreamSeparator&); |
|
87 |
|
88 String m_separator; |
|
89 bool m_needToSeparate; |
|
90 }; |
|
91 |
|
92 TextStream& operator<<(TextStream& ts, TextStreamSeparator& sep) |
|
93 { |
|
94 if (sep.m_needToSeparate) |
|
95 ts << sep.m_separator; |
|
96 else |
|
97 sep.m_needToSeparate = true; |
|
98 return ts; |
|
99 } |
|
100 |
|
101 template<typename ValueType> |
|
102 static void writeNameValuePair(TextStream& ts, const char* name, ValueType value) |
|
103 { |
|
104 ts << " [" << name << "=" << value << "]"; |
|
105 } |
|
106 |
|
107 template<typename ValueType> |
|
108 static void writeNameAndQuotedValue(TextStream& ts, const char* name, ValueType value) |
|
109 { |
|
110 ts << " [" << name << "=\"" << value << "\"]"; |
|
111 } |
|
112 |
|
113 static void writeIfNotEmpty(TextStream& ts, const char* name, const String& value) |
|
114 { |
|
115 if (!value.isEmpty()) |
|
116 writeNameValuePair(ts, name, value); |
|
117 } |
|
118 |
|
119 template<typename ValueType> |
|
120 static void writeIfNotDefault(TextStream& ts, const char* name, ValueType value, ValueType defaultValue) |
|
121 { |
|
122 if (value != defaultValue) |
|
123 writeNameValuePair(ts, name, value); |
|
124 } |
|
125 |
|
126 TextStream& operator<<(TextStream& ts, const IntPoint& p) |
|
127 { |
|
128 return ts << "(" << p.x() << "," << p.y() << ")"; |
|
129 } |
|
130 |
|
131 TextStream& operator<<(TextStream& ts, const IntRect& r) |
|
132 { |
|
133 return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height(); |
|
134 } |
|
135 |
|
136 static bool hasFractions(double val) |
|
137 { |
|
138 double epsilon = 0.0001; |
|
139 int ival = static_cast<int>(val); |
|
140 double dval = static_cast<double>(ival); |
|
141 return fabs(val - dval) > epsilon; |
|
142 } |
|
143 |
|
144 TextStream& operator<<(TextStream& ts, const FloatRect &r) |
|
145 { |
|
146 ts << "at ("; |
|
147 if (hasFractions(r.x())) |
|
148 ts << r.x(); |
|
149 else |
|
150 ts << int(r.x()); |
|
151 ts << ","; |
|
152 if (hasFractions(r.y())) |
|
153 ts << r.y(); |
|
154 else |
|
155 ts << int(r.y()); |
|
156 ts << ") size "; |
|
157 if (hasFractions(r.width())) |
|
158 ts << r.width(); |
|
159 else |
|
160 ts << int(r.width()); |
|
161 ts << "x"; |
|
162 if (hasFractions(r.height())) |
|
163 ts << r.height(); |
|
164 else |
|
165 ts << int(r.height()); |
|
166 return ts; |
|
167 } |
|
168 |
|
169 TextStream& operator<<(TextStream& ts, const FloatPoint& p) |
|
170 { |
|
171 ts << "("; |
|
172 if (hasFractions(p.x())) |
|
173 ts << p.x(); |
|
174 else |
|
175 ts << int(p.x()); |
|
176 ts << ","; |
|
177 if (hasFractions(p.y())) |
|
178 ts << p.y(); |
|
179 else |
|
180 ts << int(p.y()); |
|
181 return ts << ")"; |
|
182 } |
|
183 |
|
184 TextStream& operator<<(TextStream& ts, const FloatSize& s) |
|
185 { |
|
186 ts << "width="; |
|
187 if (hasFractions(s.width())) |
|
188 ts << s.width(); |
|
189 else |
|
190 ts << int(s.width()); |
|
191 ts << " height="; |
|
192 if (hasFractions(s.height())) |
|
193 ts << s.height(); |
|
194 else |
|
195 ts << int(s.height()); |
|
196 return ts; |
|
197 } |
|
198 |
|
199 TextStream& operator<<(TextStream& ts, const AffineTransform& transform) |
|
200 { |
|
201 if (transform.isIdentity()) |
|
202 ts << "identity"; |
|
203 else |
|
204 ts << "{m=((" |
|
205 << transform.a() << "," << transform.b() |
|
206 << ")(" |
|
207 << transform.c() << "," << transform.d() |
|
208 << ")) t=(" |
|
209 << transform.e() << "," << transform.f() |
|
210 << ")}"; |
|
211 |
|
212 return ts; |
|
213 } |
|
214 |
|
215 static TextStream& operator<<(TextStream& ts, const WindRule rule) |
|
216 { |
|
217 switch (rule) { |
|
218 case RULE_NONZERO: |
|
219 ts << "NON-ZERO"; |
|
220 break; |
|
221 case RULE_EVENODD: |
|
222 ts << "EVEN-ODD"; |
|
223 break; |
|
224 } |
|
225 |
|
226 return ts; |
|
227 } |
|
228 |
|
229 static TextStream& operator<<(TextStream& ts, const SVGUnitTypes::SVGUnitType& unitType) |
|
230 { |
|
231 switch (unitType) { |
|
232 case SVGUnitTypes::SVG_UNIT_TYPE_UNKNOWN: |
|
233 ts << "unknown"; |
|
234 break; |
|
235 case SVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE: |
|
236 ts << "userSpaceOnUse"; |
|
237 break; |
|
238 case SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX: |
|
239 ts << "objectBoundingBox"; |
|
240 break; |
|
241 } |
|
242 |
|
243 return ts; |
|
244 } |
|
245 |
|
246 static TextStream& operator<<(TextStream& ts, const SVGMarkerElement::SVGMarkerUnitsType& markerUnit) |
|
247 { |
|
248 switch (markerUnit) { |
|
249 case SVGMarkerElement::SVG_MARKERUNITS_UNKNOWN: |
|
250 ts << "unknown"; |
|
251 break; |
|
252 case SVGMarkerElement::SVG_MARKERUNITS_USERSPACEONUSE: |
|
253 ts << "userSpaceOnUse"; |
|
254 break; |
|
255 case SVGMarkerElement::SVG_MARKERUNITS_STROKEWIDTH: |
|
256 ts << "strokeWidth"; |
|
257 break; |
|
258 } |
|
259 |
|
260 return ts; |
|
261 } |
|
262 |
|
263 TextStream& operator<<(TextStream& ts, const Color& c) |
|
264 { |
|
265 return ts << c.name(); |
|
266 } |
|
267 |
|
268 // FIXME: Maybe this should be in KCanvasRenderingStyle.cpp |
|
269 static TextStream& operator<<(TextStream& ts, const DashArray& a) |
|
270 { |
|
271 ts << "{"; |
|
272 DashArray::const_iterator end = a.end(); |
|
273 for (DashArray::const_iterator it = a.begin(); it != end; ++it) { |
|
274 if (it != a.begin()) |
|
275 ts << ", "; |
|
276 ts << *it; |
|
277 } |
|
278 ts << "}"; |
|
279 return ts; |
|
280 } |
|
281 |
|
282 // FIXME: Maybe this should be in GraphicsTypes.cpp |
|
283 static TextStream& operator<<(TextStream& ts, LineCap style) |
|
284 { |
|
285 switch (style) { |
|
286 case ButtCap: |
|
287 ts << "BUTT"; |
|
288 break; |
|
289 case RoundCap: |
|
290 ts << "ROUND"; |
|
291 break; |
|
292 case SquareCap: |
|
293 ts << "SQUARE"; |
|
294 break; |
|
295 } |
|
296 return ts; |
|
297 } |
|
298 |
|
299 // FIXME: Maybe this should be in GraphicsTypes.cpp |
|
300 static TextStream& operator<<(TextStream& ts, LineJoin style) |
|
301 { |
|
302 switch (style) { |
|
303 case MiterJoin: |
|
304 ts << "MITER"; |
|
305 break; |
|
306 case RoundJoin: |
|
307 ts << "ROUND"; |
|
308 break; |
|
309 case BevelJoin: |
|
310 ts << "BEVEL"; |
|
311 break; |
|
312 } |
|
313 return ts; |
|
314 } |
|
315 |
|
316 // FIXME: Maybe this should be in Gradient.cpp |
|
317 static TextStream& operator<<(TextStream& ts, GradientSpreadMethod mode) |
|
318 { |
|
319 switch (mode) { |
|
320 case SpreadMethodPad: |
|
321 ts << "PAD"; |
|
322 break; |
|
323 case SpreadMethodRepeat: |
|
324 ts << "REPEAT"; |
|
325 break; |
|
326 case SpreadMethodReflect: |
|
327 ts << "REFLECT"; |
|
328 break; |
|
329 } |
|
330 |
|
331 return ts; |
|
332 } |
|
333 |
|
334 static void writeSVGPaintingResource(TextStream& ts, RenderSVGResource* resource) |
|
335 { |
|
336 if (resource->resourceType() == SolidColorResourceType) { |
|
337 ts << "[type=SOLID] [color=" << static_cast<RenderSVGResourceSolidColor*>(resource)->color() << "]"; |
|
338 return; |
|
339 } |
|
340 |
|
341 // All other resources derive from RenderSVGResourceContainer |
|
342 RenderSVGResourceContainer* container = static_cast<RenderSVGResourceContainer*>(resource); |
|
343 Node* node = container->node(); |
|
344 ASSERT(node); |
|
345 ASSERT(node->isSVGElement()); |
|
346 |
|
347 if (resource->resourceType() == PatternResourceType) |
|
348 ts << "[type=PATTERN]"; |
|
349 else if (resource->resourceType() == LinearGradientResourceType) |
|
350 ts << "[type=LINEAR-GRADIENT]"; |
|
351 else if (resource->resourceType() == RadialGradientResourceType) |
|
352 ts << "[type=RADIAL-GRADIENT]"; |
|
353 |
|
354 ts << " [id=\"" << static_cast<SVGElement*>(node)->getIdAttribute() << "\"]"; |
|
355 } |
|
356 |
|
357 static void writeStyle(TextStream& ts, const RenderObject& object) |
|
358 { |
|
359 const RenderStyle* style = object.style(); |
|
360 const SVGRenderStyle* svgStyle = style->svgStyle(); |
|
361 |
|
362 if (!object.localTransform().isIdentity()) |
|
363 writeNameValuePair(ts, "transform", object.localTransform()); |
|
364 writeIfNotDefault(ts, "image rendering", svgStyle->imageRendering(), SVGRenderStyle::initialImageRendering()); |
|
365 writeIfNotDefault(ts, "opacity", style->opacity(), RenderStyle::initialOpacity()); |
|
366 if (object.isRenderPath()) { |
|
367 const RenderPath& path = static_cast<const RenderPath&>(object); |
|
368 |
|
369 if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(&path, path.style())) { |
|
370 TextStreamSeparator s(" "); |
|
371 ts << " [stroke={" << s; |
|
372 writeSVGPaintingResource(ts, strokePaintingResource); |
|
373 |
|
374 double dashOffset = SVGRenderStyle::cssPrimitiveToLength(&path, svgStyle->strokeDashOffset(), 0.0f); |
|
375 const DashArray& dashArray = SVGRenderSupport::dashArrayFromRenderingStyle(style, object.document()->documentElement()->renderStyle()); |
|
376 double strokeWidth = SVGRenderStyle::cssPrimitiveToLength(&path, svgStyle->strokeWidth(), 1.0f); |
|
377 |
|
378 writeIfNotDefault(ts, "opacity", svgStyle->strokeOpacity(), 1.0f); |
|
379 writeIfNotDefault(ts, "stroke width", strokeWidth, 1.0); |
|
380 writeIfNotDefault(ts, "miter limit", svgStyle->strokeMiterLimit(), 4.0f); |
|
381 writeIfNotDefault(ts, "line cap", svgStyle->capStyle(), ButtCap); |
|
382 writeIfNotDefault(ts, "line join", svgStyle->joinStyle(), MiterJoin); |
|
383 writeIfNotDefault(ts, "dash offset", dashOffset, 0.0); |
|
384 if (!dashArray.isEmpty()) |
|
385 writeNameValuePair(ts, "dash array", dashArray); |
|
386 |
|
387 ts << "}]"; |
|
388 } |
|
389 |
|
390 if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(&path, path.style())) { |
|
391 TextStreamSeparator s(" "); |
|
392 ts << " [fill={" << s; |
|
393 writeSVGPaintingResource(ts, fillPaintingResource); |
|
394 |
|
395 writeIfNotDefault(ts, "opacity", svgStyle->fillOpacity(), 1.0f); |
|
396 writeIfNotDefault(ts, "fill rule", svgStyle->fillRule(), RULE_NONZERO); |
|
397 ts << "}]"; |
|
398 } |
|
399 writeIfNotDefault(ts, "clip rule", svgStyle->clipRule(), RULE_NONZERO); |
|
400 } |
|
401 |
|
402 writeIfNotEmpty(ts, "start marker", svgStyle->markerStartResource()); |
|
403 writeIfNotEmpty(ts, "middle marker", svgStyle->markerMidResource()); |
|
404 writeIfNotEmpty(ts, "end marker", svgStyle->markerEndResource()); |
|
405 } |
|
406 |
|
407 static TextStream& writePositionAndStyle(TextStream& ts, const RenderObject& object) |
|
408 { |
|
409 ts << " " << const_cast<RenderObject&>(object).absoluteClippedOverflowRect(); |
|
410 writeStyle(ts, object); |
|
411 return ts; |
|
412 } |
|
413 |
|
414 static TextStream& operator<<(TextStream& ts, const RenderPath& path) |
|
415 { |
|
416 writePositionAndStyle(ts, path); |
|
417 writeNameAndQuotedValue(ts, "data", path.path().debugString()); |
|
418 return ts; |
|
419 } |
|
420 |
|
421 static TextStream& operator<<(TextStream& ts, const RenderSVGRoot& root) |
|
422 { |
|
423 return writePositionAndStyle(ts, root); |
|
424 } |
|
425 |
|
426 static void writeRenderSVGTextBox(TextStream& ts, const RenderBlock& text) |
|
427 { |
|
428 SVGRootInlineBox* box = static_cast<SVGRootInlineBox*>(text.firstRootBox()); |
|
429 |
|
430 if (!box) |
|
431 return; |
|
432 |
|
433 Vector<SVGTextChunk>& chunks = const_cast<Vector<SVGTextChunk>& >(box->svgTextChunks()); |
|
434 ts << " at (" << text.x() << "," << text.y() << ") size " << box->width() << "x" << box->height() << " contains " << chunks.size() << " chunk(s)"; |
|
435 |
|
436 if (text.parent() && (text.parent()->style()->visitedDependentColor(CSSPropertyColor) != text.style()->visitedDependentColor(CSSPropertyColor))) |
|
437 writeNameValuePair(ts, "color", text.style()->visitedDependentColor(CSSPropertyColor).name()); |
|
438 } |
|
439 |
|
440 static inline bool containsInlineTextBox(SVGTextChunk& chunk, SVGInlineTextBox* box) |
|
441 { |
|
442 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); |
|
443 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end(); |
|
444 |
|
445 bool found = false; |
|
446 for (; boxIt != boxEnd; ++boxIt) { |
|
447 SVGInlineBoxCharacterRange& range = *boxIt; |
|
448 |
|
449 if (box == static_cast<SVGInlineTextBox*>(range.box)) { |
|
450 found = true; |
|
451 break; |
|
452 } |
|
453 } |
|
454 |
|
455 return found; |
|
456 } |
|
457 |
|
458 static inline void writeSVGInlineTextBox(TextStream& ts, SVGInlineTextBox* textBox, int indent) |
|
459 { |
|
460 SVGRootInlineBox* rootBox = textBox->svgRootInlineBox(); |
|
461 if (!rootBox) |
|
462 return; |
|
463 |
|
464 Vector<SVGTextChunk>& chunks = const_cast<Vector<SVGTextChunk>& >(rootBox->svgTextChunks()); |
|
465 |
|
466 Vector<SVGTextChunk>::iterator it = chunks.begin(); |
|
467 Vector<SVGTextChunk>::iterator end = chunks.end(); |
|
468 |
|
469 // Write text chunks |
|
470 unsigned int i = 1; |
|
471 for (; it != end; ++it) { |
|
472 SVGTextChunk& cur = *it; |
|
473 |
|
474 // Write inline box character ranges |
|
475 Vector<SVGInlineBoxCharacterRange>::iterator boxIt = cur.boxes.begin(); |
|
476 Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = cur.boxes.end(); |
|
477 |
|
478 if (!containsInlineTextBox(cur, textBox)) { |
|
479 i++; |
|
480 continue; |
|
481 } |
|
482 |
|
483 writeIndent(ts, indent + 1); |
|
484 |
|
485 unsigned int j = 1; |
|
486 ts << "chunk " << i << " "; |
|
487 |
|
488 if (cur.anchor == TA_MIDDLE) { |
|
489 ts << "(middle anchor"; |
|
490 if (cur.isVerticalText) |
|
491 ts << ", vertical"; |
|
492 ts << ") "; |
|
493 } else if (cur.anchor == TA_END) { |
|
494 ts << "(end anchor"; |
|
495 if (cur.isVerticalText) |
|
496 ts << ", vertical"; |
|
497 ts << ") "; |
|
498 } else if (cur.isVerticalText) |
|
499 ts << "(vertical) "; |
|
500 |
|
501 unsigned int totalOffset = 0; |
|
502 |
|
503 for (; boxIt != boxEnd; ++boxIt) { |
|
504 SVGInlineBoxCharacterRange& range = *boxIt; |
|
505 |
|
506 unsigned int offset = range.endOffset - range.startOffset; |
|
507 ASSERT(cur.start + totalOffset <= cur.end); |
|
508 |
|
509 totalOffset += offset; |
|
510 |
|
511 if (textBox != static_cast<SVGInlineTextBox*>(range.box)) { |
|
512 j++; |
|
513 continue; |
|
514 } |
|
515 |
|
516 FloatPoint topLeft = topLeftPositionOfCharacterRange(cur.start + totalOffset - offset, cur.start + totalOffset); |
|
517 |
|
518 ts << "text run " << j << " at (" << topLeft.x() << "," << topLeft.y() << ") "; |
|
519 ts << "startOffset " << range.startOffset << " endOffset " << range.endOffset; |
|
520 |
|
521 if (cur.isVerticalText) |
|
522 ts << " height " << cummulatedHeightOfInlineBoxCharacterRange(range); |
|
523 else |
|
524 ts << " width " << cummulatedWidthOfInlineBoxCharacterRange(range); |
|
525 |
|
526 if (textBox->direction() == RTL || textBox->m_dirOverride) { |
|
527 ts << (textBox->direction() == RTL ? " RTL" : " LTR"); |
|
528 |
|
529 if (textBox->m_dirOverride) |
|
530 ts << " override"; |
|
531 } |
|
532 |
|
533 ts << ": " << quoteAndEscapeNonPrintables(String(textBox->textRenderer()->text()).substring(textBox->start() + range.startOffset, offset)) << "\n"; |
|
534 |
|
535 j++; |
|
536 } |
|
537 |
|
538 i++; |
|
539 } |
|
540 } |
|
541 |
|
542 static inline void writeSVGInlineTextBoxes(TextStream& ts, const RenderText& text, int indent) |
|
543 { |
|
544 for (InlineTextBox* box = text.firstTextBox(); box; box = box->nextTextBox()) |
|
545 writeSVGInlineTextBox(ts, static_cast<SVGInlineTextBox*>(box), indent); |
|
546 } |
|
547 |
|
548 static void writeStandardPrefix(TextStream& ts, const RenderObject& object, int indent) |
|
549 { |
|
550 writeIndent(ts, indent); |
|
551 ts << object.renderName(); |
|
552 |
|
553 if (object.node()) |
|
554 ts << " {" << object.node()->nodeName() << "}"; |
|
555 } |
|
556 |
|
557 static void writeChildren(TextStream& ts, const RenderObject& object, int indent) |
|
558 { |
|
559 for (RenderObject* child = object.firstChild(); child; child = child->nextSibling()) |
|
560 write(ts, *child, indent + 1); |
|
561 } |
|
562 |
|
563 static inline String boundingBoxModeString(bool boundingBoxMode) |
|
564 { |
|
565 return boundingBoxMode ? "objectBoundingBox" : "userSpaceOnUse"; |
|
566 } |
|
567 |
|
568 static inline void writeCommonGradientProperties(TextStream& ts, GradientSpreadMethod spreadMethod, const AffineTransform& gradientTransform, bool boundingBoxMode) |
|
569 { |
|
570 writeNameValuePair(ts, "gradientUnits", boundingBoxModeString(boundingBoxMode)); |
|
571 |
|
572 if (spreadMethod != SpreadMethodPad) |
|
573 ts << " [spreadMethod=" << spreadMethod << "]"; |
|
574 |
|
575 if (!gradientTransform.isIdentity()) |
|
576 ts << " [gradientTransform=" << gradientTransform << "]"; |
|
577 } |
|
578 |
|
579 void writeSVGResourceContainer(TextStream& ts, const RenderObject& object, int indent) |
|
580 { |
|
581 writeStandardPrefix(ts, object, indent); |
|
582 |
|
583 Element* element = static_cast<Element*>(object.node()); |
|
584 const AtomicString& id = element->getIdAttribute(); |
|
585 writeNameAndQuotedValue(ts, "id", id); |
|
586 |
|
587 RenderSVGResourceContainer* resource = const_cast<RenderObject&>(object).toRenderSVGResourceContainer(); |
|
588 ASSERT(resource); |
|
589 |
|
590 if (resource->resourceType() == MaskerResourceType) { |
|
591 RenderSVGResourceMasker* masker = static_cast<RenderSVGResourceMasker*>(resource); |
|
592 writeNameValuePair(ts, "maskUnits", masker->maskUnits()); |
|
593 writeNameValuePair(ts, "maskContentUnits", masker->maskContentUnits()); |
|
594 ts << "\n"; |
|
595 #if ENABLE(FILTERS) |
|
596 } else if (resource->resourceType() == FilterResourceType) { |
|
597 RenderSVGResourceFilter* filter = static_cast<RenderSVGResourceFilter*>(resource); |
|
598 writeNameValuePair(ts, "filterUnits", filter->filterUnits()); |
|
599 writeNameValuePair(ts, "primitiveUnits", filter->primitiveUnits()); |
|
600 ts << "\n"; |
|
601 if (RefPtr<SVGFilterBuilder> builder = filter->buildPrimitives()) { |
|
602 if (FilterEffect* lastEffect = builder->lastEffect()) |
|
603 lastEffect->externalRepresentation(ts, indent + 1); |
|
604 } |
|
605 #endif |
|
606 } else if (resource->resourceType() == ClipperResourceType) { |
|
607 RenderSVGResourceClipper* clipper = static_cast<RenderSVGResourceClipper*>(resource); |
|
608 writeNameValuePair(ts, "clipPathUnits", clipper->clipPathUnits()); |
|
609 ts << "\n"; |
|
610 } else if (resource->resourceType() == MarkerResourceType) { |
|
611 RenderSVGResourceMarker* marker = static_cast<RenderSVGResourceMarker*>(resource); |
|
612 writeNameValuePair(ts, "markerUnits", marker->markerUnits()); |
|
613 ts << " [ref at " << marker->referencePoint() << "]"; |
|
614 ts << " [angle="; |
|
615 if (marker->angle() == -1) |
|
616 ts << "auto" << "]\n"; |
|
617 else |
|
618 ts << marker->angle() << "]\n"; |
|
619 } else if (resource->resourceType() == PatternResourceType) { |
|
620 RenderSVGResourcePattern* pattern = static_cast<RenderSVGResourcePattern*>(resource); |
|
621 |
|
622 // Dump final results that are used for rendering. No use in asking SVGPatternElement for its patternUnits(), as it may |
|
623 // link to other patterns using xlink:href, we need to build the full inheritance chain, aka. collectPatternProperties() |
|
624 PatternAttributes attributes = static_cast<SVGPatternElement*>(pattern->node())->collectPatternProperties(); |
|
625 writeNameValuePair(ts, "patternUnits", boundingBoxModeString(attributes.boundingBoxMode())); |
|
626 writeNameValuePair(ts, "patternContentUnits", boundingBoxModeString(attributes.boundingBoxModeContent())); |
|
627 |
|
628 AffineTransform transform = attributes.patternTransform(); |
|
629 if (!transform.isIdentity()) |
|
630 ts << " [patternTransform=" << transform << "]"; |
|
631 ts << "\n"; |
|
632 } else if (resource->resourceType() == LinearGradientResourceType) { |
|
633 RenderSVGResourceLinearGradient* gradient = static_cast<RenderSVGResourceLinearGradient*>(resource); |
|
634 |
|
635 // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may |
|
636 // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties() |
|
637 SVGLinearGradientElement* linearGradientElement = static_cast<SVGLinearGradientElement*>(gradient->node()); |
|
638 |
|
639 LinearGradientAttributes attributes = linearGradientElement->collectGradientProperties(); |
|
640 writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.boundingBoxMode()); |
|
641 |
|
642 FloatPoint startPoint; |
|
643 FloatPoint endPoint; |
|
644 linearGradientElement->calculateStartEndPoints(attributes, startPoint, endPoint); |
|
645 |
|
646 ts << " [start=" << startPoint << "] [end=" << endPoint << "]\n"; |
|
647 } else if (resource->resourceType() == RadialGradientResourceType) { |
|
648 RenderSVGResourceRadialGradient* gradient = static_cast<RenderSVGResourceRadialGradient*>(resource); |
|
649 |
|
650 // Dump final results that are used for rendering. No use in asking SVGGradientElement for its gradientUnits(), as it may |
|
651 // link to other gradients using xlink:href, we need to build the full inheritance chain, aka. collectGradientProperties() |
|
652 SVGRadialGradientElement* radialGradientElement = static_cast<SVGRadialGradientElement*>(gradient->node()); |
|
653 |
|
654 RadialGradientAttributes attributes = radialGradientElement->collectGradientProperties(); |
|
655 writeCommonGradientProperties(ts, attributes.spreadMethod(), attributes.gradientTransform(), attributes.boundingBoxMode()); |
|
656 |
|
657 FloatPoint focalPoint; |
|
658 FloatPoint centerPoint; |
|
659 float radius; |
|
660 radialGradientElement->calculateFocalCenterPointsAndRadius(attributes, focalPoint, centerPoint, radius); |
|
661 |
|
662 ts << " [center=" << centerPoint << "] [focal=" << focalPoint << "] [radius=" << radius << "]\n"; |
|
663 } else |
|
664 ts << "\n"; |
|
665 writeChildren(ts, object, indent); |
|
666 } |
|
667 |
|
668 void writeSVGContainer(TextStream& ts, const RenderObject& container, int indent) |
|
669 { |
|
670 writeStandardPrefix(ts, container, indent); |
|
671 writePositionAndStyle(ts, container); |
|
672 ts << "\n"; |
|
673 writeResources(ts, container, indent); |
|
674 writeChildren(ts, container, indent); |
|
675 } |
|
676 |
|
677 void write(TextStream& ts, const RenderSVGRoot& root, int indent) |
|
678 { |
|
679 writeStandardPrefix(ts, root, indent); |
|
680 ts << root << "\n"; |
|
681 writeChildren(ts, root, indent); |
|
682 } |
|
683 |
|
684 void writeSVGText(TextStream& ts, const RenderBlock& text, int indent) |
|
685 { |
|
686 writeStandardPrefix(ts, text, indent); |
|
687 writeRenderSVGTextBox(ts, text); |
|
688 ts << "\n"; |
|
689 writeResources(ts, text, indent); |
|
690 writeChildren(ts, text, indent); |
|
691 } |
|
692 |
|
693 void writeSVGInlineText(TextStream& ts, const RenderText& text, int indent) |
|
694 { |
|
695 writeStandardPrefix(ts, text, indent); |
|
696 |
|
697 // Why not just linesBoundingBox()? |
|
698 ts << " " << FloatRect(text.firstRunOrigin(), text.linesBoundingBox().size()) << "\n"; |
|
699 writeResources(ts, text, indent); |
|
700 writeSVGInlineTextBoxes(ts, text, indent); |
|
701 } |
|
702 |
|
703 void writeSVGImage(TextStream& ts, const RenderImage& image, int indent) |
|
704 { |
|
705 writeStandardPrefix(ts, image, indent); |
|
706 writePositionAndStyle(ts, image); |
|
707 ts << "\n"; |
|
708 writeResources(ts, image, indent); |
|
709 } |
|
710 |
|
711 void write(TextStream& ts, const RenderPath& path, int indent) |
|
712 { |
|
713 writeStandardPrefix(ts, path, indent); |
|
714 ts << path << "\n"; |
|
715 writeResources(ts, path, indent); |
|
716 } |
|
717 |
|
718 void writeSVGGradientStop(TextStream& ts, const RenderSVGGradientStop& stop, int indent) |
|
719 { |
|
720 writeStandardPrefix(ts, stop, indent); |
|
721 |
|
722 SVGStopElement* stopElement = static_cast<SVGStopElement*>(stop.node()); |
|
723 ASSERT(stopElement); |
|
724 |
|
725 RenderStyle* style = stop.style(); |
|
726 if (!style) |
|
727 return; |
|
728 |
|
729 ts << " [offset=" << stopElement->offset() << "] [color=" << stopElement->stopColorIncludingOpacity() << "]\n"; |
|
730 } |
|
731 |
|
732 void writeResources(TextStream& ts, const RenderObject& object, int indent) |
|
733 { |
|
734 const RenderStyle* style = object.style(); |
|
735 const SVGRenderStyle* svgStyle = style->svgStyle(); |
|
736 |
|
737 RenderObject& renderer = const_cast<RenderObject&>(object); |
|
738 if (!svgStyle->maskerResource().isEmpty()) { |
|
739 if (RenderSVGResourceMasker* masker = getRenderSVGResourceById<RenderSVGResourceMasker>(object.document(), svgStyle->maskerResource())) { |
|
740 writeIndent(ts, indent); |
|
741 ts << " "; |
|
742 writeNameAndQuotedValue(ts, "masker", svgStyle->maskerResource()); |
|
743 ts << " "; |
|
744 writeStandardPrefix(ts, *masker, 0); |
|
745 ts << " " << masker->resourceBoundingBox(&renderer) << "\n"; |
|
746 } |
|
747 } |
|
748 if (!svgStyle->clipperResource().isEmpty()) { |
|
749 if (RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(object.document(), svgStyle->clipperResource())) { |
|
750 writeIndent(ts, indent); |
|
751 ts << " "; |
|
752 writeNameAndQuotedValue(ts, "clipPath", svgStyle->clipperResource()); |
|
753 ts << " "; |
|
754 writeStandardPrefix(ts, *clipper, 0); |
|
755 ts << " " << clipper->resourceBoundingBox(&renderer) << "\n"; |
|
756 } |
|
757 } |
|
758 #if ENABLE(FILTERS) |
|
759 if (!svgStyle->filterResource().isEmpty()) { |
|
760 if (RenderSVGResourceFilter* filter = getRenderSVGResourceById<RenderSVGResourceFilter>(object.document(), svgStyle->filterResource())) { |
|
761 writeIndent(ts, indent); |
|
762 ts << " "; |
|
763 writeNameAndQuotedValue(ts, "filter", svgStyle->filterResource()); |
|
764 ts << " "; |
|
765 writeStandardPrefix(ts, *filter, 0); |
|
766 ts << " " << filter->resourceBoundingBox(&renderer) << "\n"; |
|
767 } |
|
768 } |
|
769 #endif |
|
770 } |
|
771 |
|
772 } // namespace WebCore |
|
773 |
|
774 #endif // ENABLE(SVG) |