Milena (Olena)  User documentation 2.0a Id
 All Classes Namespaces Functions Variables Typedefs Enumerator Groups Pages
simple_svg_1.0.0.hh
1 
2 /*******************************************************************************
3 * The "New BSD License" : http://www.opensource.org/licenses/bsd-license.php *
4 ********************************************************************************
5 
6 Copyright (c) 2010, Mark Turney
7 All rights reserved.
8 
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11  * Redistributions of source code must retain the above copyright
12  notice, this list of conditions and the following disclaimer.
13  * Redistributions in binary form must reproduce the above copyright
14  notice, this list of conditions and the following disclaimer in the
15  documentation and/or other materials provided with the distribution.
16  * Neither the name of the <organization> nor the
17  names of its contributors may be used to endorse or promote products
18  derived from this software without specific prior written permission.
19 
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
24 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 ******************************************************************************/
32 
33 #ifndef SIMPLE_SVG_HPP
34 #define SIMPLE_SVG_HPP
35 
36 #include <vector>
37 #include <string>
38 #include <sstream>
39 #include <fstream>
40 
41 #include <iostream>
42 
43 namespace svg
44 {
45  // Utility XML/String Functions.
46  template <typename T>
47  std::string attribute(std::string const & attribute_name,
48  T const & value, std::string const & unit = "")
49  {
50  std::stringstream ss;
51  ss << attribute_name << "=\"" << value << unit << "\" ";
52  return ss.str();
53  }
54  std::string elemStart(std::string const & element_name)
55  {
56  return "\t<" + element_name + " ";
57  }
58  std::string elemEnd(std::string const & element_name)
59  {
60  return "</" + element_name + ">\n";
61  }
62  std::string emptyElemEnd()
63  {
64  return "/>\n";
65  }
66 
67  // Quick optional return type. This allows functions to return an invalid
68  // value if no good return is possible. The user checks for validity
69  // before using the returned value.
70  template <typename T>
71  class optional
72  {
73  public:
74  optional<T>(T const & type)
75  : valid(true), type(type) { }
76  optional<T>() : valid(false), type(T()) { }
77  T * operator->()
78  {
79  // If we try to access an invalid value, an exception is thrown.
80  if (!valid)
81  throw std::exception();
82 
83  return &type;
84  }
85  // Test for validity.
86  bool operator!() const { return !valid; }
87  private:
88  bool valid;
89  T type;
90  };
91 
92  struct Dimensions
93  {
94  Dimensions(double width, double height) : width(width), height(height) { }
95  Dimensions(double combined = 0) : width(combined), height(combined) { }
96  double width;
97  double height;
98  };
99 
100  struct Point
101  {
102  Point(double x = 0, double y = 0) : x(x), y(y) { }
103  double x;
104  double y;
105  };
106  optional<Point> getMinPoint(std::vector<Point> const & points)
107  {
108  if (points.empty())
109  return optional<Point>();
110 
111  Point min = points[0];
112  for (unsigned i = 0; i < points.size(); ++i) {
113  if (points[i].x < min.x)
114  min.x = points[i].x;
115  if (points[i].y < min.y)
116  min.y = points[i].y;
117  }
118  return optional<Point>(min);
119  }
120  optional<Point> getMaxPoint(std::vector<Point> const & points)
121  {
122  if (points.empty())
123  return optional<Point>();
124 
125  Point max = points[0];
126  for (unsigned i = 0; i < points.size(); ++i) {
127  if (points[i].x > max.x)
128  max.x = points[i].x;
129  if (points[i].y > max.y)
130  max.y = points[i].y;
131  }
132  return optional<Point>(max);
133  }
134 
135  // Defines the dimensions, scale, origin, and origin offset of the document.
136  struct Layout
137  {
138  enum Origin { TopLeft, BottomLeft, TopRight, BottomRight };
139 
140  Layout(Dimensions const & dimensions = Dimensions(400, 300), Origin origin = BottomLeft,
141  double scale = 1, Point const & origin_offset = Point(0, 0))
142  : dimensions(dimensions), scale(scale), origin(origin), origin_offset(origin_offset) { }
143  Dimensions dimensions;
144  double scale;
145  Origin origin;
146  Point origin_offset;
147  };
148 
149  // Convert coordinates in user space to SVG native space.
150  double translateX(double x, Layout const & layout)
151  {
152  if (layout.origin == Layout::BottomRight || layout.origin == Layout::TopRight)
153  return layout.dimensions.width - ((x + layout.origin_offset.x) * layout.scale);
154  else
155  return (layout.origin_offset.x + x) * layout.scale;
156  }
157 
158  double translateY(double y, Layout const & layout)
159  {
160  if (layout.origin == Layout::BottomLeft || layout.origin == Layout::BottomRight)
161  return layout.dimensions.height - ((y + layout.origin_offset.y) * layout.scale);
162  else
163  return (layout.origin_offset.y + y) * layout.scale;
164  }
165  double translateScale(double dimension, Layout const & layout)
166  {
167  return dimension * layout.scale;
168  }
169 
170  class Serializeable
171  {
172  public:
173  Serializeable() { }
174  virtual ~Serializeable() { };
175  virtual std::string toString(Layout const & layout) const = 0;
176  };
177 
178  class Color : public Serializeable
179  {
180  public:
181  enum Defaults { Transparent = -1, Aqua, Black, Blue, Brown, Cyan, Fuchsia,
182  Green, Lime, Magenta, Orange, Purple, Red, Silver, White, Yellow };
183 
184  Color(int r, int g, int b) : transparent(false), red(r), green(g), blue(b) { }
185  Color(Defaults color)
186  : transparent(false), red(0), green(0), blue(0)
187  {
188  switch (color)
189  {
190  case Aqua: assign(0, 255, 255); break;
191  case Black: assign(0, 0, 0); break;
192  case Blue: assign(0, 0, 255); break;
193  case Brown: assign(165, 42, 42); break;
194  case Cyan: assign(0, 255, 255); break;
195  case Fuchsia: assign(255, 0, 255); break;
196  case Green: assign(0, 128, 0); break;
197  case Lime: assign(0, 255, 0); break;
198  case Magenta: assign(255, 0, 255); break;
199  case Orange: assign(255, 165, 0); break;
200  case Purple: assign(128, 0, 128); break;
201  case Red: assign(255, 0, 0); break;
202  case Silver: assign(192, 192, 192); break;
203  case White: assign(255, 255, 255); break;
204  case Yellow: assign(255, 255, 0); break;
205  default: transparent = true; break;
206  }
207  }
208  virtual ~Color() { }
209  std::string toString(Layout const &) const
210  {
211  std::stringstream ss;
212  if (transparent)
213  ss << "transparent";
214  else
215  ss << "rgb(" << red << "," << green << "," << blue << ")";
216  return ss.str();
217  }
218  private:
219  bool transparent;
220  int red;
221  int green;
222  int blue;
223 
224  void assign(int r, int g, int b)
225  {
226  red = r;
227  green = g;
228  blue = b;
229  }
230  };
231 
232  class Fill : public Serializeable
233  {
234  public:
235  Fill(Color::Defaults color) : color(color) { }
236  Fill(Color color = Color::Transparent)
237  : color(color) { }
238  std::string toString(Layout const & layout) const
239  {
240  std::stringstream ss;
241  ss << attribute("fill", color.toString(layout));
242  return ss.str();
243  }
244  private:
245  Color color;
246  };
247 
248  class Stroke : public Serializeable
249  {
250  public:
251  Stroke(double width = -1, Color color = Color::Transparent)
252  : width(width), color(color) { }
253  std::string toString(Layout const & layout) const
254  {
255  // If stroke width is invalid.
256  if (width < 0)
257  return std::string();
258 
259  std::stringstream ss;
260  ss << attribute("stroke-width", translateScale(width, layout)) << attribute("stroke", color.toString(layout));
261  return ss.str();
262  }
263  private:
264  double width;
265  Color color;
266  };
267 
268  class Font : public Serializeable
269  {
270  public:
271  Font(double size = 12, std::string const & family = "Verdana") : size(size), family(family) { }
272  std::string toString(Layout const & layout) const
273  {
274  std::stringstream ss;
275  ss << attribute("font-size", translateScale(size, layout)) << attribute("font-family", family);
276  return ss.str();
277  }
278  private:
279  double size;
280  std::string family;
281  };
282 
283  class Shape : public Serializeable
284  {
285  public:
286  Shape(Fill const & fill = Fill(), Stroke const & stroke = Stroke())
287  : fill(fill), stroke(stroke) { }
288  virtual ~Shape() { }
289  virtual std::string toString(Layout const & layout) const = 0;
290  virtual void offset(Point const & offset) = 0;
291  protected:
292  Fill fill;
293  Stroke stroke;
294  };
295  template <typename T>
296  std::string vectorToString(std::vector<T> collection, Layout const & layout)
297  {
298  std::string combination_str;
299  for (unsigned i = 0; i < collection.size(); ++i)
300  combination_str += collection[i].toString(layout);
301 
302  return combination_str;
303  }
304 
305  class Circle : public Shape
306  {
307  public:
308  Circle(Point const & center, double diameter, Fill const & fill,
309  Stroke const & stroke = Stroke())
310  : Shape(fill, stroke), center(center), radius(diameter / 2) { }
311  std::string toString(Layout const & layout) const
312  {
313  std::stringstream ss;
314  ss << elemStart("circle") << attribute("cx", translateX(center.x, layout))
315  << attribute("cy", translateY(center.y, layout))
316  << attribute("r", translateScale(radius, layout)) << fill.toString(layout)
317  << stroke.toString(layout) << emptyElemEnd();
318  return ss.str();
319  }
320  void offset(Point const & offset)
321  {
322  center.x += offset.x;
323  center.y += offset.y;
324  }
325  private:
326  Point center;
327  double radius;
328  };
329 
330  class Elipse : public Shape
331  {
332  public:
333  Elipse(Point const & center, double width, double height,
334  Fill const & fill = Fill(), Stroke const & stroke = Stroke())
335  : Shape(fill, stroke), center(center), radius_width(width / 2),
336  radius_height(height / 2) { }
337  std::string toString(Layout const & layout) const
338  {
339  std::stringstream ss;
340  ss << elemStart("ellipse") << attribute("cx", translateX(center.x, layout))
341  << attribute("cy", translateY(center.y, layout))
342  << attribute("rx", translateScale(radius_width, layout))
343  << attribute("ry", translateScale(radius_height, layout))
344  << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
345  return ss.str();
346  }
347  void offset(Point const & offset)
348  {
349  center.x += offset.x;
350  center.y += offset.y;
351  }
352  private:
353  Point center;
354  double radius_width;
355  double radius_height;
356  };
357 
358  class Rectangle : public Shape
359  {
360  public:
361  Rectangle(Point const & edge, double width, double height,
362  Fill const & fill = Fill(), Stroke const & stroke = Stroke())
363  : Shape(fill, stroke), edge(edge), width(width),
364  height(height) { }
365  std::string toString(Layout const & layout) const
366  {
367  std::stringstream ss;
368  ss << elemStart("rect") << attribute("x", translateX(edge.x, layout))
369  << attribute("y", translateY(edge.y, layout))
370  << attribute("width", translateScale(width, layout))
371  << attribute("height", translateScale(height, layout))
372  << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
373  return ss.str();
374  }
375  void offset(Point const & offset)
376  {
377  edge.x += offset.x;
378  edge.y += offset.y;
379  }
380  private:
381  Point edge;
382  double width;
383  double height;
384  };
385 
386  class Line : public Shape
387  {
388  public:
389  Line(Point const & start_point, Point const & end_point,
390  Stroke const & stroke = Stroke())
391  : Shape(Fill(), stroke), start_point(start_point),
392  end_point(end_point) { }
393  std::string toString(Layout const & layout) const
394  {
395  std::stringstream ss;
396  ss << elemStart("line") << attribute("x1", translateX(start_point.x, layout))
397  << attribute("y1", translateY(start_point.y, layout))
398  << attribute("x2", translateX(end_point.x, layout))
399  << attribute("y2", translateY(end_point.y, layout))
400  << stroke.toString(layout) << emptyElemEnd();
401  return ss.str();
402  }
403  void offset(Point const & offset)
404  {
405  start_point.x += offset.x;
406  start_point.y += offset.y;
407 
408  end_point.x += offset.x;
409  end_point.y += offset.y;
410  }
411  private:
412  Point start_point;
413  Point end_point;
414  };
415 
416  class Polygon : public Shape
417  {
418  public:
419  Polygon(Fill const & fill = Fill(), Stroke const & stroke = Stroke())
420  : Shape(fill, stroke) { }
421  Polygon(Stroke const & stroke = Stroke()) : Shape(Color::Transparent, stroke) { }
422  Polygon & operator<<(Point const & point)
423  {
424  points.push_back(point);
425  return *this;
426  }
427  std::string toString(Layout const & layout) const
428  {
429  std::stringstream ss;
430  ss << elemStart("polygon");
431 
432  ss << "points=\"";
433  for (unsigned i = 0; i < points.size(); ++i)
434  ss << translateX(points[i].x, layout) << "," << translateY(points[i].y, layout) << " ";
435  ss << "\" ";
436 
437  ss << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
438  return ss.str();
439  }
440  void offset(Point const & offset)
441  {
442  for (unsigned i = 0; i < points.size(); ++i) {
443  points[i].x += offset.x;
444  points[i].y += offset.y;
445  }
446  }
447  private:
448  std::vector<Point> points;
449  };
450 
451  class Polyline : public Shape
452  {
453  public:
454  Polyline(Fill const & fill = Fill(), Stroke const & stroke = Stroke())
455  : Shape(fill, stroke) { }
456  Polyline(Stroke const & stroke = Stroke()) : Shape(Color::Transparent, stroke) { }
457  Polyline(std::vector<Point> const & points,
458  Fill const & fill = Fill(), Stroke const & stroke = Stroke())
459  : Shape(fill, stroke), points(points) { }
460  Polyline & operator<<(Point const & point)
461  {
462  points.push_back(point);
463  return *this;
464  }
465  std::string toString(Layout const & layout) const
466  {
467  std::stringstream ss;
468  ss << elemStart("polyline");
469 
470  ss << "points=\"";
471  for (unsigned i = 0; i < points.size(); ++i)
472  ss << translateX(points[i].x, layout) << "," << translateY(points[i].y, layout) << " ";
473  ss << "\" ";
474 
475  ss << fill.toString(layout) << stroke.toString(layout) << emptyElemEnd();
476  return ss.str();
477  }
478  void offset(Point const & offset)
479  {
480  for (unsigned i = 0; i < points.size(); ++i) {
481  points[i].x += offset.x;
482  points[i].y += offset.y;
483  }
484  }
485  std::vector<Point> points;
486  };
487 
488  class Text : public Shape
489  {
490  public:
491  Text(Point const & origin, std::string const & content, Fill const & fill = Fill(),
492  Font const & font = Font(), Stroke const & stroke = Stroke())
493  : Shape(fill, stroke), origin(origin), content(content), font(font) { }
494  std::string toString(Layout const & layout) const
495  {
496  std::stringstream ss;
497  ss << elemStart("text") << attribute("x", translateX(origin.x, layout))
498  << attribute("y", translateY(origin.y, layout))
499  << fill.toString(layout) << stroke.toString(layout) << font.toString(layout)
500  << ">" << content << elemEnd("text");
501  return ss.str();
502  }
503  void offset(Point const & offset)
504  {
505  origin.x += offset.x;
506  origin.y += offset.y;
507  }
508  private:
509  Point origin;
510  std::string content;
511  Font font;
512  };
513 
514  // Sample charting class.
515  class LineChart : public Shape
516  {
517  public:
518  LineChart(Dimensions margin = Dimensions(), double scale = 1,
519  Stroke const & axis_stroke = Stroke(.5, Color::Purple))
520  : axis_stroke(axis_stroke), margin(margin), scale(scale) { }
521  LineChart & operator<<(Polyline const & polyline)
522  {
523  if (polyline.points.empty())
524  return *this;
525 
526  polylines.push_back(polyline);
527  return *this;
528  }
529  std::string toString(Layout const & layout) const
530  {
531  if (polylines.empty())
532  return "";
533 
534  std::string ret;
535  for (unsigned i = 0; i < polylines.size(); ++i)
536  ret += polylineToString(polylines[i], layout);
537 
538  return ret + axisString(layout);
539  }
540  void offset(Point const & offset)
541  {
542  for (unsigned i = 0; i < polylines.size(); ++i)
543  polylines[i].offset(offset);
544  }
545  private:
546  Stroke axis_stroke;
547  Dimensions margin;
548  double scale;
549  std::vector<Polyline> polylines;
550 
551  optional<Dimensions> getDimensions() const
552  {
553  if (polylines.empty())
554  return optional<Dimensions>();
555 
556  optional<Point> min = getMinPoint(polylines[0].points);
557  optional<Point> max = getMaxPoint(polylines[0].points);
558  for (unsigned i = 0; i < polylines.size(); ++i) {
559  if (getMinPoint(polylines[i].points)->x < min->x)
560  min->x = getMinPoint(polylines[i].points)->x;
561  if (getMinPoint(polylines[i].points)->y < min->y)
562  min->y = getMinPoint(polylines[i].points)->y;
563  if (getMaxPoint(polylines[i].points)->x > max->x)
564  max->x = getMaxPoint(polylines[i].points)->x;
565  if (getMaxPoint(polylines[i].points)->y > max->y)
566  max->y = getMaxPoint(polylines[i].points)->y;
567  }
568 
569  return optional<Dimensions>(Dimensions(max->x - min->x, max->y - min->y));
570  }
571  std::string axisString(Layout const & layout) const
572  {
573  optional<Dimensions> dimensions = getDimensions();
574  if (!dimensions)
575  return "";
576 
577  // Make the axis 10% wider and higher than the data points.
578  double width = dimensions->width * 1.1;
579  double height = dimensions->height * 1.1;
580 
581  // Draw the axis.
582  Polyline axis(Color::Transparent, axis_stroke);
583  axis << Point(margin.width, margin.height + height) << Point(margin.width, margin.height)
584  << Point(margin.width + width, margin.height);
585 
586  return axis.toString(layout);
587  }
588  std::string polylineToString(Polyline const & polyline, Layout const & layout) const
589  {
590  Polyline shifted_polyline = polyline;
591  shifted_polyline.offset(Point(margin.width, margin.height));
592 
593  std::vector<Circle> vertices;
594  for (unsigned i = 0; i < shifted_polyline.points.size(); ++i)
595  vertices.push_back(Circle(shifted_polyline.points[i], getDimensions()->height / 30.0, Color::Black));
596 
597  return shifted_polyline.toString(layout) + vectorToString(vertices, layout);
598  }
599  };
600 
601  class Document
602  {
603  public:
604  Document(std::string const & file_name, Layout layout = Layout())
605  : file_name(file_name), layout(layout) { }
606 
607  Document & operator<<(Shape const & shape)
608  {
609  body_nodes_str += shape.toString(layout);
610  return *this;
611  }
612  std::string toString() const
613  {
614  std::stringstream ss;
615  ss << "<?xml " << attribute("version", "1.0") << attribute("standalone", "no")
616  << "?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
617  << "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n<svg "
618  << attribute("width", layout.dimensions.width, "px")
619  << attribute("height", layout.dimensions.height, "px")
620  << attribute("xmlns", "http://www.w3.org/2000/svg")
621  << attribute("version", "1.1") << ">\n" << body_nodes_str << elemEnd("svg");
622  return ss.str();
623  }
624  bool save() const
625  {
626  std::ofstream ofs(file_name.c_str());
627  if (!ofs.good())
628  return false;
629 
630  ofs << toString();
631  ofs.close();
632  return true;
633  }
634  private:
635  std::string file_name;
636  Layout layout;
637 
638  std::string body_nodes_str;
639  };
640 }
641 
642 #endif