1 /*=========================================================================
2
3 Program: Visualization Toolkit
4 Module: vtkSVGContextDevice2D.cxx
5
6 Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7 All rights reserved.
8 See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9
10 This software is distributed WITHOUT ANY WARRANTY; without even
11 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 PURPOSE. See the above copyright notice for more information.
13
14 =========================================================================*/
15
16 // Hide VTK_DEPRECATED_IN_9_1_0() warnings for this class.
17 #define VTK_DEPRECATION_LEVEL 0
18
19 #include "vtkSVGContextDevice2D.h"
20
21 #include "vtkAssume.h"
22 #include "vtkBase64OutputStream.h"
23 #include "vtkBrush.h"
24 #include "vtkColor.h"
25 #include "vtkFloatArray.h"
26 #include "vtkFreeTypeTools.h"
27 #include "vtkImageCast.h"
28 #include "vtkImageData.h"
29 #include "vtkIntArray.h"
30 #include "vtkMath.h"
31 #include "vtkMatrix3x3.h"
32 #include "vtkMatrix4x4.h"
33 #include "vtkNew.h"
34 #include "vtkObjectFactory.h"
35 #include "vtkPNGWriter.h"
36 #include "vtkPath.h"
37 #include "vtkPen.h"
38 #include "vtkPointData.h"
39 #include "vtkRenderWindow.h"
40 #include "vtkRenderer.h"
41 #include "vtkTextProperty.h"
42 #include "vtkTextRenderer.h"
43 #include "vtkTransform.h"
44 #include "vtkUnicodeString.h"
45 #include "vtkUnsignedCharArray.h"
46 #include "vtkVector.h"
47 #include "vtkVectorOperators.h"
48 #include "vtkXMLDataElement.h"
49
50 #include <algorithm>
51 #include <cassert>
52 #include <cmath>
53 #include <iomanip>
54 #include <map>
55 #include <set>
56 #include <sstream>
57 #include <utility>
58
59 namespace
60 {
61
ColorToString(const unsigned char * rgb)62 std::string ColorToString(const unsigned char* rgb)
63 {
64 std::ostringstream out;
65 out << "#";
66 for (int i = 0; i < 3; ++i)
67 {
68 out << std::setw(2) << std::right << std::setfill('0') << std::hex
69 << static_cast<unsigned int>(rgb[i]);
70 }
71 return out.str();
72 }
73
74 // Bbox is xmin, xmax, ymin, ymax. Writes:
75 // "xmin,ymin,xmax,ymax"
BBoxToString(const std::array<int,4> & bbox)76 std::string BBoxToString(const std::array<int, 4>& bbox)
77 {
78 std::ostringstream out;
79 out << bbox[0] << "," << bbox[2] << "," << bbox[1] << "," << bbox[3];
80 return out.str();
81 }
82
Transform2DToString(const std::array<double,9> xform)83 std::string Transform2DToString(const std::array<double, 9> xform)
84 {
85 std::ostringstream out;
86 out << "matrix(" << xform[0] << "," << xform[3] << "," << xform[1] << "," << xform[4] << ","
87 << xform[2] << "," << xform[5] << ")";
88 return out.str();
89 }
90
91 struct EllipseHelper
92 {
EllipseHelper__anona003e8100111::EllipseHelper93 EllipseHelper(float cx, float cy, float rx, float ry)
94 : X(0.f)
95 , Y(0.f)
96 , Cx(cx)
97 , Cy(cy)
98 , Rx(rx)
99 , Ry(ry)
100 {
101 }
102
UpdateDegrees__anona003e8100111::EllipseHelper103 void UpdateDegrees(float degrees) { this->UpdateRadians(vtkMath::RadiansFromDegrees(degrees)); }
104
UpdateRadians__anona003e8100111::EllipseHelper105 void UpdateRadians(float radians)
106 {
107 this->X = this->Cx + std::cos(radians) * this->Rx;
108 this->Y = this->Cy + std::sin(radians) * this->Ry;
109 }
110
111 float X;
112 float Y;
113
114 private:
115 float Cx;
116 float Cy;
117 float Rx;
118 float Ry;
119 };
120
121 struct FontKey
122 {
123 vtkSmartPointer<vtkTextProperty> TextProperty;
124
FontKey__anona003e8100111::FontKey125 explicit FontKey(vtkTextProperty* tprop)
126 : TextProperty(vtkSmartPointer<vtkTextProperty>::New())
127 {
128 // Clone into an internal tprop. The property will likely be modified by
129 // the time we get around to writing out definitions.
130 this->TextProperty->ShallowCopy(tprop);
131
132 // Blank out properties that we don't care about for raw outlines:
133 this->TextProperty->SetFontSize(0);
134 this->TextProperty->SetOrientation(0.);
135 }
136
137 FontKey(const FontKey& o) = default;
138
operator <__anona003e8100111::FontKey139 bool operator<(const FontKey& other) const
140 {
141 const int thisFontFamily = this->TextProperty->GetFontFamily();
142 const int otherFontFamily = other.TextProperty->GetFontFamily();
143 if (thisFontFamily < otherFontFamily)
144 {
145 return true;
146 }
147 else if (thisFontFamily > otherFontFamily)
148 {
149 return false;
150 }
151
152 const bool thisBold = this->TextProperty->GetBold() != 0;
153 const bool otherBold = other.TextProperty->GetBold() != 0;
154 if (thisBold < otherBold)
155 {
156 return true;
157 }
158 else if (thisBold > otherBold)
159 {
160 return false;
161 }
162
163 const bool thisItalic = this->TextProperty->GetItalic() != 0;
164 const bool otherItalic = other.TextProperty->GetItalic() != 0;
165 if (thisItalic < otherItalic)
166 {
167 return true;
168 }
169 else if (thisItalic > otherItalic)
170 {
171 return false;
172 }
173
174 if (thisFontFamily == VTK_FONT_FILE)
175 {
176 const char* thisFile = this->TextProperty->GetFontFile();
177 const char* otherFile = other.TextProperty->GetFontFile();
178 if (thisFile < otherFile)
179 {
180 return true;
181 }
182 else if (thisFile > otherFile)
183 {
184 return false;
185 }
186 }
187
188 return false;
189 }
190 };
191
192 // FIXME(#18327): Port to work on UTf-8 directly rather than relying on
193 // `vtkUnicodeString`.
194 struct FontInfo
195 {
196 using CharType = vtkUnicodeString::value_type;
197 using KerningPairType = std::pair<CharType, CharType>;
198
FontInfo__anona003e8100111::FontInfo199 explicit FontInfo(const std::string& svgId)
200 : SVGId(svgId)
201 {
202 }
203
ProcessString__anona003e8100111::FontInfo204 void ProcessString(const vtkUnicodeString& str)
205 {
206 vtkUnicodeString::const_iterator it = str.begin();
207 vtkUnicodeString::const_iterator end = str.end();
208 if (it == end)
209 {
210 return;
211 }
212
213 vtkUnicodeString::const_iterator next = it;
214 std::advance(next, 1);
215 while (next != end)
216 {
217 this->Chars.insert(*it);
218 this->KerningPairs.insert(std::make_pair(*it, *next));
219 std::advance(it, 1);
220 std::advance(next, 1);
221 }
222
223 // Last char:
224 this->Chars.insert(*it);
225 }
226
227 std::string SVGId;
228 std::set<CharType> Chars;
229 std::set<KerningPairType> KerningPairs;
230
231 private:
232 FontInfo(const FontInfo&) = delete;
233 void operator=(const FontInfo&) = delete;
234 };
235
236 struct ImageInfo
237 {
ImageInfo__anona003e8100111::ImageInfo238 explicit ImageInfo(vtkImageData* img)
239 {
240 vtkNew<vtkPNGWriter> pngWriter;
241 pngWriter->WriteToMemoryOn();
242 pngWriter->SetCompressionLevel(0);
243 pngWriter->SetInputData(img);
244 pngWriter->Write();
245
246 vtkUnsignedCharArray* png = pngWriter->GetResult();
247 if (!png || png->GetNumberOfValues() == 0)
248 {
249 return;
250 }
251
252 std::ostringstream base64Stream;
253 base64Stream << "data:image/png;base64,";
254
255 vtkNew<vtkBase64OutputStream> base64Encoder;
256 base64Encoder->SetStream(&base64Stream);
257 if (!base64Encoder->StartWriting() ||
258 !base64Encoder->Write(png->GetPointer(0), png->GetNumberOfValues()) ||
259 !base64Encoder->EndWriting())
260 {
261 return;
262 }
263
264 int* dims = img->GetDimensions();
265 this->Size[0] = dims[0];
266 this->Size[1] = dims[1];
267
268 this->PNGBase64 = base64Stream.str();
269 }
270
ImageInfo__anona003e8100111::ImageInfo271 ImageInfo(ImageInfo&& o) noexcept
272 : Size(o.Size)
273 , Id(std::move(o.Id))
274 , PNGBase64(std::move(o.PNGBase64))
275 {
276 }
277
operator <__anona003e8100111::ImageInfo278 bool operator<(const ImageInfo& other) const
279 {
280 if (this->Size[0] < other.Size[0])
281 {
282 return true;
283 }
284 else if (this->Size[0] > other.Size[0])
285 {
286 return false;
287 }
288
289 if (this->Size[1] < other.Size[1])
290 {
291 return true;
292 }
293 else if (this->Size[1] > other.Size[1])
294 {
295 return false;
296 }
297
298 if (this->PNGBase64 < other.PNGBase64)
299 {
300 return true;
301 }
302
303 return false;
304 }
305
306 std::array<int, 2> Size;
307 std::string Id;
308 std::string PNGBase64;
309
310 private:
311 ImageInfo(const ImageInfo&) = delete;
312 void operator=(const ImageInfo&) = delete;
313 };
314
315 struct PatternInfo
316 {
PatternInfo__anona003e8100111::PatternInfo317 explicit PatternInfo(const ImageInfo& img, int textureProperty)
318 // We only care about Repeat and Stretch, since SVG doesn't allow control
319 // over Nearest/Linear interpolation.
320 : TextureProperty(textureProperty & (vtkBrush::Repeat | vtkBrush::Stretch))
321 , ImageSize(img.Size)
322 , ImageId(img.Id)
323 {
324 }
325
PatternInfo__anona003e8100111::PatternInfo326 PatternInfo(PatternInfo&& o) noexcept
327 : TextureProperty(o.TextureProperty)
328 , ImageSize(o.ImageSize)
329 , ImageId(std::move(o.ImageId))
330 , PatternId(std::move(o.PatternId))
331 {
332 }
333
operator <__anona003e8100111::PatternInfo334 bool operator<(const PatternInfo& other) const
335 {
336 if (this->TextureProperty < other.TextureProperty)
337 {
338 return true;
339 }
340 else if (this->TextureProperty > other.TextureProperty)
341 {
342 return false;
343 }
344
345 if (this->ImageId < other.ImageId)
346 {
347 return true;
348 }
349
350 return false;
351 }
352
353 int TextureProperty;
354 std::array<int, 2> ImageSize;
355 std::string ImageId;
356 std::string PatternId;
357
358 private:
359 PatternInfo(const PatternInfo&) = delete;
360 void operator=(const PatternInfo&) = delete;
361 };
362
363 struct ClipRectInfo
364 {
ClipRectInfo__anona003e8100111::ClipRectInfo365 explicit ClipRectInfo(const std::array<int, 4>& rect)
366 : Rect(rect)
367 {
368 }
369
ClipRectInfo__anona003e8100111::ClipRectInfo370 ClipRectInfo(ClipRectInfo&& o) noexcept
371 : Rect(o.Rect)
372 , Id(std::move(o.Id))
373 {
374 }
375
operator <__anona003e8100111::ClipRectInfo376 bool operator<(const ClipRectInfo& other) const
377 {
378 for (size_t i = 0; i < this->Rect.size(); ++i)
379 {
380 if (this->Rect[i] < other.Rect[i])
381 {
382 return true;
383 }
384 else if (this->Rect[i] > other.Rect[i])
385 {
386 return false;
387 }
388 }
389
390 return false;
391 }
392
393 std::array<int, 4> Rect; // x, y, w, h
394 std::string Id;
395
396 private:
397 ClipRectInfo(const ClipRectInfo&) = delete;
398 void operator=(const ClipRectInfo&) = delete;
399 };
400
401 // SVG's y axis is inverted compared to VTK's:
402 struct YConverter
403 {
404 float Height;
405
YConverter__anona003e8100111::YConverter406 explicit YConverter(float height)
407 : Height(height)
408 {
409 }
410
operator ()__anona003e8100111::YConverter411 float operator()(float inY) { return this->Height - inY; }
412 };
413
414 } // end anon namespace
415
416 // Need to be able to use vtkColor4f in a std::map. Must be outside of the anon
417 // namespace to work.
operator <(const vtkColor4f & a,const vtkColor4f & b)418 static bool operator<(const vtkColor4f& a, const vtkColor4f& b)
419 {
420 for (int i = 0; i < 4; ++i)
421 {
422 if (a[i] < b[i])
423 {
424 return true;
425 }
426 else if (a[i] > b[i])
427 {
428 return false;
429 }
430 }
431
432 return false;
433 }
434
435 struct vtkSVGContextDevice2D::Details
436 {
437 using FontMapType = std::map<FontKey, FontInfo*>;
438 using ImageSetType = std::set<ImageInfo>;
439 using PatternSetType = std::set<PatternInfo>;
440 using ClipRectSetType = std::set<ClipRectInfo>;
441
442 FontMapType FontMap;
443 ImageSetType ImageSet;
444 PatternSetType PatternSet;
445 ClipRectSetType ClipRectSet;
446
~DetailsvtkSVGContextDevice2D::Details447 ~Details() { this->FreeFontMap(); }
448
FreeFontMapvtkSVGContextDevice2D::Details449 void FreeFontMap()
450 {
451 for (auto& it : this->FontMap)
452 {
453 delete it.second;
454 }
455 this->FontMap.clear();
456 }
457
GetImageInfovtkSVGContextDevice2D::Details458 const ImageInfo& GetImageInfo(vtkImageData* img)
459 {
460 ImageInfo newInfo(img);
461
462 auto insertResult = this->ImageSet.insert(std::move(newInfo));
463 const ImageInfo& info = *insertResult.first;
464 if (insertResult.second)
465 {
466 std::ostringstream id;
467 id << "vtkEmbeddedImage" << this->ImageSet.size();
468 // This is safe; setting the id won't change the sort order in the set.
469 const_cast<ImageInfo&>(info).Id = id.str();
470 }
471
472 return info;
473 }
474
GetPatternInfovtkSVGContextDevice2D::Details475 const PatternInfo& GetPatternInfo(vtkImageData* texture, int textureProperty)
476 {
477 const ImageInfo& imageInfo = this->GetImageInfo(texture);
478 PatternInfo newInfo(imageInfo, textureProperty);
479
480 auto insertResult = this->PatternSet.insert(std::move(newInfo));
481 const PatternInfo& info = *insertResult.first;
482 if (insertResult.second)
483 {
484 std::ostringstream id;
485 id << "vtkPattern" << this->PatternSet.size();
486 // This is safe; setting the id won't change the sort order in the set.
487 const_cast<PatternInfo&>(info).PatternId = id.str();
488 }
489
490 return info;
491 }
492
GetClipRectInfovtkSVGContextDevice2D::Details493 const ClipRectInfo& GetClipRectInfo(const std::array<int, 4>& rect)
494 {
495 ClipRectInfo newInfo(rect);
496
497 auto insertResult = this->ClipRectSet.insert(std::move(newInfo));
498 const ClipRectInfo& info = *insertResult.first;
499 if (insertResult.second)
500 {
501 std::ostringstream id;
502 id << "vtkClipRect" << this->ClipRectSet.size();
503 // This is safe; setting the id won't change the sort order in the set.
504 const_cast<ClipRectInfo&>(info).Id = id.str();
505 }
506
507 return info;
508 }
509
GetFontInfovtkSVGContextDevice2D::Details510 FontInfo& GetFontInfo(vtkTextProperty* tprop)
511 {
512 FontKey key(tprop);
513 FontMapType::const_iterator it = this->FontMap.find(key);
514 if (it == this->FontMap.end())
515 {
516 std::ostringstream tmp;
517 tmp << "vtkExportedFont-" << std::hex << this << "_" << std::dec << this->FontMap.size()
518 << "_" << tprop->GetFontFamilyAsString();
519 std::string id = tmp.str();
520 auto result = this->FontMap.insert(std::make_pair(key, new FontInfo(id)));
521 it = result.first;
522 }
523
524 return *it->second;
525 }
526 };
527
528 //------------------------------------------------------------------------------
529 vtkStandardNewMacro(vtkSVGContextDevice2D);
530 vtkCxxSetObjectMacro(vtkSVGContextDevice2D, Viewport, vtkViewport);
531
532 //------------------------------------------------------------------------------
PrintSelf(std::ostream & os,vtkIndent indent)533 void vtkSVGContextDevice2D::PrintSelf(std::ostream& os, vtkIndent indent)
534 {
535 this->Superclass::PrintSelf(os, indent);
536 }
537
538 //------------------------------------------------------------------------------
SetSVGContext(vtkXMLDataElement * context,vtkXMLDataElement * defs)539 void vtkSVGContextDevice2D::SetSVGContext(vtkXMLDataElement* context, vtkXMLDataElement* defs)
540 {
541 this->ContextNode = context;
542 this->ActiveNode = context;
543 this->DefinitionNode = defs;
544 }
545
546 //------------------------------------------------------------------------------
GenerateDefinitions()547 void vtkSVGContextDevice2D::GenerateDefinitions()
548 {
549 if (this->EmbedFonts)
550 {
551 this->WriteFonts();
552 }
553
554 this->WriteImages();
555 this->WritePatterns(); // Must come after images
556 this->WriteClipRects();
557 }
558
559 //------------------------------------------------------------------------------
Begin(vtkViewport * vp)560 void vtkSVGContextDevice2D::Begin(vtkViewport* vp)
561 {
562 // Recreate the pen/brush to reset state:
563 this->Pen->Delete();
564 this->Pen = vtkPen::New();
565 this->Brush->Delete();
566 this->Brush = vtkBrush::New();
567
568 this->SetViewport(vp);
569 this->CanvasHeight = static_cast<float>(vp->GetVTKWindow()->GetSize()[1]);
570 std::fill(this->ClipRect.begin(), this->ClipRect.end(), 0);
571 std::fill(this->ActiveNodeClipRect.begin(), this->ActiveNodeClipRect.end(), 0);
572 std::fill(this->ActiveNodeTransform.begin(), this->ActiveNodeTransform.end(), 0.);
573 this->ActiveNodeTransform[0] = 1.;
574 this->ActiveNodeTransform[4] = 1.;
575 this->ActiveNodeTransform[8] = 1.;
576 this->Matrix->Identity();
577 }
578
579 //------------------------------------------------------------------------------
End()580 void vtkSVGContextDevice2D::End()
581 {
582 this->SetViewport(nullptr);
583 }
584
585 //------------------------------------------------------------------------------
DrawPoly(float * points,int n,unsigned char * colors,int nc_comps)586 void vtkSVGContextDevice2D::DrawPoly(float* points, int n, unsigned char* colors, int nc_comps)
587 {
588 if (!colors)
589 {
590 vtkNew<vtkXMLDataElement> polyLine;
591 polyLine->SetName("polyline");
592 this->ActiveNode->AddNestedElement(polyLine);
593 this->ApplyPenStateToNode(polyLine);
594
595 YConverter y(this->CanvasHeight);
596
597 std::ostringstream verts;
598 verts << "\n";
599 for (int i = 0; i < n; ++i)
600 {
601 verts << points[i * 2] << "," << y(points[i * 2 + 1]) << "\n";
602 }
603 polyLine->SetAttribute("points", verts.str().c_str());
604 }
605 else
606 {
607 this->PushGraphicsState();
608 this->ApplyPenStippleToNode(this->ActiveNode);
609 this->ApplyPenWidthToNode(this->ActiveNode);
610 bool useAlpha = nc_comps == 4;
611 if (!useAlpha)
612 {
613 this->ApplyPenOpacityToNode(this->ActiveNode);
614 }
615
616 for (int i = 0; i < n - 1; ++i)
617 {
618 const vtkVector2f p1(points + i * 2);
619 const vtkColor4ub c1(colors + i * nc_comps);
620 const vtkVector2f p2(points + (i + 1) * 2);
621 const vtkColor4ub c2(colors + (i + 1) * nc_comps);
622
623 this->DrawLineGradient(p1, c1, p2, c2, useAlpha);
624 }
625
626 this->PopGraphicsState();
627 }
628 }
629
630 //------------------------------------------------------------------------------
DrawLines(float * points,int n,unsigned char * colors,int nc_comps)631 void vtkSVGContextDevice2D::DrawLines(float* points, int n, unsigned char* colors, int nc_comps)
632 {
633 if (!colors)
634 {
635 // Use path instead of lines for a more efficient/compact representation:
636 vtkNew<vtkXMLDataElement> path;
637 path->SetName("path");
638 this->ActiveNode->AddNestedElement(path);
639 this->ApplyPenStateToNode(path);
640
641 YConverter y(this->CanvasHeight);
642
643 std::ostringstream d;
644 d << "\n";
645 int numLines = n / 2;
646 for (int i = 0; i < numLines; ++i)
647 {
648 const float* p1 = points + i * 4;
649 const float* p2 = points + i * 4 + 2;
650 d << "M" << p1[0] << "," << y(p1[1]) << "L" << p2[0] << "," << y(p2[1]) << "\n";
651 }
652 path->SetAttribute("d", d.str().c_str());
653 }
654 else
655 {
656 this->PushGraphicsState();
657 this->ApplyPenStippleToNode(this->ActiveNode);
658 this->ApplyPenWidthToNode(this->ActiveNode);
659 bool useAlpha = nc_comps == 4;
660 if (!useAlpha)
661 {
662 this->ApplyPenOpacityToNode(this->ActiveNode);
663 }
664
665 const int numLines = n / 2;
666 for (int i = 0; i < numLines; ++i)
667 {
668 const vtkVector2f p1(points + i * 4);
669 const vtkVector2f p2(points + i * 4 + 2);
670 const vtkColor4ub c1(colors + i * 2 * nc_comps);
671 const vtkColor4ub c2(colors + (i * 2 + 1) * nc_comps);
672
673 this->DrawLineGradient(p1, c1, p2, c2, useAlpha);
674 }
675
676 this->PopGraphicsState();
677 }
678 }
679
680 //------------------------------------------------------------------------------
DrawPoints(float * points,int n,unsigned char * colors,int nc_comps)681 void vtkSVGContextDevice2D::DrawPoints(float* points, int n, unsigned char* colors, int nc_comps)
682 {
683 if (!colors)
684 {
685 // Use path instead of rects for a more efficient/compact representation.
686 vtkNew<vtkXMLDataElement> path;
687 path->SetName("path");
688 this->ActiveNode->AddNestedElement(path);
689
690 this->ApplyPenAsFillColorToNode(path);
691 this->ApplyPenAsFillOpacityToNode(path);
692
693 YConverter y(this->CanvasHeight);
694
695 float deltaX;
696 float deltaY;
697 this->GetScaledPenWidth(deltaX, deltaY);
698 deltaX *= 0.5f;
699 deltaY *= 0.5f;
700
701 std::ostringstream d;
702 d << "\n";
703 for (int i = 0; i < n; ++i)
704 {
705 const float* p = points + i * 2;
706 d << "M" << p[0] - deltaX << "," << y(p[1] - deltaY)
707 << "\n"
708 "L"
709 << p[0] + deltaX << "," << y(p[1] - deltaY) << "\n"
710 << p[0] + deltaX << "," << y(p[1] + deltaY) << "\n"
711 << p[0] - deltaX << "," << y(p[1] + deltaY) << "\nz\n";
712 }
713 path->SetAttribute("d", d.str().c_str());
714 }
715 else
716 {
717 const float width = this->GetScaledPenWidth();
718 const float halfWidth = width * 0.5f;
719 const bool useAlpha = nc_comps == 4;
720
721 if (!useAlpha)
722 {
723 this->PushGraphicsState();
724 this->ApplyPenAsFillOpacityToNode(this->ActiveNode);
725 }
726
727 YConverter y(this->CanvasHeight);
728
729 for (int i = 0; i < n; ++i)
730 {
731 const float* p = points + i * 2;
732 const unsigned char* c = colors + i * nc_comps;
733
734 vtkNew<vtkXMLDataElement> point;
735 this->ActiveNode->AddNestedElement(point);
736
737 point->SetName("rect");
738 point->SetFloatAttribute("x", p[0] - halfWidth);
739 point->SetFloatAttribute("y", y(p[1] - halfWidth));
740 point->SetFloatAttribute("width", width);
741 point->SetFloatAttribute("height", width);
742 point->SetAttribute("fill", ColorToString(c).c_str());
743 if (useAlpha && c[3] != 255)
744 {
745 point->SetFloatAttribute("fill-opacity", c[3] / 255.f);
746 }
747 }
748
749 if (!useAlpha)
750 {
751 this->PopGraphicsState();
752 }
753 }
754 }
755
756 //------------------------------------------------------------------------------
DrawPointSprites(vtkImageData * spriteIn,float * points,int n,unsigned char * colors,int nc_comps)757 void vtkSVGContextDevice2D::DrawPointSprites(
758 vtkImageData* spriteIn, float* points, int n, unsigned char* colors, int nc_comps)
759 {
760 if (nc_comps != 3 && nc_comps != 4)
761 {
762 vtkErrorMacro("Unsupported number of components: " << nc_comps);
763 return;
764 }
765
766 vtkImageData* rgba = this->PreparePointSprite(spriteIn);
767 if (!rgba)
768 {
769 vtkErrorMacro("Unsupported point sprite format.");
770 return;
771 }
772
773 assert(rgba->GetScalarType() == VTK_UNSIGNED_CHAR);
774 assert(rgba->GetNumberOfScalarComponents() == 4);
775
776 int dims[3];
777 rgba->GetDimensions(dims);
778 vtkIdType numPoints = rgba->GetNumberOfPoints();
779 vtkUnsignedCharArray* colorArray =
780 vtkArrayDownCast<vtkUnsignedCharArray>(rgba->GetPointData()->GetScalars());
781 const float sizeFactor =
782 this->GetScaledPenWidth() / static_cast<float>(std::max(dims[0], dims[1]));
783 const float spriteWidth = dims[0] * sizeFactor;
784 const float spriteHeight = dims[1] * sizeFactor;
785 const float halfWidth = spriteWidth * 0.5f;
786 const float halfHeight = spriteHeight * 0.5f;
787 const float brushAlpha = this->Brush->GetOpacity() / 255.f;
788 YConverter y(this->CanvasHeight);
789
790 typedef std::map<vtkColor4f, std::string> SpriteMap;
791 SpriteMap spriteMap;
792
793 for (int i = 0; i < n; ++i)
794 {
795 const float* p = points + 2 * i;
796
797 vtkColor4f color;
798 if (colors)
799 {
800 unsigned char* c = colors + nc_comps * i;
801 switch (nc_comps)
802 {
803 case 3:
804 color.Set(c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, brushAlpha);
805 break;
806
807 case 4:
808 color.Set(c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, c[3] / 255.f);
809 break;
810
811 default:
812 vtkErrorMacro("Unsupported number of color components: " << nc_comps);
813 continue;
814 }
815 }
816 else
817 {
818 vtkColor4ub penColor = this->Pen->GetColorObject();
819 color = vtkColor4f(
820 penColor[0] / 255.f, penColor[1] / 255.f, penColor[2] / 255.f, penColor[3] / 255.f);
821 }
822
823 std::string sprite;
824 SpriteMap::iterator it = spriteMap.find(color);
825 if (it != spriteMap.end())
826 {
827 sprite = it->second;
828 }
829 else
830 {
831 vtkNew<vtkUnsignedCharArray> spriteColor;
832 spriteColor->SetNumberOfComponents(4);
833 spriteColor->SetNumberOfTuples(numPoints);
834
835 for (vtkIdType t = 0; t < numPoints; ++t)
836 {
837 // This is what the OpenGL implementation does:
838 for (int c = 0; c < 4; ++c)
839 {
840 spriteColor->SetTypedComponent(
841 t, c, static_cast<unsigned char>(colorArray->GetTypedComponent(t, c) * color[c] + .5f));
842 }
843 }
844
845 vtkNew<vtkImageData> spriteImage;
846 spriteImage->ShallowCopy(rgba);
847 spriteImage->GetPointData()->SetScalars(spriteColor);
848
849 const ImageInfo& info = this->Impl->GetImageInfo(spriteImage);
850 sprite = info.Id;
851
852 spriteMap.insert(std::make_pair(color, sprite));
853 }
854
855 const float xScale = spriteWidth / dims[0];
856 const float yScale = spriteHeight / dims[1];
857
858 // Offset the coordinates to center the sprite on the anchor:
859 const float anchorX = p[0] - halfWidth;
860 const float anchorY = y(p[1] - halfHeight);
861
862 // Construct a matrix representing the following transformation:
863 //
864 // [X] = [T3] [T2] [S] [T1]
865 //
866 // [X] = final transform
867 // [T1] = translate(-pos.X, -pos.Y); Move to origin to prepare for scaling.
868 // [S] = scale(xScale, yScale); Resize the image to match the input rect.
869 // [T2] = translate(0, -pos.H); Anchor at bottom corner instead of top
870 // [T3] = translate(pos.X, pos.Y); Move back to anchor point
871 std::ostringstream xform;
872 xform << "matrix(" << xScale << ",0,0," << yScale << "," << anchorX - xScale * anchorX << ","
873 << anchorY - (yScale * anchorY + spriteHeight) << ")";
874
875 vtkNew<vtkXMLDataElement> use;
876 this->ActiveNode->AddNestedElement(use);
877 use->SetName("use");
878 use->SetFloatAttribute("x", anchorX);
879 use->SetFloatAttribute("y", anchorY); // YConverter already applied
880 use->SetFloatAttribute("width", spriteWidth);
881 use->SetFloatAttribute("height", spriteHeight);
882 use->SetAttribute("transform", xform.str().c_str());
883 use->SetAttribute("xlink:href", (std::string("#") + sprite).c_str());
884 }
885
886 rgba->UnRegister(this);
887 }
888
889 //------------------------------------------------------------------------------
DrawMarkers(int shape,bool highlight,float * points,int n,unsigned char * colors,int nc_comps)890 void vtkSVGContextDevice2D::DrawMarkers(
891 int shape, bool highlight, float* points, int n, unsigned char* colors, int nc_comps)
892 {
893 bool fill = false;
894 bool stroke = false;
895 float strokeWidth = 0.f;
896
897 std::string markerId;
898 switch (shape)
899 {
900 case VTK_MARKER_CROSS:
901 markerId = this->AddCrossSymbol(highlight);
902 stroke = true;
903 strokeWidth = highlight ? 1.5f : 1.f;
904 break;
905
906 default:
907 // default is here for consistency with old impl -- defaults to plus for
908 // unrecognized shapes.
909 VTK_FALLTHROUGH;
910 case VTK_MARKER_PLUS:
911 markerId = this->AddPlusSymbol(highlight);
912 stroke = true;
913 strokeWidth = highlight ? 1.5f : 1.f;
914 break;
915
916 case VTK_MARKER_SQUARE:
917 markerId = this->AddSquareSymbol(highlight);
918 fill = true;
919 break;
920
921 case VTK_MARKER_CIRCLE:
922 markerId = this->AddCircleSymbol(highlight);
923 fill = true;
924 break;
925
926 case VTK_MARKER_DIAMOND:
927 markerId = this->AddDiamondSymbol(highlight);
928 fill = true;
929 break;
930 }
931
932 const float width = this->GetScaledPenWidth();
933 const float halfWidth = width * 0.5f;
934 YConverter y(this->CanvasHeight);
935
936 // Adjust stroke width for scaling. Symbols are defined in a unit square.
937 strokeWidth /= width;
938
939 markerId = std::string("#") + markerId;
940
941 if (!colors)
942 {
943 this->PushGraphicsState();
944 if (stroke)
945 {
946 this->ApplyPenColorToNode(this->ActiveNode);
947 this->ApplyPenOpacityToNode(this->ActiveNode);
948 this->ApplyPenStippleToNode(this->ActiveNode);
949 this->ActiveNode->SetFloatAttribute("stroke-width", strokeWidth);
950 }
951 if (fill)
952 {
953 this->ApplyPenAsFillColorToNode(this->ActiveNode);
954 this->ApplyPenAsFillOpacityToNode(this->ActiveNode);
955 }
956
957 for (int i = 0; i < n; ++i)
958 {
959 const float* p = points + i * 2;
960
961 vtkNew<vtkXMLDataElement> node;
962 this->ActiveNode->AddNestedElement(node);
963 node->SetName("use");
964 node->SetFloatAttribute("x", p[0] - halfWidth);
965 node->SetFloatAttribute("y", y(p[1]) - halfWidth);
966 node->SetFloatAttribute("width", width);
967 node->SetFloatAttribute("height", width);
968 node->SetAttribute("xlink:href", markerId.c_str());
969 }
970
971 this->PopGraphicsState();
972 }
973 else
974 {
975 const bool useAlpha = nc_comps == 4;
976
977 if (!useAlpha)
978 {
979 this->PushGraphicsState();
980 if (stroke)
981 {
982 this->ApplyPenOpacityToNode(this->ActiveNode);
983 }
984 if (fill)
985 {
986 this->ApplyPenAsFillOpacityToNode(this->ActiveNode);
987 }
988 }
989
990 for (int i = 0; i < n; ++i)
991 {
992 const float* p = points + i * 2;
993 const unsigned char* c = colors + i * nc_comps;
994 const std::string colStr = ColorToString(c);
995
996 vtkNew<vtkXMLDataElement> node;
997 this->ActiveNode->AddNestedElement(node);
998 node->SetName("use");
999 node->SetFloatAttribute("x", p[0] - halfWidth);
1000 node->SetFloatAttribute("y", y(p[1]) - halfWidth);
1001 node->SetFloatAttribute("width", width);
1002 node->SetFloatAttribute("height", width);
1003 node->SetAttribute("xlink:href", markerId.c_str());
1004 if (stroke)
1005 {
1006 node->SetAttribute("stroke", colStr.c_str());
1007 node->SetFloatAttribute("stroke-width", strokeWidth);
1008 }
1009 if (fill)
1010 {
1011 node->SetAttribute("fill", colStr.c_str());
1012 }
1013 if (useAlpha && c[3] != 255)
1014 {
1015 const float a = c[3] / 255.f;
1016 if (stroke)
1017 {
1018 node->SetFloatAttribute("stroke-opacity", a);
1019 }
1020 if (fill)
1021 {
1022 node->SetFloatAttribute("fill-opacity", a);
1023 }
1024 }
1025 }
1026
1027 if (!useAlpha)
1028 {
1029 this->PopGraphicsState();
1030 }
1031 }
1032 }
1033
1034 //------------------------------------------------------------------------------
DrawQuad(float * points,int n)1035 void vtkSVGContextDevice2D::DrawQuad(float* points, int n)
1036 {
1037 this->DrawPolygon(points, n);
1038 }
1039
1040 //------------------------------------------------------------------------------
DrawQuadStrip(float * points,int n)1041 void vtkSVGContextDevice2D::DrawQuadStrip(float* points, int n)
1042 {
1043 if (n < 4 || n % 2 != 0)
1044 { // Must be at least one quad, and a whole number of quads.
1045 return;
1046 }
1047
1048 // Combine all into a path that traces the exterior (Even verts on one side,
1049 // odd verts on the other):
1050 vtkNew<vtkXMLDataElement> path;
1051 path->SetName("path");
1052 this->ActiveNode->AddNestedElement(path);
1053
1054 this->ApplyBrushStateToNode(path);
1055
1056 YConverter y(this->CanvasHeight);
1057 std::ostringstream d;
1058 d << "\nM" << points[0] << "," << y(points[1]) << "\nL\n";
1059 for (int i = 2; i < n; i += 2)
1060 {
1061 d << points[i * 2] << "," << y(points[i * 2 + 1]) << "\n";
1062 }
1063
1064 for (int i = n - 1; i >= 0; i -= 2)
1065 {
1066 d << points[i * 2] << "," << y(points[i * 2 + 1]) << "\n";
1067 }
1068 d << "z";
1069
1070 path->SetAttribute("d", d.str().c_str());
1071 }
1072
1073 //------------------------------------------------------------------------------
DrawPolygon(float * points,int n)1074 void vtkSVGContextDevice2D::DrawPolygon(float* points, int n)
1075 {
1076 vtkNew<vtkXMLDataElement> path;
1077 path->SetName("path");
1078 this->ActiveNode->AddNestedElement(path);
1079
1080 this->ApplyBrushStateToNode(path);
1081
1082 YConverter y(this->CanvasHeight);
1083 std::ostringstream d;
1084 d << "\nM" << points[0] << "," << y(points[1]) << "\nL";
1085 for (int i = 1; i < n; ++i)
1086 {
1087 d << points[i * 2] << "," << y(points[i * 2 + 1]) << "\n";
1088 }
1089 d << "z";
1090
1091 path->SetAttribute("d", d.str().c_str());
1092 }
1093
1094 //------------------------------------------------------------------------------
DrawColoredPolygon(float * points,int numPoints,unsigned char * colors,int nc_comps)1095 void vtkSVGContextDevice2D::DrawColoredPolygon(
1096 float* points, int numPoints, unsigned char* colors, int nc_comps)
1097 {
1098 assert(numPoints > 0);
1099 assert(nc_comps >= 3 && nc_comps <= 4);
1100 assert(points != nullptr);
1101
1102 // Just use the standard draw method if there is a texture or colors are not
1103 // specified:
1104 if (this->Brush->GetTexture() != nullptr || nc_comps == 0)
1105 {
1106 this->DrawPolygon(points, numPoints);
1107 return;
1108 }
1109
1110 // If all of the points have the same color, use a more compact method to
1111 // draw the poly:
1112 bool sameColor = true;
1113 for (int i = 1; i < numPoints && sameColor; ++i)
1114 {
1115 sameColor = std::equal(colors, colors + nc_comps, colors + (i * nc_comps));
1116 }
1117 if (sameColor)
1118 {
1119 const vtkColor4ub oldBrush = this->Brush->GetColorObject();
1120 switch (nc_comps)
1121 {
1122 case 4:
1123 this->Brush->SetOpacity(colors[3]);
1124 VTK_FALLTHROUGH;
1125 case 3:
1126 this->Brush->SetColor(colors);
1127 break;
1128
1129 default:
1130 vtkWarningMacro("Unsupported number of color components: " << nc_comps);
1131 return;
1132 }
1133
1134 this->DrawPolygon(points, numPoints);
1135 this->Brush->SetColor(oldBrush);
1136 return;
1137 }
1138
1139 const bool useAlpha = nc_comps == 4;
1140 const vtkVector2f p0(points);
1141 const vtkColor4ub c0(colors);
1142
1143 // We may have 3 or 4 components, so initialize these with a sane alpha value:
1144 vtkColor4ub c1{ 0, 0, 0, 255 };
1145 vtkColor4ub c2{ 0, 0, 0, 255 };
1146
1147 for (int i = 1; i < numPoints - 1; ++i)
1148 {
1149 const vtkVector2f p1(points + 2 * i);
1150 const vtkVector2f p2(points + 2 * (i + 1));
1151 std::copy_n(colors + nc_comps * i, nc_comps, c1.GetData());
1152 std::copy_n(colors + nc_comps * (i + 1), nc_comps, c2.GetData());
1153
1154 this->DrawTriangleGradient(p0, c0, p1, c1, p2, c2, useAlpha);
1155 }
1156 }
1157
1158 //------------------------------------------------------------------------------
DrawEllipseWedge(float cx,float cy,float outRx,float outRy,float inRx,float inRy,float startAngle,float stopAngle)1159 void vtkSVGContextDevice2D::DrawEllipseWedge(float cx, float cy, float outRx, float outRy,
1160 float inRx, float inRy, float startAngle, float stopAngle)
1161 {
1162 if (stopAngle < startAngle)
1163 {
1164 std::swap(startAngle, stopAngle);
1165 }
1166
1167 const float arcLength = stopAngle - startAngle;
1168 const bool isArc = arcLength < 359.99f;
1169 const bool isFilled = inRx == 0.f && inRy == 0.f;
1170 const bool isCircle = inRx == inRy && outRx == outRy;
1171 const int largeArcFlag = (arcLength >= 180.f) ? 1 : 0;
1172 const int sweepFlag = 0;
1173 YConverter y(this->CanvasHeight);
1174
1175 if (!isArc)
1176 {
1177 if (isFilled)
1178 {
1179 // Easy case: full ellipse/circle:
1180 if (isCircle)
1181 {
1182 vtkNew<vtkXMLDataElement> circle;
1183 this->ActiveNode->AddNestedElement(circle);
1184 this->ApplyBrushStateToNode(circle);
1185 circle->SetName("circle");
1186 circle->SetFloatAttribute("cx", cx);
1187 circle->SetFloatAttribute("cy", y(cy));
1188 circle->SetFloatAttribute("r", outRx);
1189 }
1190 else // !isCircle
1191 {
1192 vtkNew<vtkXMLDataElement> ellipse;
1193 this->ActiveNode->AddNestedElement(ellipse);
1194 this->ApplyBrushStateToNode(ellipse);
1195 ellipse->SetName("ellipse");
1196 ellipse->SetFloatAttribute("cx", cx);
1197 ellipse->SetFloatAttribute("cy", y(cy));
1198 ellipse->SetFloatAttribute("rx", outRx);
1199 ellipse->SetFloatAttribute("ry", outRy);
1200 }
1201 }
1202 else // !isFilled
1203 {
1204 vtkNew<vtkXMLDataElement> path;
1205 this->ActiveNode->AddNestedElement(path);
1206 this->ApplyBrushStateToNode(path);
1207 path->SetName("path");
1208 path->SetAttribute("fill-rule", "evenodd");
1209
1210 std::ostringstream d;
1211
1212 // Outer ellipse:
1213 EllipseHelper helper(cx, cy, outRx, outRy);
1214 helper.UpdateDegrees(0.f);
1215 d << "M" << helper.X << "," << y(helper.Y) << "\n";
1216 helper.UpdateDegrees(180.f);
1217 d << "A" << outRx << "," << outRy << " 0 1 1 " << helper.X << "," << y(helper.Y) << "\n";
1218 helper.UpdateDegrees(360.f);
1219 d << "A" << outRx << "," << outRy << " 0 1 1 " << helper.X << "," << y(helper.Y) << "\nz\n";
1220
1221 // Inner ellipse:
1222 helper = EllipseHelper(cx, cy, inRx, inRy);
1223 helper.UpdateDegrees(0.f);
1224 d << "M" << helper.X << "," << y(helper.Y) << "\n";
1225 helper.UpdateDegrees(180.f);
1226 d << "A" << inRx << "," << inRy << " 0 1 1 " << helper.X << "," << y(helper.Y) << "\n";
1227 helper.UpdateDegrees(360.f);
1228 d << "A" << inRx << "," << inRy << " 0 1 1 " << helper.X << "," << y(helper.Y) << "\nz\n";
1229
1230 path->SetAttribute("d", d.str().c_str());
1231 }
1232 }
1233 else // isArc
1234 {
1235 if (isFilled)
1236 {
1237 vtkNew<vtkXMLDataElement> path;
1238 this->ActiveNode->AddNestedElement(path);
1239 this->ApplyBrushStateToNode(path);
1240 path->SetName("path");
1241
1242 std::ostringstream d;
1243 EllipseHelper helper(cx, cy, outRx, outRy);
1244
1245 d << "M" << cx << "," << y(cy) << "\n";
1246 helper.UpdateDegrees(startAngle);
1247 d << "L" << helper.X << "," << y(helper.Y) << "\n";
1248 helper.UpdateDegrees(stopAngle);
1249 d << "A" << outRx << "," << outRy << " 0 " << largeArcFlag << " " << sweepFlag << " "
1250 << helper.X << "," << y(helper.Y) << "\nz\n";
1251 path->SetAttribute("d", d.str().c_str());
1252 }
1253 else // !isFilled
1254 {
1255 vtkNew<vtkXMLDataElement> path;
1256 this->ActiveNode->AddNestedElement(path);
1257 this->ApplyBrushStateToNode(path);
1258 path->SetName("path");
1259 path->SetAttribute("fill-rule", "evenodd");
1260
1261 std::ostringstream d;
1262
1263 // Outer ellipse
1264 EllipseHelper helper(cx, cy, outRx, outRy);
1265 helper.UpdateDegrees(startAngle);
1266 d << "M" << helper.X << "," << y(helper.Y) << "\n";
1267 helper.UpdateDegrees(stopAngle);
1268 d << "A" << outRx << "," << outRy << " 0 " << largeArcFlag << " " << sweepFlag << " "
1269 << helper.X << "," << y(helper.Y) << "\n";
1270 path->SetAttribute("d", d.str().c_str());
1271
1272 // Inner ellipse
1273 const int innerSweepFlag = 1;
1274 helper = EllipseHelper(cx, cy, inRx, inRy);
1275 helper.UpdateDegrees(stopAngle);
1276 d << "L" << helper.X << "," << y(helper.Y) << "\n";
1277 helper.UpdateDegrees(startAngle);
1278 d << "A" << inRx << "," << inRy << " 0 " << largeArcFlag << " " << innerSweepFlag << " "
1279 << helper.X << "," << y(helper.Y) << "\nz\n";
1280 path->SetAttribute("d", d.str().c_str());
1281 }
1282 }
1283 }
1284
1285 //------------------------------------------------------------------------------
DrawEllipticArc(float cx,float cy,float rX,float rY,float startAngle,float stopAngle)1286 void vtkSVGContextDevice2D::DrawEllipticArc(
1287 float cx, float cy, float rX, float rY, float startAngle, float stopAngle)
1288 {
1289 if (stopAngle < startAngle)
1290 {
1291 std::swap(startAngle, stopAngle);
1292 }
1293
1294 const float arcLength = stopAngle - startAngle;
1295 const bool isArc = arcLength < 360.f;
1296 const bool isCircle = rX == rY;
1297 const int largeArcFlag = (arcLength >= 180.f) ? 1 : 0;
1298 const int sweepFlag = 0;
1299 YConverter y(this->CanvasHeight);
1300
1301 if (!isArc)
1302 {
1303 // Easy case: full ellipse/circle:
1304 if (isCircle)
1305 {
1306 vtkNew<vtkXMLDataElement> circle;
1307 this->ActiveNode->AddNestedElement(circle);
1308 this->ApplyPenStateToNode(circle);
1309 this->ApplyBrushStateToNode(circle);
1310 circle->SetName("circle");
1311 circle->SetFloatAttribute("cx", cx);
1312 circle->SetFloatAttribute("cy", y(cy));
1313 circle->SetFloatAttribute("r", rX);
1314 }
1315 else // !isCircle
1316 {
1317 vtkNew<vtkXMLDataElement> ellipse;
1318 this->ActiveNode->AddNestedElement(ellipse);
1319 this->ApplyPenStateToNode(ellipse);
1320 this->ApplyBrushStateToNode(ellipse);
1321 ellipse->SetName("ellipse");
1322 ellipse->SetFloatAttribute("cx", cx);
1323 ellipse->SetFloatAttribute("cy", y(cy));
1324 ellipse->SetFloatAttribute("rx", rX);
1325 ellipse->SetFloatAttribute("ry", rY);
1326 }
1327 }
1328 else // isArc
1329 {
1330 vtkNew<vtkXMLDataElement> path;
1331 this->ActiveNode->AddNestedElement(path);
1332 this->ApplyPenStateToNode(path);
1333 this->ApplyBrushStateToNode(path);
1334 path->SetName("path");
1335
1336 std::ostringstream d;
1337 EllipseHelper helper(cx, cy, rX, rY);
1338 helper.UpdateDegrees(startAngle);
1339 d << "M" << helper.X << "," << y(helper.Y) << "\n";
1340 helper.UpdateDegrees(stopAngle);
1341 d << "A" << rX << "," << rY << " 0 " << largeArcFlag << " " << sweepFlag << " " << helper.X
1342 << "," << y(helper.Y) << "\n";
1343 path->SetAttribute("d", d.str().c_str());
1344 }
1345 }
1346
1347 //------------------------------------------------------------------------------
DrawString(float * point,const vtkStdString & string)1348 void vtkSVGContextDevice2D::DrawString(float* point, const vtkStdString& string)
1349 {
1350 // FIXME(#18327): Migrate to processing here rather than working on
1351 // `vtkUnicodeString`.
1352 this->DrawString(point, vtkUnicodeString::from_utf8(string));
1353 }
1354
1355 //------------------------------------------------------------------------------
ComputeStringBounds(const vtkStdString & string,float bounds[4])1356 void vtkSVGContextDevice2D::ComputeStringBounds(const vtkStdString& string, float bounds[4])
1357 {
1358 // FIXME(#18327): Migrate to processing here rather than working on
1359 // `vtkUnicodeString`.
1360 this->ComputeStringBounds(vtkUnicodeString::from_utf8(string), bounds);
1361 }
1362
1363 //------------------------------------------------------------------------------
DrawString(float * point,const vtkUnicodeString & string)1364 void vtkSVGContextDevice2D::DrawString(float* point, const vtkUnicodeString& string)
1365 {
1366 vtkTextRenderer* tren = vtkTextRenderer::GetInstance();
1367 if (!tren)
1368 {
1369 vtkErrorMacro("vtkTextRenderer unavailable. Link to vtkRenderingFreeType "
1370 "to get the default implementation.");
1371 return;
1372 }
1373
1374 int backend = this->TextAsPath ? vtkTextRenderer::Default : tren->DetectBackend(string);
1375
1376 if (backend == vtkTextRenderer::FreeType)
1377 {
1378 // Embed freetype text and fonts in the SVG:
1379 FontInfo& info = this->Impl->GetFontInfo(this->TextProp);
1380 info.ProcessString(string);
1381
1382 vtkNew<vtkXMLDataElement> text;
1383 this->ActiveNode->AddNestedElement(text);
1384 text->SetName("text");
1385 this->ApplyTextPropertyStateToNode(text, point[0], point[1]);
1386 // Position is encoded in the transform:
1387 text->SetFloatAttribute("x", 0.f);
1388 text->SetFloatAttribute("y", 0.f);
1389
1390 std::string utf8String = string.utf8_str();
1391 text->SetCharacterData(utf8String.c_str(), static_cast<int>(utf8String.size()));
1392 }
1393 else
1394 {
1395 // Export other text (e.g. MathText) as a path:
1396 vtkNew<vtkPath> tPath;
1397 int dpi = this->Viewport->GetVTKWindow()->GetDPI();
1398 if (!tren->StringToPath(this->TextProp, string, tPath, dpi, backend))
1399 {
1400 vtkErrorMacro("Error generating path for MathText string '" << string << "'.");
1401 return;
1402 }
1403
1404 vtkNew<vtkXMLDataElement> path;
1405 this->ActiveNode->AddNestedElement(path);
1406 path->SetName("path");
1407 this->ApplyTextPropertyStateToNodeForPath(path, point[0], point[1]);
1408
1409 std::ostringstream d;
1410 this->DrawPath(tPath, d);
1411 path->SetAttribute("d", d.str().c_str());
1412 }
1413 }
1414
1415 //------------------------------------------------------------------------------
ComputeStringBounds(const vtkUnicodeString & string,float bounds[4])1416 void vtkSVGContextDevice2D::ComputeStringBounds(const vtkUnicodeString& string, float bounds[4])
1417 {
1418 vtkTextRenderer* tren = vtkTextRenderer::GetInstance();
1419 if (!tren)
1420 {
1421 vtkErrorMacro("vtkTextRenderer unavailable. Link to vtkRenderingFreeType "
1422 "to get the default implementation.");
1423 std::fill(bounds, bounds + 4, 0.f);
1424 return;
1425 }
1426
1427 assert(this->Viewport && this->Viewport->GetVTKWindow());
1428 int dpi = this->Viewport->GetVTKWindow()->GetDPI();
1429
1430 vtkTextRenderer::Metrics m;
1431 if (!tren->GetMetrics(this->TextProp, string, m, dpi))
1432 {
1433 vtkErrorMacro("Error computing bbox for string '" << string << "'.");
1434 std::fill(bounds, bounds + 4, 0.f);
1435 return;
1436 }
1437
1438 bounds[0] = 0.f;
1439 bounds[1] = 0.f;
1440 bounds[2] = static_cast<float>(m.BoundingBox[1] - m.BoundingBox[0] + 1);
1441 bounds[3] = static_cast<float>(m.BoundingBox[3] - m.BoundingBox[2] + 1);
1442 }
1443
1444 //------------------------------------------------------------------------------
ComputeJustifiedStringBounds(const char * string,float bounds[4])1445 void vtkSVGContextDevice2D::ComputeJustifiedStringBounds(const char* string, float bounds[4])
1446 {
1447 // FIXME(#18327): Migrate to processing here rather than working on
1448 // `vtkUnicodeString`.
1449 this->ComputeStringBounds(vtkUnicodeString::from_utf8(string), bounds);
1450 }
1451
1452 //------------------------------------------------------------------------------
DrawMathTextString(float * point,const vtkStdString & str)1453 void vtkSVGContextDevice2D::DrawMathTextString(float* point, const vtkStdString& str)
1454 {
1455 this->DrawString(point, str);
1456 }
1457
1458 //------------------------------------------------------------------------------
DrawImage(float p[2],float scale,vtkImageData * image)1459 void vtkSVGContextDevice2D::DrawImage(float p[2], float scale, vtkImageData* image)
1460 {
1461 int dims[3];
1462 image->GetDimensions(dims);
1463 dims[0] *= scale;
1464 dims[1] *= scale;
1465 this->DrawImage(vtkRectf(p[0], p[1], dims[0], dims[1]), image);
1466 }
1467
1468 //------------------------------------------------------------------------------
DrawImage(const vtkRectf & pos,vtkImageData * image)1469 void vtkSVGContextDevice2D::DrawImage(const vtkRectf& pos, vtkImageData* image)
1470 {
1471 const ImageInfo& info = this->Impl->GetImageInfo(image);
1472 YConverter y(this->CanvasHeight);
1473
1474 const float xScale = pos.GetWidth() / info.Size[0];
1475 const float yScale = pos.GetHeight() / info.Size[1];
1476
1477 // Construct a matrix representing the following transformation:
1478 //
1479 // [X] = [T3] [T2] [S] [T1]
1480 //
1481 // [X] = final transform
1482 // [T1] = translate(-pos.X, -pos.Y); Move to origin to prepare for scaling.
1483 // [S] = scale(xScale, yScale); Resize the image to match the input rect.
1484 // [T2] = translate(0, -pos.H); Anchor at bottom corner instead of top
1485 // [T3] = translate(pos.X, pos.Y); Move back to anchor point
1486 std::ostringstream xform;
1487 xform << "matrix(" << xScale << ",0,0," << yScale << "," << pos.GetX() - xScale * pos.GetX()
1488 << "," << y(pos.GetY()) - (yScale * y(pos.GetY()) + pos.GetHeight()) << ")";
1489
1490 vtkNew<vtkXMLDataElement> use;
1491 this->ActiveNode->AddNestedElement(use);
1492 use->SetName("use");
1493 use->SetFloatAttribute("x", pos.GetX());
1494 use->SetFloatAttribute("y", y(pos.GetY()));
1495 use->SetFloatAttribute("width", pos.GetWidth());
1496 use->SetFloatAttribute("height", pos.GetHeight());
1497 use->SetAttribute("transform", xform.str().c_str());
1498 use->SetAttribute("xlink:href", (std::string("#") + info.Id).c_str());
1499 }
1500
1501 //------------------------------------------------------------------------------
SetColor4(unsigned char[])1502 void vtkSVGContextDevice2D::SetColor4(unsigned char[])
1503 {
1504 // This is how the OpenGL2 impl handles this...
1505 vtkErrorMacro("color cannot be set this way.");
1506 }
1507
1508 //------------------------------------------------------------------------------
SetTexture(vtkImageData * image,int properties)1509 void vtkSVGContextDevice2D::SetTexture(vtkImageData* image, int properties)
1510 {
1511 this->Brush->SetTexture(image);
1512 this->Brush->SetTextureProperties(properties);
1513 }
1514
1515 //------------------------------------------------------------------------------
SetPointSize(float size)1516 void vtkSVGContextDevice2D::SetPointSize(float size)
1517 {
1518 this->Pen->SetWidth(size);
1519 }
1520
1521 //------------------------------------------------------------------------------
SetLineWidth(float width)1522 void vtkSVGContextDevice2D::SetLineWidth(float width)
1523 {
1524 this->Pen->SetWidth(width);
1525 }
1526
1527 //------------------------------------------------------------------------------
SetLineType(int type)1528 void vtkSVGContextDevice2D::SetLineType(int type)
1529 {
1530 this->Pen->SetLineType(type);
1531 }
1532
1533 //------------------------------------------------------------------------------
SetMatrix(vtkMatrix3x3 * m)1534 void vtkSVGContextDevice2D::SetMatrix(vtkMatrix3x3* m)
1535 {
1536 // Adjust the transform to account for the fact that SVG's y-axis is reversed:
1537 std::array<double, 9> mat3;
1538 vtkSVGContextDevice2D::AdjustMatrixForSVG(m->GetData(), mat3.data());
1539
1540 std::array<double, 16> mat4;
1541 vtkSVGContextDevice2D::Matrix3ToMatrix4(mat3.data(), mat4.data());
1542
1543 this->Matrix->SetMatrix(mat4.data());
1544 this->ApplyTransform();
1545 }
1546
1547 //------------------------------------------------------------------------------
GetMatrix(vtkMatrix3x3 * mat3)1548 void vtkSVGContextDevice2D::GetMatrix(vtkMatrix3x3* mat3)
1549 {
1550 vtkSVGContextDevice2D::Matrix4ToMatrix3(this->Matrix->GetMatrix()->GetData(), mat3->GetData());
1551 vtkSVGContextDevice2D::AdjustMatrixForSVG(mat3->GetData(), mat3->GetData());
1552 }
1553
1554 //------------------------------------------------------------------------------
MultiplyMatrix(vtkMatrix3x3 * m)1555 void vtkSVGContextDevice2D::MultiplyMatrix(vtkMatrix3x3* m)
1556 {
1557 // Adjust the transform to account for the fact that SVG's y-axis is reversed:
1558 std::array<double, 9> mat3;
1559 vtkSVGContextDevice2D::AdjustMatrixForSVG(m->GetData(), mat3.data());
1560
1561 std::array<double, 16> mat4;
1562 vtkSVGContextDevice2D::Matrix3ToMatrix4(mat3.data(), mat4.data());
1563 this->Matrix->Concatenate(mat4.data());
1564 this->ApplyTransform();
1565 }
1566
1567 //------------------------------------------------------------------------------
PushMatrix()1568 void vtkSVGContextDevice2D::PushMatrix()
1569 {
1570 this->Matrix->Push();
1571 }
1572
1573 //------------------------------------------------------------------------------
PopMatrix()1574 void vtkSVGContextDevice2D::PopMatrix()
1575 {
1576 this->Matrix->Pop();
1577 this->ApplyTransform();
1578 }
1579
1580 //------------------------------------------------------------------------------
SetClipping(int * x)1581 void vtkSVGContextDevice2D::SetClipping(int* x)
1582 {
1583 if (!std::equal(this->ClipRect.begin(), this->ClipRect.end(), x))
1584 {
1585 std::copy(x, x + this->ClipRect.size(), this->ClipRect.begin());
1586 this->SetupClippingAndTransform();
1587 }
1588 }
1589
1590 //------------------------------------------------------------------------------
EnableClipping(bool enable)1591 void vtkSVGContextDevice2D::EnableClipping(bool enable)
1592 {
1593 if (enable != this->IsClipping)
1594 {
1595 this->IsClipping = enable;
1596 this->SetupClippingAndTransform();
1597 }
1598 }
1599
1600 //------------------------------------------------------------------------------
vtkSVGContextDevice2D()1601 vtkSVGContextDevice2D::vtkSVGContextDevice2D()
1602 : Impl(new Details)
1603 , Viewport(nullptr)
1604 , ContextNode(nullptr)
1605 , ActiveNode(nullptr)
1606 , DefinitionNode(nullptr)
1607 , CanvasHeight(0.f)
1608 , SubdivisionThreshold(1.f)
1609 , IsClipping(false)
1610 , ActiveNodeIsClipping(false)
1611 , EmbedFonts(false)
1612 , TextAsPath(true)
1613 {
1614 std::fill(this->ClipRect.begin(), this->ClipRect.end(), 0);
1615 std::fill(this->ActiveNodeClipRect.begin(), this->ActiveNodeClipRect.end(), 0);
1616
1617 std::fill(this->ActiveNodeTransform.begin(), this->ActiveNodeTransform.end(), 0.);
1618 this->ActiveNodeTransform[0] = 1.;
1619 this->ActiveNodeTransform[4] = 1.;
1620 this->ActiveNodeTransform[8] = 1.;
1621 }
1622
1623 //------------------------------------------------------------------------------
~vtkSVGContextDevice2D()1624 vtkSVGContextDevice2D::~vtkSVGContextDevice2D()
1625 {
1626 this->SetViewport(nullptr);
1627 delete this->Impl;
1628 }
1629
1630 //------------------------------------------------------------------------------
PushGraphicsState()1631 void vtkSVGContextDevice2D::PushGraphicsState()
1632 {
1633 vtkNew<vtkXMLDataElement> newGState;
1634 newGState->SetName("g");
1635 this->ActiveNode->AddNestedElement(newGState);
1636 this->ActiveNode = newGState;
1637 }
1638
1639 //------------------------------------------------------------------------------
PopGraphicsState()1640 void vtkSVGContextDevice2D::PopGraphicsState()
1641 {
1642 if (this->ActiveNode == this->ContextNode)
1643 {
1644 vtkErrorMacro("Internal error: Attempting to pop graphics state past "
1645 "context node. This likely means there's a pop with no "
1646 "corresponding push.");
1647 return;
1648 }
1649
1650 vtkXMLDataElement* oldActive = this->ActiveNode;
1651 this->ActiveNode = this->ActiveNode->GetParent();
1652
1653 // If the old active node is empty, remove it completely:
1654 if (oldActive->GetNumberOfNestedElements() == 0)
1655 {
1656 this->ActiveNode->RemoveNestedElement(oldActive);
1657 }
1658 }
1659
1660 //------------------------------------------------------------------------------
SetupClippingAndTransform()1661 void vtkSVGContextDevice2D::SetupClippingAndTransform()
1662 {
1663 // To manage transforms and clipping, we don't push/pop/concatenate transforms
1664 // in the output, and instead only push a single <g> element under the
1665 // ContextNode with the current transform and clipping formation. Any other
1666 // calls to PushGraphicsState (for instance, setting a common color to a
1667 // collection of primitives) should be popped before changing transform or
1668 // clipping info.
1669
1670 // If we're more than one node nested under ContextNode, that's an error.
1671 // See above.
1672 if (this->ContextNode != this->ActiveNode && this->ContextNode != this->ActiveNode->GetParent())
1673 {
1674 vtkErrorMacro("This method must only be called when there is, at most, one "
1675 "<g> element between ActiveNode and ContextNode.");
1676 return;
1677 }
1678
1679 // Have the transform/clipping settings actually changed?
1680 double* mat4 = this->Matrix->GetMatrix()->GetData();
1681 const bool isClippingChanged = this->IsClipping == this->ActiveNodeIsClipping;
1682 const bool clipRectChanged =
1683 !std::equal(this->ClipRect.begin(), this->ClipRect.end(), this->ActiveNodeClipRect.begin());
1684 const bool transformChanged =
1685 vtkSVGContextDevice2D::Transform2DEqual(this->ActiveNodeTransform.data(), mat4);
1686 if (!isClippingChanged && (!this->IsClipping || !clipRectChanged) && !transformChanged)
1687 {
1688 return;
1689 }
1690
1691 // Sync the cached values:
1692 vtkSVGContextDevice2D::Matrix4ToMatrix3(mat4, this->ActiveNodeTransform.data());
1693 std::copy(this->ClipRect.begin(), this->ClipRect.end(), this->ActiveNodeClipRect.begin());
1694 this->ActiveNodeIsClipping = this->IsClipping;
1695
1696 // Strip the old transform/clip node out if needed:
1697 if (this->ActiveNode != this->ContextNode)
1698 {
1699 this->PopGraphicsState();
1700 }
1701 assert(this->ActiveNode == this->ContextNode);
1702
1703 // If no clipping or transform is present, no need for a new <g> element,
1704 // just add new primitives to the ContextNode directly.
1705 const std::array<double, 9> ident = { { 1., 0., 0., 0., 1., 0., 0., 0., 1. } };
1706 const bool isIdentity = vtkSVGContextDevice2D::Transform2DEqual(ident.data(), mat4);
1707
1708 if (!this->IsClipping && isIdentity)
1709 {
1710 return;
1711 }
1712
1713 // Finally, add new gstate with transform and clipping info.
1714 this->PushGraphicsState();
1715 if (!isIdentity)
1716 {
1717 this->ActiveNode->SetAttribute(
1718 "transform", Transform2DToString(this->ActiveNodeTransform).c_str());
1719 }
1720 if (this->IsClipping)
1721 {
1722 const ClipRectInfo& info = this->Impl->GetClipRectInfo(this->ClipRect);
1723 this->ActiveNode->SetAttribute("clip-path", (std::string("url(#") + info.Id + ")").c_str());
1724 }
1725 }
1726
1727 //------------------------------------------------------------------------------
ApplyPenStateToNode(vtkXMLDataElement * node)1728 void vtkSVGContextDevice2D::ApplyPenStateToNode(vtkXMLDataElement* node)
1729 {
1730 this->ApplyPenColorToNode(node);
1731 this->ApplyPenOpacityToNode(node);
1732 this->ApplyPenWidthToNode(node);
1733 this->ApplyPenStippleToNode(node);
1734 }
1735
1736 //------------------------------------------------------------------------------
ApplyPenColorToNode(vtkXMLDataElement * node)1737 void vtkSVGContextDevice2D::ApplyPenColorToNode(vtkXMLDataElement* node)
1738 {
1739 node->SetAttribute("stroke", ColorToString(this->Pen->GetColor()).c_str());
1740 }
1741
1742 //------------------------------------------------------------------------------
ApplyPenOpacityToNode(vtkXMLDataElement * node)1743 void vtkSVGContextDevice2D::ApplyPenOpacityToNode(vtkXMLDataElement* node)
1744 {
1745 if (this->Pen->GetOpacity() != 255)
1746 {
1747 node->SetFloatAttribute("stroke-opacity", this->Pen->GetOpacity() / 255.f);
1748 }
1749 }
1750
1751 //------------------------------------------------------------------------------
ApplyPenWidthToNode(vtkXMLDataElement * node)1752 void vtkSVGContextDevice2D::ApplyPenWidthToNode(vtkXMLDataElement* node)
1753 {
1754 float width = this->GetScaledPenWidth();
1755 if (std::fabs(width - 1.f) > 1e-5)
1756 {
1757 node->SetFloatAttribute("stroke-width", width);
1758 }
1759 }
1760
1761 //------------------------------------------------------------------------------
ApplyPenStippleToNode(vtkXMLDataElement * node)1762 void vtkSVGContextDevice2D::ApplyPenStippleToNode(vtkXMLDataElement* node)
1763 {
1764 // These match the OpenGL2 implementation:
1765 switch (this->Pen->GetLineType())
1766 {
1767 default:
1768 vtkErrorMacro("Unknown line type: " << this->Pen->GetLineType());
1769 VTK_FALLTHROUGH;
1770
1771 case vtkPen::NO_PEN:
1772 node->SetAttribute("stroke-dasharray", "0,10");
1773 break;
1774
1775 case vtkPen::SOLID_LINE:
1776 node->RemoveAttribute("stroke-dasharray");
1777 break;
1778
1779 case vtkPen::DASH_LINE:
1780 node->SetAttribute("stroke-dasharray", "8");
1781 break;
1782
1783 case vtkPen::DOT_LINE:
1784 node->SetAttribute("stroke-dasharray", "1,7");
1785 break;
1786
1787 case vtkPen::DASH_DOT_LINE:
1788 node->SetAttribute("stroke-dasharray", "4,6,2,4");
1789 break;
1790
1791 case vtkPen::DASH_DOT_DOT_LINE:
1792 // This is dash-dot-dash, but eh. It matches the OpenGL2 0x1C47 pattern.
1793 node->SetAttribute("stroke-dasharray", "3,3,1,3,3,3");
1794 break;
1795
1796 case vtkPen::DENSE_DOT_LINE:
1797 node->SetAttribute("stroke-dasharray", "1,3");
1798 break;
1799 }
1800 }
1801
1802 //------------------------------------------------------------------------------
ApplyPenAsFillColorToNode(vtkXMLDataElement * node)1803 void vtkSVGContextDevice2D::ApplyPenAsFillColorToNode(vtkXMLDataElement* node)
1804 {
1805 node->SetAttribute("fill", ColorToString(this->Pen->GetColor()).c_str());
1806 }
1807
1808 //------------------------------------------------------------------------------
ApplyPenAsFillOpacityToNode(vtkXMLDataElement * node)1809 void vtkSVGContextDevice2D::ApplyPenAsFillOpacityToNode(vtkXMLDataElement* node)
1810 {
1811 if (this->Pen->GetOpacity() != 255)
1812 {
1813 node->SetFloatAttribute("fill-opacity", this->Pen->GetOpacity() / 255.f);
1814 }
1815 }
1816
1817 //------------------------------------------------------------------------------
ApplyBrushStateToNode(vtkXMLDataElement * node)1818 void vtkSVGContextDevice2D::ApplyBrushStateToNode(vtkXMLDataElement* node)
1819 {
1820 if (!this->Brush->GetTexture())
1821 {
1822 this->ApplyBrushColorToNode(node);
1823 this->ApplyBrushOpacityToNode(node);
1824 }
1825 else
1826 {
1827 // Do not apply brush opacity; this matches the OpenGL2 implementation.
1828 this->ApplyBrushTextureToNode(node);
1829 }
1830 }
1831
1832 //------------------------------------------------------------------------------
ApplyBrushColorToNode(vtkXMLDataElement * node)1833 void vtkSVGContextDevice2D::ApplyBrushColorToNode(vtkXMLDataElement* node)
1834 {
1835 node->SetAttribute("fill", ColorToString(this->Brush->GetColor()).c_str());
1836 }
1837
1838 //------------------------------------------------------------------------------
ApplyBrushOpacityToNode(vtkXMLDataElement * node)1839 void vtkSVGContextDevice2D::ApplyBrushOpacityToNode(vtkXMLDataElement* node)
1840 {
1841 if (this->Brush->GetOpacity() != 255)
1842 {
1843 node->SetFloatAttribute("fill-opacity", this->Brush->GetOpacity() / 255.f);
1844 }
1845 }
1846
1847 //------------------------------------------------------------------------------
ApplyBrushTextureToNode(vtkXMLDataElement * node)1848 void vtkSVGContextDevice2D::ApplyBrushTextureToNode(vtkXMLDataElement* node)
1849 {
1850 vtkImageData* img = this->Brush->GetTexture();
1851 int prop = this->Brush->GetTextureProperties();
1852
1853 const PatternInfo& info = this->Impl->GetPatternInfo(img, prop);
1854 std::ostringstream fill;
1855 fill << "url(#" << info.PatternId << ")";
1856 node->SetAttribute("fill", fill.str().c_str());
1857 }
1858
1859 //------------------------------------------------------------------------------
ApplyTextPropertyStateToNode(vtkXMLDataElement * node,float x,float y)1860 void vtkSVGContextDevice2D::ApplyTextPropertyStateToNode(vtkXMLDataElement* node, float x, float y)
1861 {
1862 vtkFreeTypeTools* ftt = vtkFreeTypeTools::GetInstance();
1863 if (!ftt)
1864 {
1865 vtkErrorMacro("Error embedding fonts: No vtkFreeTypeTools instance "
1866 "available.");
1867 return;
1868 }
1869
1870 YConverter yConv(this->CanvasHeight);
1871
1872 using FaceMetrics = vtkFreeTypeTools::FaceMetrics;
1873 FaceMetrics faceMetrics = ftt->GetFaceMetrics(this->TextProp);
1874
1875 vtkVector3d colord;
1876 this->TextProp->GetColor(colord.GetData());
1877 vtkColor3ub color = {
1878 static_cast<unsigned char>((colord[0] * 255.) + 0.5),
1879 static_cast<unsigned char>((colord[1] * 255.) + 0.5),
1880 static_cast<unsigned char>((colord[2] * 255.) + 0.5),
1881 };
1882
1883 std::ostringstream xform;
1884 xform << "translate(" << x << "," << yConv(y) << ")";
1885 if (this->TextProp->GetOrientation() != 0.)
1886 {
1887 xform << "rotate(" << this->TextProp->GetOrientation() << ") ";
1888 }
1889
1890 std::ostringstream fontSize;
1891 fontSize << this->TextProp->GetFontSize() << "pt";
1892
1893 node->SetAttribute("fill", ColorToString(color.GetData()).c_str());
1894 node->SetFloatAttribute("fill-opacity", static_cast<float>(this->TextProp->GetOpacity()));
1895 node->SetAttribute("font-family", faceMetrics.FamilyName.c_str());
1896 node->SetAttribute("font-size", fontSize.str().c_str());
1897 node->SetAttribute("font-style", this->TextProp->GetItalic() != 0 ? "italic" : "normal");
1898 node->SetAttribute("font-weight", this->TextProp->GetBold() != 0 ? "bold" : "normal");
1899
1900 switch (this->TextProp->GetJustification())
1901 {
1902 default:
1903 case VTK_TEXT_LEFT:
1904 break;
1905
1906 case VTK_TEXT_CENTERED:
1907 node->SetAttribute("text-anchor", "middle");
1908 break;
1909
1910 case VTK_TEXT_RIGHT:
1911 node->SetAttribute("text-anchor", "right");
1912 break;
1913 }
1914
1915 switch (this->TextProp->GetVerticalJustification())
1916 {
1917 default:
1918 case VTK_TEXT_BOTTOM:
1919 node->SetAttribute("alignment-baseline", "bottom");
1920 break;
1921
1922 case VTK_TEXT_CENTERED:
1923 if (this->TextProp->GetUseTightBoundingBox())
1924 {
1925 node->SetAttribute("alignment-baseline", "middle");
1926 }
1927 else
1928 {
1929 node->SetAttribute("alignment-baseline", "central");
1930 }
1931 break;
1932
1933 case VTK_TEXT_TOP:
1934 node->SetAttribute("alignment-baseline", "top");
1935 break;
1936 }
1937
1938 node->SetAttribute("transform", xform.str().c_str());
1939 }
1940
1941 //------------------------------------------------------------------------------
ApplyTextPropertyStateToNodeForPath(vtkXMLDataElement * node,float x,float y)1942 void vtkSVGContextDevice2D::ApplyTextPropertyStateToNodeForPath(
1943 vtkXMLDataElement* node, float x, float y)
1944 {
1945 vtkVector3d colord;
1946 this->TextProp->GetColor(colord.GetData());
1947 vtkColor3ub color = {
1948 static_cast<unsigned char>((colord[0] * 255.) + 0.5),
1949 static_cast<unsigned char>((colord[1] * 255.) + 0.5),
1950 static_cast<unsigned char>((colord[2] * 255.) + 0.5),
1951 };
1952
1953 YConverter yConv(this->CanvasHeight);
1954
1955 std::ostringstream xform;
1956 xform << "translate(" << x << "," << yConv(y) << ")";
1957
1958 node->SetAttribute("fill", ColorToString(color.GetData()).c_str());
1959 node->SetFloatAttribute("fill-opacity", static_cast<float>(this->TextProp->GetOpacity()));
1960
1961 node->SetAttribute("transform", xform.str().c_str());
1962 }
1963
1964 //------------------------------------------------------------------------------
ApplyTransform()1965 void vtkSVGContextDevice2D::ApplyTransform()
1966 {
1967 this->SetupClippingAndTransform();
1968 }
1969
1970 //------------------------------------------------------------------------------
AddCrossSymbol(bool)1971 std::string vtkSVGContextDevice2D::AddCrossSymbol(bool)
1972 {
1973 std::ostringstream idStream;
1974 idStream << "Cross";
1975
1976 std::string id = idStream.str();
1977
1978 if (!this->DefinitionNode->FindNestedElementWithNameAndId("symbol", id.c_str()))
1979 {
1980 vtkNew<vtkXMLDataElement> symbol;
1981 this->DefinitionNode->AddNestedElement(symbol);
1982
1983 symbol->SetName("symbol");
1984 symbol->SetId(id.c_str());
1985 symbol->SetAttribute("id", id.c_str());
1986 symbol->SetAttribute("viewBox", "0,0 1,1");
1987
1988 vtkNew<vtkXMLDataElement> path;
1989 symbol->AddNestedElement(path);
1990
1991 path->SetName("path");
1992 path->SetAttribute("d", "M0,0L1,1M0,1L1,0");
1993 }
1994
1995 return id;
1996 }
1997
1998 //------------------------------------------------------------------------------
AddPlusSymbol(bool)1999 std::string vtkSVGContextDevice2D::AddPlusSymbol(bool)
2000 {
2001 std::ostringstream idStream;
2002 idStream << "Plus";
2003
2004 std::string id = idStream.str();
2005
2006 if (!this->DefinitionNode->FindNestedElementWithNameAndId("symbol", id.c_str()))
2007 {
2008 vtkNew<vtkXMLDataElement> symbol;
2009 this->DefinitionNode->AddNestedElement(symbol);
2010
2011 symbol->SetName("symbol");
2012 symbol->SetId(id.c_str());
2013 symbol->SetAttribute("id", id.c_str());
2014 symbol->SetAttribute("viewBox", "0,0 1,1");
2015
2016 vtkNew<vtkXMLDataElement> path;
2017 symbol->AddNestedElement(path);
2018
2019 path->SetName("path");
2020 path->SetAttribute("d", "M0.5,0L0.5,1M0,0.5L1,0.5");
2021 }
2022
2023 return id;
2024 }
2025
2026 //------------------------------------------------------------------------------
AddSquareSymbol(bool)2027 std::string vtkSVGContextDevice2D::AddSquareSymbol(bool)
2028 {
2029 std::ostringstream idStream;
2030 idStream << "Square";
2031
2032 std::string id = idStream.str();
2033
2034 if (!this->DefinitionNode->FindNestedElementWithNameAndId("symbol", id.c_str()))
2035 {
2036 vtkNew<vtkXMLDataElement> symbol;
2037 this->DefinitionNode->AddNestedElement(symbol);
2038
2039 symbol->SetName("symbol");
2040 symbol->SetId(id.c_str());
2041 symbol->SetAttribute("id", id.c_str());
2042 symbol->SetAttribute("viewBox", "0,0 1,1");
2043
2044 vtkNew<vtkXMLDataElement> rect;
2045 symbol->AddNestedElement(rect);
2046
2047 rect->SetName("rect");
2048 rect->SetFloatAttribute("x", 0.f);
2049 rect->SetFloatAttribute("y", 0.f);
2050 rect->SetFloatAttribute("width", 1.f);
2051 rect->SetFloatAttribute("height", 1.f);
2052 }
2053
2054 return id;
2055 }
2056
2057 //------------------------------------------------------------------------------
AddCircleSymbol(bool)2058 std::string vtkSVGContextDevice2D::AddCircleSymbol(bool)
2059 {
2060 std::ostringstream idStream;
2061 idStream << "Circle";
2062
2063 std::string id = idStream.str();
2064
2065 if (!this->DefinitionNode->FindNestedElementWithNameAndId("symbol", id.c_str()))
2066 {
2067 vtkNew<vtkXMLDataElement> symbol;
2068 this->DefinitionNode->AddNestedElement(symbol);
2069
2070 symbol->SetName("symbol");
2071 symbol->SetId(id.c_str());
2072 symbol->SetAttribute("id", id.c_str());
2073 symbol->SetAttribute("viewBox", "0,0 1,1");
2074
2075 vtkNew<vtkXMLDataElement> circle;
2076 symbol->AddNestedElement(circle);
2077
2078 circle->SetName("circle");
2079 circle->SetFloatAttribute("cx", 0.5f);
2080 circle->SetFloatAttribute("cy", 0.5f);
2081 circle->SetFloatAttribute("r", 0.5f);
2082 }
2083
2084 return id;
2085 }
2086
2087 //------------------------------------------------------------------------------
AddDiamondSymbol(bool)2088 std::string vtkSVGContextDevice2D::AddDiamondSymbol(bool)
2089 {
2090 std::ostringstream idStream;
2091 idStream << "Diamond";
2092
2093 std::string id = idStream.str();
2094
2095 if (!this->DefinitionNode->FindNestedElementWithNameAndId("symbol", id.c_str()))
2096 {
2097 vtkNew<vtkXMLDataElement> symbol;
2098 this->DefinitionNode->AddNestedElement(symbol);
2099
2100 symbol->SetName("symbol");
2101 symbol->SetId(id.c_str());
2102 symbol->SetAttribute("id", id.c_str());
2103 symbol->SetAttribute("viewBox", "0,0 1,1");
2104
2105 vtkNew<vtkXMLDataElement> path;
2106 symbol->AddNestedElement(path);
2107
2108 path->SetName("path");
2109 path->SetAttribute("d", "M0,.5L.5,1 1,.5 .5,0z");
2110 }
2111
2112 return id;
2113 }
2114
2115 //------------------------------------------------------------------------------
DrawPath(vtkPath * path,std::ostream & out)2116 void vtkSVGContextDevice2D::DrawPath(vtkPath* path, std::ostream& out)
2117 {
2118 // The text renderer always uses floats to generate paths, so we'll optimize
2119 // a bit here:
2120 vtkFloatArray* points = vtkArrayDownCast<vtkFloatArray>(path->GetPoints()->GetData());
2121 vtkIntArray* codes = path->GetCodes();
2122
2123 if (!points)
2124 {
2125 vtkErrorMacro("This method expects the path point precision to be floats.");
2126 return;
2127 }
2128
2129 vtkIdType numTuples = points->GetNumberOfTuples();
2130 if (numTuples != codes->GetNumberOfTuples() || codes->GetNumberOfComponents() != 1 ||
2131 points->GetNumberOfComponents() != 3)
2132 {
2133 vtkErrorMacro("Invalid path data.");
2134 return;
2135 }
2136
2137 if (numTuples == 0)
2138 { // Nothing to do.
2139 return;
2140 }
2141
2142 // Use a lambda to invert the y positions for SVG:
2143 auto y = [](float yIn) -> float { return -yIn; };
2144
2145 typedef vtkPath::ControlPointType CodeEnum;
2146 typedef vtkIntArray::ValueType CodeType;
2147 CodeType* code = codes->GetPointer(0);
2148 CodeType* codeEnd = code + numTuples;
2149
2150 typedef vtkFloatArray::ValueType PointType;
2151 PointType* point = points->GetPointer(0);
2152
2153 // These are only used in an assertion, ifdef silences warning on non-debug
2154 // builds
2155 #ifndef NDEBUG
2156 PointType* pointBegin = point;
2157 CodeType* codeBegin = code;
2158 #endif
2159
2160 // Track the last code so we can save a little space by chaining draw commands
2161 int lastCode = -1;
2162
2163 while (code < codeEnd)
2164 {
2165 assert("Sanity check" && (code - codeBegin) * 3 == point - pointBegin);
2166
2167 switch (static_cast<CodeEnum>(*code))
2168 {
2169 case vtkPath::MOVE_TO:
2170 if (lastCode != *code)
2171 {
2172 lastCode = *code;
2173 out << "M";
2174 }
2175 out << point[0] << "," << y(point[1]) << "\n";
2176 point += 3;
2177 ++code;
2178 break;
2179
2180 case vtkPath::LINE_TO:
2181 if (lastCode != *code)
2182 {
2183 lastCode = *code;
2184 out << "L";
2185 }
2186 out << point[0] << "," << y(point[1]) << "\n";
2187 point += 3;
2188 ++code;
2189 break;
2190
2191 case vtkPath::CONIC_CURVE:
2192 assert(CodeEnum(code[1]) == vtkPath::CONIC_CURVE);
2193 if (lastCode != *code)
2194 {
2195 lastCode = *code;
2196 out << "Q";
2197 }
2198 out << point[0] << "," << y(point[1]) << " " << point[3] << "," << y(point[4]) << "\n";
2199 point += 6;
2200 code += 2;
2201 break;
2202
2203 case vtkPath::CUBIC_CURVE:
2204 assert(CodeEnum(code[1]) == vtkPath::CUBIC_CURVE);
2205 assert(CodeEnum(code[2]) == vtkPath::CUBIC_CURVE);
2206 if (lastCode != *code)
2207 {
2208 lastCode = *code;
2209 out << "C";
2210 }
2211 out << point[0] << "," << y(point[1]) << " " << point[3] << "," << y(point[4]) << " "
2212 << point[6] << "," << y(point[7]) << "\n";
2213 point += 9;
2214 code += 3;
2215 break;
2216
2217 default:
2218 vtkErrorMacro("Unknown control code.");
2219 }
2220 }
2221 }
2222
2223 //------------------------------------------------------------------------------
DrawLineGradient(const vtkVector2f & p1,const vtkColor4ub & c1,const vtkVector2f & p2,const vtkColor4ub & c2,bool useAlpha)2224 void vtkSVGContextDevice2D::DrawLineGradient(const vtkVector2f& p1, const vtkColor4ub& c1,
2225 const vtkVector2f& p2, const vtkColor4ub& c2, bool useAlpha)
2226 {
2227 const vtkColor4ub aveColor = { static_cast<unsigned char>(static_cast<int>(c1[0] + c2[0]) / 2),
2228 static_cast<unsigned char>(static_cast<int>(c1[1] + c2[1]) / 2),
2229 static_cast<unsigned char>(static_cast<int>(c1[2] + c2[2]) / 2),
2230 static_cast<unsigned char>(static_cast<int>(c1[3] + c2[3]) / 2) };
2231
2232 // If the colors are more or less the same, go ahead and draw this segment.
2233 // Same if the segment is small enough to fit on a single pixel.
2234 if (this->LengthLessThanTolerance(p1, p2) || this->ColorsAreClose(c1, c2, useAlpha))
2235 {
2236 YConverter y(this->CanvasHeight);
2237 vtkNew<vtkXMLDataElement> line;
2238 this->ActiveNode->AddNestedElement(line);
2239 line->SetName("line");
2240 line->SetFloatAttribute("x1", p1[0]);
2241 line->SetFloatAttribute("y1", y(p1[1]));
2242 line->SetFloatAttribute("x2", p2[0]);
2243 line->SetFloatAttribute("y2", y(p2[1]));
2244 this->ApplyPenWidthToNode(line);
2245 line->SetAttribute("stroke", ColorToString(aveColor.GetData()).c_str());
2246 if (useAlpha && aveColor[3] != 255)
2247 {
2248 line->SetFloatAttribute("stroke-opacity", aveColor[3] / 255.f);
2249 }
2250 // FIXME: Disable gradient stipple for now, we'd need to account for offsets
2251 // this->ApplyPenStippleToNode(node);
2252
2253 return;
2254 }
2255
2256 // Otherwise, subdivide into two more line segments:
2257 const vtkVector2f avePos = (p1 + p2) * 0.5;
2258
2259 this->DrawLineGradient(p1, c1, avePos, aveColor, useAlpha);
2260 this->DrawLineGradient(avePos, aveColor, p2, c2, useAlpha);
2261 }
2262
2263 //------------------------------------------------------------------------------
DrawTriangleGradient(const vtkVector2f & p1,const vtkColor4ub & c1,const vtkVector2f & p2,const vtkColor4ub & c2,const vtkVector2f & p3,const vtkColor4ub & c3,bool useAlpha)2264 void vtkSVGContextDevice2D::DrawTriangleGradient(const vtkVector2f& p1, const vtkColor4ub& c1,
2265 const vtkVector2f& p2, const vtkColor4ub& c2, const vtkVector2f& p3, const vtkColor4ub& c3,
2266 bool useAlpha)
2267 {
2268 // If the colors are more or less the same, go ahead and draw this triangle.
2269 // Same if the triangle is small enough to fit on a single pixel.
2270 if (this->AreaLessThanTolerance(p1, p2, p3) || this->ColorsAreClose(c1, c2, c3, useAlpha))
2271 {
2272 YConverter y(this->CanvasHeight);
2273 const vtkColor4ub aveColor = { static_cast<unsigned char>(
2274 static_cast<int>(c1[0] + c2[0] + c3[0]) / 3),
2275 static_cast<unsigned char>(static_cast<int>(c1[1] + c2[1] + c3[1]) / 3),
2276 static_cast<unsigned char>(static_cast<int>(c1[2] + c2[2] + c3[2]) / 3),
2277 static_cast<unsigned char>(static_cast<int>(c1[3] + c2[3] + c3[3]) / 3) };
2278 vtkNew<vtkXMLDataElement> polygon;
2279 this->ActiveNode->AddNestedElement(polygon);
2280 polygon->SetName("polygon");
2281 polygon->SetAttribute("fill", ColorToString(aveColor.GetData()).c_str());
2282 if (useAlpha && aveColor[3] != 255)
2283 {
2284 polygon->SetFloatAttribute("fill-opacity", aveColor[3] / 255.f);
2285 }
2286
2287 // This should disable antialiasing on supported viewers (works on webkit).
2288 // Helps prevent visible boundaries between polygons:
2289 polygon->SetAttribute("shape-rendering", "crispEdges");
2290
2291 std::ostringstream points;
2292 points << p1[0] << "," << y(p1[1]) << " " << p2[0] << "," << y(p2[1]) << " " << p3[0] << ","
2293 << y(p3[1]);
2294 polygon->SetAttribute("points", points.str().c_str());
2295
2296 return;
2297 }
2298
2299 // Otherwise, subdivide into 4 triangles:
2300 // v1
2301 // +
2302 // /|
2303 // / |
2304 // / |
2305 // / |
2306 // v12 +----+ v13
2307 // /| /|
2308 // / | / |
2309 // / | / |
2310 // / |/ |
2311 // +----+----+
2312 // v2 v23 v3
2313 const vtkVector2f p12 = (p1 + p2) * 0.5;
2314 const vtkVector2f p23 = (p2 + p3) * 0.5;
2315 const vtkVector2f p13 = (p1 + p3) * 0.5;
2316 const vtkColor4ub c12 = { static_cast<unsigned char>(static_cast<int>(c1[0] + c2[0]) / 2),
2317 static_cast<unsigned char>(static_cast<int>(c1[1] + c2[1]) / 2),
2318 static_cast<unsigned char>(static_cast<int>(c1[2] + c2[2]) / 2),
2319 static_cast<unsigned char>(static_cast<int>(c1[3] + c2[3]) / 2) };
2320 const vtkColor4ub c23 = { static_cast<unsigned char>(static_cast<int>(c2[0] + c3[0]) / 2),
2321 static_cast<unsigned char>(static_cast<int>(c2[1] + c3[1]) / 2),
2322 static_cast<unsigned char>(static_cast<int>(c2[2] + c3[2]) / 2),
2323 static_cast<unsigned char>(static_cast<int>(c2[3] + c3[3]) / 2) };
2324 const vtkColor4ub c13 = { static_cast<unsigned char>(static_cast<int>(c1[0] + c3[0]) / 2),
2325 static_cast<unsigned char>(static_cast<int>(c1[1] + c3[1]) / 2),
2326 static_cast<unsigned char>(static_cast<int>(c1[2] + c3[2]) / 2),
2327 static_cast<unsigned char>(static_cast<int>(c1[3] + c3[3]) / 2) };
2328
2329 this->DrawTriangleGradient(p1, c1, p12, c12, p13, c13, useAlpha);
2330 this->DrawTriangleGradient(p2, c2, p12, c12, p23, c23, useAlpha);
2331 this->DrawTriangleGradient(p3, c3, p13, c13, p23, c23, useAlpha);
2332 this->DrawTriangleGradient(p12, c12, p13, c13, p23, c23, useAlpha);
2333 }
2334
2335 //------------------------------------------------------------------------------
AreaLessThanTolerance(const vtkVector2f & p1,const vtkVector2f & p2,const vtkVector2f & p3)2336 bool vtkSVGContextDevice2D::AreaLessThanTolerance(
2337 const vtkVector2f& p1, const vtkVector2f& p2, const vtkVector2f& p3)
2338 {
2339 return this->LengthLessThanTolerance(p1, p2) && this->LengthLessThanTolerance(p1, p3) &&
2340 this->LengthLessThanTolerance(p2, p3);
2341 }
2342
2343 //------------------------------------------------------------------------------
LengthLessThanTolerance(const vtkVector2f & p1,const vtkVector2f & p2)2344 bool vtkSVGContextDevice2D::LengthLessThanTolerance(const vtkVector2f& p1, const vtkVector2f& p2)
2345 {
2346 return (p2 - p1).SquaredNorm() < this->SubdivisionThreshold;
2347 }
2348
2349 //------------------------------------------------------------------------------
ColorsAreClose(const vtkColor4ub & c1,const vtkColor4ub & c2,bool useAlpha)2350 bool vtkSVGContextDevice2D::ColorsAreClose(
2351 const vtkColor4ub& c1, const vtkColor4ub& c2, bool useAlpha)
2352 {
2353 const std::array<unsigned char, 4> tol = { { 16, 8, 32, 32 } };
2354 int comps = useAlpha ? 4 : 3;
2355 for (int i = 0; i < comps; ++i)
2356 {
2357 if (std::abs(c1[i] - c2[i]) > tol[i])
2358 {
2359 return false;
2360 }
2361 }
2362
2363 return true;
2364 }
2365
2366 //------------------------------------------------------------------------------
ColorsAreClose(const vtkColor4ub & c1,const vtkColor4ub & c2,const vtkColor4ub & c3,bool useAlpha)2367 bool vtkSVGContextDevice2D::ColorsAreClose(
2368 const vtkColor4ub& c1, const vtkColor4ub& c2, const vtkColor4ub& c3, bool useAlpha)
2369 {
2370 return (this->ColorsAreClose(c1, c2, useAlpha) && this->ColorsAreClose(c2, c3, useAlpha) &&
2371 this->ColorsAreClose(c1, c3, useAlpha));
2372 }
2373
2374 //------------------------------------------------------------------------------
WriteFonts()2375 void vtkSVGContextDevice2D::WriteFonts()
2376 {
2377 vtkFreeTypeTools* ftt = vtkFreeTypeTools::GetInstance();
2378 if (!ftt)
2379 {
2380 vtkErrorMacro("Error embedding fonts: No vtkFreeTypeTools instance "
2381 "available.");
2382 return;
2383 }
2384
2385 using FaceMetrics = vtkFreeTypeTools::FaceMetrics;
2386 using GlyphOutline = vtkFreeTypeTools::GlyphOutline;
2387
2388 for (const auto& fontEntry : this->Impl->FontMap)
2389 {
2390 const FontKey& key = fontEntry.first;
2391 const FontInfo* info = fontEntry.second;
2392 FaceMetrics faceMetrics = ftt->GetFaceMetrics(key.TextProperty);
2393
2394 // We only embed scalable fonts for now.
2395 if (!faceMetrics.Scalable)
2396 {
2397 vtkWarningMacro("Cannot embed non-scalable fonts (referring to font file: "
2398 << key.TextProperty->GetFontFile() << ")");
2399 continue;
2400 }
2401
2402 vtkNew<vtkXMLDataElement> font;
2403 this->DefinitionNode->AddNestedElement(font);
2404 font->SetName("font");
2405 font->SetAttribute("id", info->SVGId.c_str());
2406 font->SetIntAttribute("horiz-adv-x", faceMetrics.HorizAdvance);
2407
2408 vtkNew<vtkXMLDataElement> face;
2409 font->AddNestedElement(face);
2410 face->SetName("font-face");
2411 face->SetAttribute("font-family", faceMetrics.FamilyName.c_str());
2412 face->SetAttribute("font-style", faceMetrics.Italic ? "italic" : "normal");
2413 face->SetAttribute("font-weight", faceMetrics.Bold ? "bold" : "normal");
2414 face->SetAttribute("font-size", "all");
2415 face->SetIntAttribute("units-per-em", faceMetrics.UnitsPerEM);
2416 face->SetIntAttribute("ascent", faceMetrics.Ascender);
2417 face->SetIntAttribute("descent", faceMetrics.Descender);
2418 face->SetAttribute("bbox", BBoxToString(faceMetrics.BoundingBox).c_str());
2419 face->SetAttribute("alphabetic", "0");
2420
2421 for (auto charId : info->Chars)
2422 {
2423 GlyphOutline glyphInfo = ftt->GetUnscaledGlyphOutline(key.TextProperty, charId);
2424 vtkUnicodeString unicode(1, charId);
2425
2426 vtkNew<vtkXMLDataElement> glyph;
2427 face->AddNestedElement(glyph);
2428 glyph->SetName("glyph");
2429 glyph->SetAttributeEncoding(VTK_ENCODING_UTF_8);
2430 glyph->SetAttribute("unicode", unicode.utf8_str());
2431 glyph->SetIntAttribute("horiz-adv-x", glyphInfo.HorizAdvance);
2432
2433 std::ostringstream d;
2434 this->DrawPath(glyphInfo.Path, d);
2435 glyph->SetAttribute("d", d.str().c_str());
2436 }
2437
2438 for (auto charPair : info->KerningPairs)
2439 {
2440 const vtkUnicodeString unicode1(1, charPair.first);
2441 const vtkUnicodeString unicode2(1, charPair.second);
2442 std::array<int, 2> kerning =
2443 ftt->GetUnscaledKerning(key.TextProperty, charPair.first, charPair.second);
2444
2445 if (std::abs(kerning[0]) == 0)
2446 {
2447 continue;
2448 }
2449
2450 vtkNew<vtkXMLDataElement> hkern;
2451 font->AddNestedElement(hkern);
2452 hkern->SetName("hkern");
2453 hkern->SetAttributeEncoding(VTK_ENCODING_UTF_8);
2454 hkern->SetAttribute("u1", unicode1.utf8_str());
2455 hkern->SetAttribute("u2", unicode2.utf8_str());
2456 hkern->SetIntAttribute("k", -kerning[0]);
2457 }
2458 }
2459 }
2460
2461 //------------------------------------------------------------------------------
WriteImages()2462 void vtkSVGContextDevice2D::WriteImages()
2463 {
2464 for (const ImageInfo& info : this->Impl->ImageSet)
2465 {
2466 vtkNew<vtkXMLDataElement> image;
2467 this->DefinitionNode->AddNestedElement(image);
2468 image->SetName("image");
2469 image->SetAttribute("id", info.Id.c_str());
2470 image->SetIntAttribute("width", info.Size[0]);
2471 image->SetIntAttribute("height", info.Size[1]);
2472 image->SetAttribute("xlink:href", info.PNGBase64.c_str());
2473 }
2474 }
2475
2476 //------------------------------------------------------------------------------
WritePatterns()2477 void vtkSVGContextDevice2D::WritePatterns()
2478 {
2479 for (const PatternInfo& info : this->Impl->PatternSet)
2480 {
2481 vtkNew<vtkXMLDataElement> pattern;
2482 this->DefinitionNode->AddNestedElement(pattern);
2483 pattern->SetName("pattern");
2484 pattern->SetAttribute("id", info.PatternId.c_str());
2485
2486 // We only care about Repeat and Stretch, since SVG doesn't allow control
2487 // over Nearest/Linear interpolation.
2488 const bool isTiled = ((info.TextureProperty & vtkBrush::Repeat) != 0);
2489 if (isTiled)
2490 {
2491 pattern->SetIntAttribute("width", info.ImageSize[0]);
2492 pattern->SetIntAttribute("height", info.ImageSize[1]);
2493 pattern->SetAttribute("patternUnits", "userSpaceOnUse");
2494 }
2495 else // Stretched
2496 {
2497 std::ostringstream viewBox;
2498 viewBox << "0,0," << info.ImageSize[0] << "," << info.ImageSize[1];
2499 pattern->SetIntAttribute("width", 1);
2500 pattern->SetIntAttribute("height", 1);
2501 pattern->SetAttribute("viewBox", viewBox.str().c_str());
2502 pattern->SetAttribute("preserveAspectRatio", "none");
2503 }
2504
2505 vtkNew<vtkXMLDataElement> use;
2506 pattern->AddNestedElement(use);
2507 use->SetName("use");
2508 use->SetFloatAttribute("x", 0);
2509 use->SetFloatAttribute("y", 0);
2510 use->SetIntAttribute("width", info.ImageSize[0]);
2511 use->SetIntAttribute("height", info.ImageSize[1]);
2512 use->SetAttribute("xlink:href", (std::string("#") + info.ImageId).c_str());
2513 }
2514 }
2515
2516 //------------------------------------------------------------------------------
WriteClipRects()2517 void vtkSVGContextDevice2D::WriteClipRects()
2518 {
2519 for (const auto& info : this->Impl->ClipRectSet)
2520 {
2521 vtkNew<vtkXMLDataElement> clipPath;
2522 this->DefinitionNode->AddNestedElement(clipPath);
2523 clipPath->SetName("clipPath");
2524 clipPath->SetAttribute("id", info.Id.c_str());
2525
2526 // Get rect
2527 vtkNew<vtkXMLDataElement> rect;
2528 clipPath->AddNestedElement(rect);
2529 rect->SetName("rect");
2530 rect->SetAttribute("fill", "#000");
2531 rect->SetIntAttribute("x", info.Rect[0]);
2532 rect->SetIntAttribute("y", info.Rect[1]);
2533 rect->SetIntAttribute("width", info.Rect[2]);
2534 rect->SetIntAttribute("height", info.Rect[3]);
2535 }
2536 }
2537
2538 //------------------------------------------------------------------------------
AdjustMatrixForSVG(const double in[9],double out[9])2539 void vtkSVGContextDevice2D::AdjustMatrixForSVG(const double in[9], double out[9])
2540 {
2541 // Adjust the transform to account for the fact that SVG's y-axis is reversed:
2542 //
2543 // [S] = [T]^-1 [V] [T]
2544 //
2545 // [S] is the transform in SVG space (stored in this->Matrix).
2546 // [V] is the transform in VTK space (inputs from Context2D API).
2547 // | 1 0 0 |
2548 // [T] = | 0 -1 h | where h = viewport height.
2549 // | 0 0 1 | [T] flips the y axis.
2550 // Also, [T] = [T]^-1 in this case.
2551
2552 std::array<double, 9> tmpMat3;
2553 std::array<double, 9> VTKToSVG;
2554 vtkSVGContextDevice2D::GetSVGMatrix(VTKToSVG.data());
2555 vtkMatrix3x3::Multiply3x3(VTKToSVG.data(), in, tmpMat3.data());
2556 vtkMatrix3x3::Multiply3x3(tmpMat3.data(), VTKToSVG.data(), out);
2557 }
2558
2559 //------------------------------------------------------------------------------
GetSVGMatrix(double svg[9])2560 void vtkSVGContextDevice2D::GetSVGMatrix(double svg[9])
2561 {
2562 svg[0] = 1.;
2563 svg[1] = 0.;
2564 svg[2] = 0.;
2565 svg[3] = 0.;
2566 svg[4] = -1.;
2567 svg[5] = this->CanvasHeight;
2568 svg[6] = 0.;
2569 svg[7] = 0.;
2570 svg[8] = 1.;
2571 }
2572
2573 //------------------------------------------------------------------------------
Transform2DEqual(const double mat3[9],const double mat4[16])2574 bool vtkSVGContextDevice2D::Transform2DEqual(const double mat3[9], const double mat4[16])
2575 {
2576 const double tol = 1e-5;
2577
2578 const std::array<size_t, 6> mat3Map = { { 0, 1, 2, 3, 4, 5 } };
2579 const std::array<size_t, 6> mat4Map = { { 0, 1, 3, 4, 5, 7 } };
2580
2581 for (size_t i = 0; i < 6; ++i)
2582 {
2583 if (std::fabs(mat3[mat3Map[i]] - mat4[mat4Map[i]]) > tol)
2584 {
2585 return false;
2586 }
2587 }
2588
2589 return true;
2590 }
2591
2592 //------------------------------------------------------------------------------
Matrix3ToMatrix4(const double mat3[9],double mat4[16])2593 void vtkSVGContextDevice2D::Matrix3ToMatrix4(const double mat3[9], double mat4[16])
2594 {
2595 mat4[0] = mat3[0];
2596 mat4[1] = mat3[1];
2597 mat4[2] = 0.;
2598 mat4[3] = mat3[2];
2599 mat4[4] = mat3[3];
2600 mat4[5] = mat3[4];
2601 mat4[6] = 0.;
2602 mat4[7] = mat3[5];
2603 mat4[8] = 0.;
2604 mat4[9] = 0.;
2605 mat4[10] = 1.;
2606 mat4[11] = 0.;
2607 mat4[12] = 0.;
2608 mat4[13] = 0.;
2609 mat4[14] = 0.;
2610 mat4[15] = 1.;
2611 }
2612
2613 //------------------------------------------------------------------------------
Matrix4ToMatrix3(const double mat4[16],double mat3[9])2614 void vtkSVGContextDevice2D::Matrix4ToMatrix3(const double mat4[16], double mat3[9])
2615 {
2616 mat3[0] = mat4[0];
2617 mat3[1] = mat4[1];
2618 mat3[2] = mat4[3];
2619 mat3[3] = mat4[4];
2620 mat3[4] = mat4[5];
2621 mat3[5] = mat4[7];
2622 mat3[6] = 0.;
2623 mat3[7] = 0.;
2624 mat3[8] = 1.;
2625 }
2626
2627 //------------------------------------------------------------------------------
GetScaledPenWidth()2628 float vtkSVGContextDevice2D::GetScaledPenWidth()
2629 {
2630 float x, y;
2631 this->GetScaledPenWidth(x, y);
2632 return (x + y) * 0.5f;
2633 }
2634
2635 //------------------------------------------------------------------------------
GetScaledPenWidth(float & x,float & y)2636 void vtkSVGContextDevice2D::GetScaledPenWidth(float& x, float& y)
2637 {
2638 x = y = this->Pen->GetWidth();
2639 this->TransformSize(x, y);
2640 }
2641
2642 //------------------------------------------------------------------------------
TransformSize(float & x,float & y)2643 void vtkSVGContextDevice2D::TransformSize(float& x, float& y)
2644 {
2645 // Get current 3x3 SVG transform:
2646 std::array<double, 9> m;
2647 vtkSVGContextDevice2D::Matrix4ToMatrix3(this->Matrix->GetMatrix()->GetData(), m.data());
2648
2649 // Invert it (we want to go from local space --> global space)
2650 vtkMatrix3x3::Invert(m.data(), m.data());
2651
2652 // Extract the scale values:
2653 const double xScale = std::copysign(std::sqrt(m[0] * m[0] + m[1] * m[1]), m[0]);
2654 const double yScale = std::copysign(std::sqrt(m[3] * m[3] + m[4] * m[4]), m[4]);
2655
2656 x *= static_cast<float>(xScale);
2657 y *= static_cast<float>(yScale);
2658 }
2659
2660 //------------------------------------------------------------------------------
PreparePointSprite(vtkImageData * in)2661 vtkImageData* vtkSVGContextDevice2D::PreparePointSprite(vtkImageData* in)
2662 {
2663 int numComps = in->GetNumberOfScalarComponents();
2664
2665 // We'll only handle RGB / RGBA:
2666 if (numComps != 3 && numComps != 4)
2667 {
2668 vtkWarningMacro("Images with " << numComps << " components not supported.");
2669 return nullptr;
2670 }
2671
2672 // Need to convert scalar type?
2673 if (in->GetScalarType() != VTK_UNSIGNED_CHAR)
2674 {
2675 vtkNew<vtkImageCast> cast;
2676 cast->SetInputData(in);
2677 cast->SetOutputScalarTypeToUnsignedChar();
2678 cast->Update();
2679 in = cast->GetOutput();
2680 in->Register(this);
2681 }
2682 else
2683 {
2684 in->Register(this); // Keep refcounts consistent
2685 }
2686
2687 if (in->GetNumberOfScalarComponents() == 3)
2688 { // If RGB, append a constant alpha.
2689 vtkNew<vtkImageData> rgba;
2690 rgba->ShallowCopy(in);
2691
2692 vtkUnsignedCharArray* data =
2693 vtkArrayDownCast<vtkUnsignedCharArray>(rgba->GetPointData()->GetScalars());
2694 if (!data)
2695 {
2696 vtkErrorMacro("Internal error: vtkImageCast failed.");
2697 in->UnRegister(this);
2698 return nullptr;
2699 }
2700
2701 vtkIdType numTuples = data->GetNumberOfTuples();
2702 vtkNew<vtkUnsignedCharArray> newData;
2703 newData->SetNumberOfComponents(4);
2704 newData->SetNumberOfTuples(numTuples);
2705
2706 // Give the compiler an explicit hint about array strides so it can optimize
2707 // the memory accesses below:
2708 VTK_ASSUME(data->GetNumberOfComponents() == 3);
2709 VTK_ASSUME(newData->GetNumberOfComponents() == 4);
2710
2711 for (vtkIdType t = 0; t < numTuples; ++t)
2712 {
2713 newData->SetTypedComponent(t, 0, data->GetTypedComponent(t, 0));
2714 newData->SetTypedComponent(t, 1, data->GetTypedComponent(t, 1));
2715 newData->SetTypedComponent(t, 2, data->GetTypedComponent(t, 2));
2716 newData->SetTypedComponent(t, 3, 255);
2717 }
2718 rgba->GetPointData()->SetScalars(newData);
2719
2720 in->UnRegister(this);
2721 in = rgba;
2722 in->Register(this);
2723 }
2724
2725 return in;
2726 }
2727