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