1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2016-2018, Torsten Paul <torsten.paul@gmx.de>,
5 * Marius Kintel <marius@kintel.net>
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 * and/or sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25 #include <stdio.h>
26 #include <string>
27 #include <vector>
28
29 #include <boost/tokenizer.hpp>
30 #include <boost/algorithm/string.hpp>
31 #include <boost/spirit/include/qi.hpp>
32
33 #include "shape.h"
34 #include "circle.h"
35 #include "ellipse.h"
36 #include "line.h"
37 #include "text.h"
38 #include "tspan.h"
39 #include "data.h"
40 #include "polygon.h"
41 #include "polyline.h"
42 #include "rect.h"
43 #include "svgpage.h"
44 #include "path.h"
45 #include "group.h"
46
47 #include "transformation.h"
48 #include "degree_trig.h"
49
50 namespace libsvg {
51
shape()52 shape::shape() : parent(nullptr), x(0), y(0)
53 {
54 }
55
~shape()56 shape::~shape()
57 {
58 }
59
60 shape *
create_from_name(const char * name)61 shape::create_from_name(const char *name)
62 {
63 if (circle::name == name) {
64 return new circle();
65 } else if (ellipse::name == name) {
66 return new ellipse();
67 } else if (line::name == name) {
68 return new line();
69 } else if (text::name == name) {
70 return new text();
71 } else if (tspan::name == name) {
72 return new tspan();
73 } else if (data::name == name) {
74 return new data();
75 } else if (polygon::name == name) {
76 return new polygon();
77 } else if (polyline::name == name) {
78 return new polyline();
79 } else if (rect::name == name) {
80 return new rect();
81 } else if (svgpage::name == name) {
82 return new svgpage();
83 } else if (path::name == name) {
84 return new path();
85 } else if (group::name == name) {
86 return new group();
87 } else {
88 return nullptr;
89 }
90 }
91
92 void
set_attrs(attr_map_t & attrs)93 shape::set_attrs(attr_map_t& attrs)
94 {
95 this->id = attrs["id"];
96 this->transform = attrs["transform"];
97 this->stroke_width = attrs["stroke-width"];
98 this->stroke_linecap = attrs["stroke-linecap"];
99 this->stroke_linejoin = attrs["stroke-linejoin"];
100 this->style = attrs["style"];
101 }
102
103 const std::string
get_style(std::string name) const104 shape::get_style(std::string name) const
105 {
106 std::vector<std::string> styles;
107 boost::split(styles, this->style, boost::is_any_of(";"));
108
109 for (const auto& style : styles) {
110 std::vector<std::string> values;
111 boost::split(values, style, boost::is_any_of(":"));
112 if (values.size() != 2) {
113 continue;
114 }
115 if (name == values[0]) {
116 return values[1];
117 }
118 }
119 return std::string();
120 }
121
122 double
get_stroke_width() const123 shape::get_stroke_width() const
124 {
125 double stroke_width;
126 if (this->stroke_width.empty()) {
127 stroke_width = parse_double(get_style("stroke-width"));
128 } else {
129 stroke_width = parse_double(this->stroke_width);
130 }
131 return stroke_width < 0.01 ? 1 : stroke_width;
132 }
133
134 ClipperLib::EndType
get_stroke_linecap() const135 shape::get_stroke_linecap() const
136 {
137 std::string cap;
138 if (this->stroke_linecap.empty()) {
139 cap = get_style("stroke-linecap");
140 } else {
141 cap = this->stroke_linecap;
142 }
143
144 if (cap == "butt") {
145 return ClipperLib::etOpenButt;
146 } else if (cap == "round") {
147 return ClipperLib::etOpenRound;
148 } else if (cap == "square") {
149 return ClipperLib::etOpenSquare;
150 }
151 return ClipperLib::etOpenSquare;
152 }
153
154 ClipperLib::JoinType
get_stroke_linejoin() const155 shape::get_stroke_linejoin() const
156 {
157 std::string join;
158 if (this->stroke_linejoin.empty()) {
159 join = get_style("stroke-linejoin");
160 } else {
161 join = this->stroke_linejoin;
162 }
163 if (join == "bevel") {
164 return ClipperLib::jtSquare;
165 } else if (join == "round") {
166 return ClipperLib::jtRound;
167 } else if (join == "square") {
168 return ClipperLib::jtMiter;
169 }
170 return ClipperLib::jtMiter;
171 }
172
173 void
collect_transform_matrices(std::vector<Eigen::Matrix3d> & matrices,shape * s)174 shape::collect_transform_matrices(std::vector<Eigen::Matrix3d>& matrices, shape *s)
175 {
176 std::string transform_arg(s->transform);
177
178 boost::replace_all(transform_arg, "matrix", "m");
179 boost::replace_all(transform_arg, "translate", "t");
180 boost::replace_all(transform_arg, "scale", "s");
181 boost::replace_all(transform_arg, "rotate", "r");
182 boost::replace_all(transform_arg, "skewX", "x");
183 boost::replace_all(transform_arg, "skewY", "y");
184
185 std::string commands = "mtsrxy";
186
187 using tokenizer = boost::tokenizer<boost::char_separator<char> >;
188 boost::char_separator<char> sep(" ,()", commands.c_str());
189 tokenizer tokens(transform_arg, sep);
190
191 transformation *t = nullptr;
192 std::vector<transformation *> transformations;
193 for (const auto& v : tokens) {
194 if ((v.length() == 1) && (commands.find(v) != std::string::npos)) {
195 if (t != nullptr) {
196 transformations.push_back(t);
197 t = nullptr;
198 }
199 switch (v[0]) {
200 case 'm':
201 t = new matrix();
202 break;
203 case 't':
204 t = new translate();
205 break;
206 case 's':
207 t = new scale();
208 break;
209 case 'r':
210 t = new rotate();
211 break;
212 case 'x':
213 t = new skew_x();
214 break;
215 case 'y':
216 t = new skew_y();
217 break;
218 default:
219 std::cout << "unknown transform op " << v << std::endl;
220 t = nullptr;
221 }
222 } else {
223 if (t) {
224 t->add_arg(v);
225 }
226 }
227 }
228 if (t != nullptr) {
229 transformations.push_back(t);
230 }
231
232 for (std::vector<transformation *>::reverse_iterator it = transformations.rbegin(); it != transformations.rend(); ++it) {
233 transformation *t = *it;
234 std::vector<Eigen::Matrix3d> m = t->get_matrices();
235 matrices.insert(matrices.begin(), m.rbegin(), m.rend());
236 delete t;
237 }
238 }
239
240 void
apply_transform()241 shape::apply_transform()
242 {
243 std::vector<Eigen::Matrix3d> matrices;
244 for (shape *s = this;s->get_parent() != nullptr;s = s->get_parent()) {
245 collect_transform_matrices(matrices, s);
246 }
247
248 path_list_t result_list;
249 for (const auto& p : path_list) {
250 result_list.push_back(path_t());
251 for (const auto &v : p) {
252 Eigen::Vector3d result(v.x(), v.y(), 1);
253 for (std::vector<Eigen::Matrix3d>::reverse_iterator it3 = matrices.rbegin(); it3 != matrices.rend(); ++it3) {
254 result = *it3 * result;
255 }
256
257 result_list.back().push_back(result);
258 }
259 }
260 path_list = result_list;
261 }
262
263 void
offset_path(path_list_t & path_list,path_t & path,double stroke_width,ClipperLib::EndType stroke_linecap)264 shape::offset_path(path_list_t& path_list, path_t& path, double stroke_width, ClipperLib::EndType stroke_linecap) {
265 ClipperLib::Path line;
266 ClipperLib::Paths result;
267 for (const auto& v : path) {
268 line << ClipperLib::IntPoint(v.x() * 10000, v.y() * 10000);
269 }
270
271 ClipperLib::ClipperOffset co;
272 co.AddPath(line, get_stroke_linejoin(), stroke_linecap);
273 co.Execute(result, stroke_width * 5000.0);
274
275 for (const auto& p : result) {
276 path_list.push_back(path_t());
277 for (const auto &point : p) {
278 path_list.back().push_back(Eigen::Vector3d(point.X / 10000.0, point.Y / 10000.0, 0));
279 }
280 path_list.back().push_back(Eigen::Vector3d(p[0].X / 10000.0, p[0].Y / 10000.0, 0));
281 }
282 }
283
284 void
draw_ellipse(path_t & path,double x,double y,double rx,double ry)285 shape::draw_ellipse(path_t& path, double x, double y, double rx, double ry) {
286 unsigned long fn = 40;
287 for (unsigned long idx = 1; idx <= fn; ++idx) {
288 const double a = idx * 360.0 / fn;
289 const double xx = rx * sin_degrees(a) + x;
290 const double yy = ry * cos_degrees(a) + y;
291 path.push_back(Eigen::Vector3d(xx, yy, 0));
292 }
293 }
294
operator <<(std::ostream & os,const shape & s)295 std::ostream & operator<<(std::ostream &os, const shape& s)
296 {
297 return os << s.dump() << " | id = '" << s.id << "', transform = '" << s.transform << "'";
298 }
299
300 }
301