OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2015 Google Inc. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license that can be | |
5 * found in the LICENSE file. | |
6 */ | |
7 | |
8 #include "SkSVGDevice.h" | |
9 | |
10 #include "SkBitmap.h" | |
11 #include "SkDraw.h" | |
12 #include "SkPaint.h" | |
13 #include "SkParsePath.h" | |
14 #include "SkPathOps.h" | |
15 #include "SkShader.h" | |
16 #include "SkStream.h" | |
17 #include "SkTypeface.h" | |
18 #include "SkUtils.h" | |
19 #include "SkXMLWriter.h" | |
20 | |
21 namespace { | |
22 | |
23 static SkString svg_color(SkColor color) { | |
24 return SkStringPrintf("rgb(%u,%u,%u)", | |
25 SkColorGetR(color), | |
26 SkColorGetG(color), | |
27 SkColorGetB(color)); | |
28 } | |
29 | |
30 static SkScalar svg_opacity(SkColor color) { | |
31 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE; | |
32 } | |
33 | |
34 // Keep in sync with SkPaint::Cap | |
35 static const char* cap_map[] = { | |
36 NULL, // kButt_Cap (default) | |
37 "round", // kRound_Cap | |
38 "square" // kSquare_Cap | |
39 }; | |
40 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, missing_cap_map
_entry); | |
41 | |
42 static const char* svg_cap(SkPaint::Cap cap) { | |
43 SkASSERT(cap < SK_ARRAY_COUNT(cap_map)); | |
44 return cap_map[cap]; | |
45 } | |
46 | |
47 // Keep in sync with SkPaint::Join | |
48 static const char* join_map[] = { | |
49 NULL, // kMiter_Join (default) | |
50 "round", // kRound_Join | |
51 "bevel" // kBevel_Join | |
52 }; | |
53 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, missing_join_
map_entry); | |
54 | |
55 static const char* svg_join(SkPaint::Join join) { | |
56 SkASSERT(join < SK_ARRAY_COUNT(join_map)); | |
57 return join_map[join]; | |
58 } | |
59 | |
60 // Keep in sync with SkPaint::Align | |
61 static const char* text_align_map[] = { | |
62 NULL, // kLeft_Align (default) | |
63 "middle", // kCenter_Align | |
64 "end" // kRight_Align | |
65 }; | |
66 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount, | |
67 missing_text_align_map_entry); | |
68 static const char* svg_text_align(SkPaint::Align align) { | |
69 SkASSERT(align < SK_ARRAY_COUNT(text_align_map)); | |
70 return text_align_map[align]; | |
71 } | |
72 | |
73 static SkString svg_transform(const SkMatrix& t) { | |
74 SkASSERT(!t.isIdentity()); | |
75 | |
76 SkString tstr; | |
77 switch (t.getType()) { | |
78 case SkMatrix::kPerspective_Mask: | |
79 SkDebugf("Can't handle perspective matrices."); | |
80 break; | |
81 case SkMatrix::kTranslate_Mask: | |
82 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY()); | |
83 break; | |
84 case SkMatrix::kScale_Mask: | |
85 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY()); | |
86 break; | |
87 default: | |
88 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined | |
89 // | a c e | | |
90 // | b d f | | |
91 // | 0 0 1 | | |
92 tstr.printf("matrix(%g %g %g %g %g %g)", | |
93 t.getScaleX(), t.getSkewY(), | |
94 t.getSkewX(), t.getScaleY(), | |
95 t.getTranslateX(), t.getTranslateY()); | |
96 break; | |
97 } | |
98 | |
99 return tstr; | |
100 } | |
101 | |
102 static void append_escaped_unichar(SkUnichar c, SkString* text) { | |
103 switch(c) { | |
104 case '&': | |
105 text->append("&"); | |
106 break; | |
107 case '"': | |
108 text->append("""); | |
109 break; | |
110 case '\'': | |
111 text->append("'"); | |
112 break; | |
113 case '<': | |
114 text->append("<"); | |
115 break; | |
116 case '>': | |
117 text->append(">"); | |
118 break; | |
119 default: | |
120 text->appendUnichar(c); | |
121 break; | |
122 } | |
123 } | |
124 | |
125 static SkString svg_text(const void* text, size_t byteLen, const SkPaint& paint)
{ | |
126 SkString svgText; | |
127 int count = paint.countText(text, byteLen); | |
128 | |
129 switch(paint.getTextEncoding()) { | |
130 case SkPaint::kGlyphID_TextEncoding: { | |
131 SkASSERT(count * sizeof(uint16_t) == byteLen); | |
132 SkAutoSTArray<64, SkUnichar> unichars(count); | |
133 paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get()); | |
134 for (int i = 0; i < count; ++i) { | |
135 append_escaped_unichar(unichars[i], &svgText); | |
136 } | |
137 } break; | |
138 case SkPaint::kUTF8_TextEncoding: { | |
139 const char* c8 = reinterpret_cast<const char*>(text); | |
140 for (int i = 0; i < count; ++i) { | |
141 append_escaped_unichar(SkUTF8_NextUnichar(&c8), &svgText); | |
142 } | |
143 SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8); | |
144 } break; | |
145 case SkPaint::kUTF16_TextEncoding: { | |
146 const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text); | |
147 for (int i = 0; i < count; ++i) { | |
148 append_escaped_unichar(SkUTF16_NextUnichar(&c16), &svgText); | |
149 } | |
150 SkASSERT(SkIsAlign2(byteLen)); | |
151 SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16)
; | |
152 } break; | |
153 case SkPaint::kUTF32_TextEncoding: { | |
154 SkASSERT(count * sizeof(uint32_t) == byteLen); | |
155 const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text); | |
156 for (int i = 0; i < count; ++i) { | |
157 append_escaped_unichar(c32[i], &svgText); | |
158 } | |
159 } break; | |
160 default: | |
161 SkFAIL("unknown text encoding"); | |
162 } | |
163 | |
164 return svgText; | |
165 } | |
166 | |
167 struct Resources { | |
168 Resources(const SkPaint& paint) | |
169 : fPaintServer(svg_color(paint.getColor())) {} | |
170 | |
171 SkString fPaintServer; | |
172 SkString fClip; | |
173 }; | |
174 | |
175 } | |
176 | |
177 // For now all this does is serve unique serial IDs, but it will eventually evol
ve to track | |
178 // and deduplicate resources. | |
179 class SkSVGDevice::ResourceBucket : ::SkNoncopyable { | |
180 public: | |
181 ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0) {} | |
182 | |
183 SkString addLinearGradient() { | |
184 return SkStringPrintf("gradient_%d", fGradientCount++); | |
185 } | |
186 | |
187 SkString addClip() { | |
188 return SkStringPrintf("clip_%d", fClipCount++); | |
189 } | |
190 | |
191 SkString addPath() { | |
192 return SkStringPrintf("path_%d", fPathCount++); | |
193 } | |
194 | |
195 private: | |
196 uint32_t fGradientCount; | |
197 uint32_t fClipCount; | |
198 uint32_t fPathCount; | |
199 }; | |
200 | |
201 class SkSVGDevice::AutoElement : ::SkNoncopyable { | |
202 public: | |
203 AutoElement(const char name[], SkXMLWriter* writer) | |
204 : fWriter(writer) | |
205 , fResourceBucket(NULL) { | |
206 fWriter->startElement(name); | |
207 } | |
208 | |
209 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket, | |
210 const SkDraw& draw, const SkPaint& paint) | |
211 : fWriter(writer) | |
212 , fResourceBucket(bucket) { | |
213 | |
214 Resources res = this->addResources(draw, paint); | |
215 | |
216 fWriter->startElement(name); | |
217 | |
218 this->addPaint(paint, res); | |
219 | |
220 if (!draw.fMatrix->isIdentity()) { | |
221 this->addAttribute("transform", svg_transform(*draw.fMatrix)); | |
222 } | |
223 } | |
224 | |
225 ~AutoElement() { | |
226 fWriter->endElement(); | |
227 } | |
228 | |
229 void addAttribute(const char name[], const char val[]) { | |
230 fWriter->addAttribute(name, val); | |
231 } | |
232 | |
233 void addAttribute(const char name[], const SkString& val) { | |
234 fWriter->addAttribute(name, val.c_str()); | |
235 } | |
236 | |
237 void addAttribute(const char name[], int32_t val) { | |
238 fWriter->addS32Attribute(name, val); | |
239 } | |
240 | |
241 void addAttribute(const char name[], SkScalar val) { | |
242 fWriter->addScalarAttribute(name, val); | |
243 } | |
244 | |
245 void addText(const SkString& text) { | |
246 fWriter->addText(text.c_str(), text.size()); | |
247 } | |
248 | |
249 void addRectAttributes(const SkRect&); | |
250 void addPathAttributes(const SkPath&); | |
251 void addTextAttributes(const SkPaint&); | |
252 | |
253 private: | |
254 Resources addResources(const SkDraw& draw, const SkPaint& paint); | |
255 void addClipResources(const SkDraw& draw, Resources* resources); | |
256 void addShaderResources(const SkPaint& paint, Resources* resources); | |
257 | |
258 void addPaint(const SkPaint& paint, const Resources& resources); | |
259 | |
260 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkSh
ader* shader); | |
261 | |
262 SkXMLWriter* fWriter; | |
263 ResourceBucket* fResourceBucket; | |
264 }; | |
265 | |
266 void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& r
esources) { | |
267 SkPaint::Style style = paint.getStyle(); | |
268 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style)
{ | |
269 this->addAttribute("fill", resources.fPaintServer); | |
270 | |
271 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { | |
272 this->addAttribute("fill-opacity", svg_opacity(paint.getColor())); | |
273 } | |
274 } else { | |
275 SkASSERT(style == SkPaint::kStroke_Style); | |
276 this->addAttribute("fill", "none"); | |
277 } | |
278 | |
279 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Styl
e) { | |
280 this->addAttribute("stroke", resources.fPaintServer); | |
281 | |
282 SkScalar strokeWidth = paint.getStrokeWidth(); | |
283 if (strokeWidth == 0) { | |
284 // Hairline stroke | |
285 strokeWidth = 1; | |
286 this->addAttribute("vector-effect", "non-scaling-stroke"); | |
287 } | |
288 this->addAttribute("stroke-width", strokeWidth); | |
289 | |
290 if (const char* cap = svg_cap(paint.getStrokeCap())) { | |
291 this->addAttribute("stroke-linecap", cap); | |
292 } | |
293 | |
294 if (const char* join = svg_join(paint.getStrokeJoin())) { | |
295 this->addAttribute("stroke-linejoin", join); | |
296 } | |
297 | |
298 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) { | |
299 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter()); | |
300 } | |
301 | |
302 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { | |
303 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor())); | |
304 } | |
305 } else { | |
306 SkASSERT(style == SkPaint::kFill_Style); | |
307 this->addAttribute("stroke", "none"); | |
308 } | |
309 | |
310 if (!resources.fClip.isEmpty()) { | |
311 this->addAttribute("clip-path", resources.fClip); | |
312 } | |
313 } | |
314 | |
315 Resources SkSVGDevice::AutoElement::addResources(const SkDraw& draw, const SkPai
nt& paint) { | |
316 Resources resources(paint); | |
317 | |
318 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clip
s. | |
319 bool hasClip = !draw.fClipStack->isWideOpen(); | |
320 bool hasShader = SkToBool(paint.getShader()); | |
321 | |
322 if (hasClip || hasShader) { | |
323 AutoElement defs("defs", fWriter); | |
324 | |
325 if (hasClip) { | |
326 this->addClipResources(draw, &resources); | |
327 } | |
328 | |
329 if (hasShader) { | |
330 this->addShaderResources(paint, &resources); | |
331 } | |
332 } | |
333 | |
334 return resources; | |
335 } | |
336 | |
337 void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resource
s* resources) { | |
338 const SkShader* shader = paint.getShader(); | |
339 SkASSERT(SkToBool(shader)); | |
340 | |
341 SkShader::GradientInfo grInfo; | |
342 grInfo.fColorCount = 0; | |
343 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) { | |
344 // TODO: non-linear gradient support | |
345 SkDebugf("unsupported shader type\n"); | |
346 return; | |
347 } | |
348 | |
349 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount); | |
350 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount); | |
351 grInfo.fColors = grColors.get(); | |
352 grInfo.fColorOffsets = grOffsets.get(); | |
353 | |
354 // One more call to get the actual colors/offsets. | |
355 shader->asAGradient(&grInfo); | |
356 SkASSERT(grInfo.fColorCount <= grColors.count()); | |
357 SkASSERT(grInfo.fColorCount <= grOffsets.count()); | |
358 | |
359 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shad
er).c_str()); | |
360 } | |
361 | |
362 void SkSVGDevice::AutoElement::addClipResources(const SkDraw& draw, Resources* r
esources) { | |
363 SkASSERT(!draw.fClipStack->isWideOpen()); | |
364 | |
365 SkPath clipPath; | |
366 (void) draw.fClipStack->asPath(&clipPath); | |
367 | |
368 SkString clipID = fResourceBucket->addClip(); | |
369 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ? | |
370 "evenodd" : "nonzero"; | |
371 { | |
372 // clipPath is in device space, but since we're only pushing transform a
ttributes | |
373 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == d
evice space. | |
374 AutoElement clipPathElement("clipPath", fWriter); | |
375 clipPathElement.addAttribute("id", clipID); | |
376 | |
377 SkRect clipRect = SkRect::MakeEmpty(); | |
378 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) { | |
379 AutoElement rectElement("rect", fWriter); | |
380 rectElement.addRectAttributes(clipRect); | |
381 rectElement.addAttribute("clip-rule", clipRule); | |
382 } else { | |
383 AutoElement pathElement("path", fWriter); | |
384 pathElement.addPathAttributes(clipPath); | |
385 pathElement.addAttribute("clip-rule", clipRule); | |
386 } | |
387 } | |
388 | |
389 resources->fClip.printf("url(#%s)", clipID.c_str()); | |
390 } | |
391 | |
392 SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::Gradient
Info& info, | |
393 const SkShader* shader)
{ | |
394 SkASSERT(fResourceBucket); | |
395 SkString id = fResourceBucket->addLinearGradient(); | |
396 | |
397 { | |
398 AutoElement gradient("linearGradient", fWriter); | |
399 | |
400 gradient.addAttribute("id", id); | |
401 gradient.addAttribute("gradientUnits", "userSpaceOnUse"); | |
402 gradient.addAttribute("x1", info.fPoint[0].x()); | |
403 gradient.addAttribute("y1", info.fPoint[0].y()); | |
404 gradient.addAttribute("x2", info.fPoint[1].x()); | |
405 gradient.addAttribute("y2", info.fPoint[1].y()); | |
406 | |
407 if (!shader->getLocalMatrix().isIdentity()) { | |
408 this->addAttribute("gradientTransform", svg_transform(shader->getLoc
alMatrix())); | |
409 } | |
410 | |
411 SkASSERT(info.fColorCount >= 2); | |
412 for (int i = 0; i < info.fColorCount; ++i) { | |
413 SkColor color = info.fColors[i]; | |
414 SkString colorStr(svg_color(color)); | |
415 | |
416 { | |
417 AutoElement stop("stop", fWriter); | |
418 stop.addAttribute("offset", info.fColorOffsets[i]); | |
419 stop.addAttribute("stop-color", colorStr.c_str()); | |
420 | |
421 if (SK_AlphaOPAQUE != SkColorGetA(color)) { | |
422 stop.addAttribute("stop-opacity", svg_opacity(color)); | |
423 } | |
424 } | |
425 } | |
426 } | |
427 | |
428 return id; | |
429 } | |
430 | |
431 void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) { | |
432 // x, y default to 0 | |
433 if (rect.x() != 0) { | |
434 this->addAttribute("x", rect.x()); | |
435 } | |
436 if (rect.y() != 0) { | |
437 this->addAttribute("y", rect.y()); | |
438 } | |
439 | |
440 this->addAttribute("width", rect.width()); | |
441 this->addAttribute("height", rect.height()); | |
442 } | |
443 | |
444 void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) { | |
445 SkString pathData; | |
446 SkParsePath::ToSVGString(path, &pathData); | |
447 this->addAttribute("d", pathData); | |
448 } | |
449 | |
450 void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) { | |
451 this->addAttribute("font-size", paint.getTextSize()); | |
452 | |
453 SkTypeface::Style style = paint.getTypeface()->style(); | |
454 if (style & SkTypeface::kItalic) { | |
455 this->addAttribute("font-style", "italic"); | |
456 } | |
457 if (style & SkTypeface::kBold) { | |
458 this->addAttribute("font-weight", "bold"); | |
459 } | |
460 | |
461 SkAutoTUnref<const SkTypeface> tface(paint.getTypeface() ? | |
462 SkRef(paint.getTypeface()) : SkTypeface::RefDefault(style)); | |
463 SkString familyName; | |
464 tface->getFamilyName(&familyName); | |
465 if (!familyName.isEmpty()) { | |
466 this->addAttribute("font-family", familyName); | |
467 } | |
468 | |
469 if (const char* textAlign = svg_text_align(paint.getTextAlign())) { | |
470 this->addAttribute("text-anchor", textAlign); | |
471 } | |
472 } | |
473 | |
474 SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkWStream* wstream) { | |
475 if (!SkToBool(wstream)) { | |
476 return NULL; | |
477 } | |
478 | |
479 return SkNEW_ARGS(SkSVGDevice, (size, wstream)); | |
480 } | |
481 | |
482 SkSVGDevice::SkSVGDevice(const SkISize& size, SkWStream* wstream) | |
483 : fWriter(SkNEW_ARGS(SkXMLStreamWriter, (wstream))) | |
484 , fResourceBucket(SkNEW(ResourceBucket)) { | |
485 | |
486 fLegacyBitmap.setInfo(SkImageInfo::MakeUnknown(size.width(), size.height()))
; | |
487 | |
488 fWriter->writeHeader(); | |
489 | |
490 // The root <svg> tag gets closed by the destructor. | |
491 fRootElement.reset(SkNEW_ARGS(AutoElement, ("svg", fWriter))); | |
492 | |
493 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg"); | |
494 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); | |
495 fRootElement->addAttribute("width", size.width()); | |
496 fRootElement->addAttribute("height", size.height()); | |
497 } | |
498 | |
499 SkSVGDevice::~SkSVGDevice() { | |
500 } | |
501 | |
502 SkImageInfo SkSVGDevice::imageInfo() const { | |
503 return fLegacyBitmap.info(); | |
504 } | |
505 | |
506 const SkBitmap& SkSVGDevice::onAccessBitmap() { | |
507 return fLegacyBitmap; | |
508 } | |
509 | |
510 void SkSVGDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { | |
511 AutoElement rect("rect", fWriter, fResourceBucket, draw, paint); | |
512 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()), | |
513 SkIntToScalar(this->height()))); | |
514 } | |
515 | |
516 void SkSVGDevice::drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t cou
nt, | |
517 const SkPoint[], const SkPaint& paint) { | |
518 // todo | |
519 SkDebugf("unsupported operation: drawPoints()\n"); | |
520 } | |
521 | |
522 void SkSVGDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& p
aint) { | |
523 AutoElement rect("rect", fWriter, fResourceBucket, draw, paint); | |
524 rect.addRectAttributes(r); | |
525 } | |
526 | |
527 void SkSVGDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint
& paint) { | |
528 AutoElement ellipse("ellipse", fWriter, fResourceBucket, draw, paint); | |
529 ellipse.addAttribute("cx", oval.centerX()); | |
530 ellipse.addAttribute("cy", oval.centerY()); | |
531 ellipse.addAttribute("rx", oval.width() / 2); | |
532 ellipse.addAttribute("ry", oval.height() / 2); | |
533 } | |
534 | |
535 void SkSVGDevice::drawRRect(const SkDraw&, const SkRRect& rr, const SkPaint& pai
nt) { | |
536 // todo | |
537 SkDebugf("unsupported operation: drawRRect()\n"); | |
538 } | |
539 | |
540 void SkSVGDevice::drawPath(const SkDraw& draw, const SkPath& path, const SkPaint
& paint, | |
541 const SkMatrix* prePathMatrix, bool pathIsMutable) { | |
542 AutoElement elem("path", fWriter, fResourceBucket, draw, paint); | |
543 elem.addPathAttributes(path); | |
544 } | |
545 | |
546 void SkSVGDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap, | |
547 const SkMatrix& matrix, const SkPaint& paint) { | |
548 // todo | |
549 SkDebugf("unsupported operation: drawBitmap()\n"); | |
550 } | |
551 | |
552 void SkSVGDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap, | |
553 int x, int y, const SkPaint& paint) { | |
554 // todo | |
555 SkDebugf("unsupported operation: drawSprite()\n"); | |
556 } | |
557 | |
558 void SkSVGDevice::drawBitmapRect(const SkDraw&, const SkBitmap&, const SkRect* s
rcOrNull, | |
559 const SkRect& dst, const SkPaint& paint, | |
560 SkCanvas::DrawBitmapRectFlags flags) { | |
561 // todo | |
562 SkDebugf("unsupported operation: drawBitmapRect()\n"); | |
563 } | |
564 | |
565 void SkSVGDevice::drawText(const SkDraw& draw, const void* text, size_t len, | |
566 SkScalar x, SkScalar y, const SkPaint& paint) { | |
567 AutoElement elem("text", fWriter, fResourceBucket, draw, paint); | |
568 elem.addTextAttributes(paint); | |
569 elem.addAttribute("x", x); | |
570 elem.addAttribute("y", y); | |
571 elem.addText(svg_text(text, len, paint)); | |
572 } | |
573 | |
574 void SkSVGDevice::drawPosText(const SkDraw& draw, const void* text, size_t len, | |
575 const SkScalar pos[], int scalarsPerPos, const SkP
oint& offset, | |
576 const SkPaint& paint) { | |
577 SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2); | |
578 | |
579 AutoElement elem("text", fWriter, fResourceBucket, draw, paint); | |
580 elem.addTextAttributes(paint); | |
581 | |
582 SkString xStr; | |
583 SkString yStr; | |
584 for (int i = 0; i < paint.countText(text, len); ++i) { | |
585 xStr.appendf("%.8g, ", offset.x() + pos[i * scalarsPerPos]); | |
586 | |
587 if (scalarsPerPos == 2) { | |
588 yStr.appendf("%.8g, ", offset.y() + pos[i * scalarsPerPos + 1]); | |
589 } | |
590 } | |
591 | |
592 if (scalarsPerPos != 2) { | |
593 yStr.appendScalar(offset.y()); | |
594 } | |
595 | |
596 elem.addAttribute("x", xStr); | |
597 elem.addAttribute("y", yStr); | |
598 elem.addText(svg_text(text, len, paint)); | |
599 } | |
600 | |
601 void SkSVGDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len, co
nst SkPath& path, | |
602 const SkMatrix* matrix, const SkPaint& paint) { | |
603 SkString pathID = fResourceBucket->addPath(); | |
604 | |
605 { | |
606 AutoElement defs("defs", fWriter); | |
607 AutoElement pathElement("path", fWriter); | |
608 pathElement.addAttribute("id", pathID); | |
609 pathElement.addPathAttributes(path); | |
610 | |
611 } | |
612 | |
613 { | |
614 AutoElement textElement("text", fWriter); | |
615 textElement.addTextAttributes(paint); | |
616 | |
617 if (matrix && !matrix->isIdentity()) { | |
618 textElement.addAttribute("transform", svg_transform(*matrix)); | |
619 } | |
620 | |
621 { | |
622 AutoElement textPathElement("textPath", fWriter); | |
623 textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pat
hID.c_str())); | |
624 | |
625 if (paint.getTextAlign() != SkPaint::kLeft_Align) { | |
626 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align || | |
627 paint.getTextAlign() == SkPaint::kRight_Align); | |
628 textPathElement.addAttribute("startOffset", | |
629 paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "10
0%"); | |
630 } | |
631 | |
632 textPathElement.addText(svg_text(text, len, paint)); | |
633 } | |
634 } | |
635 } | |
636 | |
637 void SkSVGDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCo
unt, | |
638 const SkPoint verts[], const SkPoint texs[], | |
639 const SkColor colors[], SkXfermode* xmode, | |
640 const uint16_t indices[], int indexCount, | |
641 const SkPaint& paint) { | |
642 // todo | |
643 SkDebugf("unsupported operation: drawVertices()\n"); | |
644 } | |
645 | |
646 void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y, | |
647 const SkPaint&) { | |
648 // todo | |
649 SkDebugf("unsupported operation: drawDevice()\n"); | |
650 } | |
OLD | NEW |