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