/src/libzmf/src/lib/ZMFCollector.cpp
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* |
3 | | * This file is a part of the libzmf project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | */ |
9 | | |
10 | | #include "ZMFCollector.h" |
11 | | |
12 | | #include <algorithm> |
13 | | #include <iterator> |
14 | | #include <string> |
15 | | #include <boost/math/constants/constants.hpp> |
16 | | #include <boost/variant.hpp> |
17 | | |
18 | | namespace libzmf |
19 | | { |
20 | | |
21 | | namespace |
22 | | { |
23 | | |
24 | | Point calculateEllipsePoint(const Point &c, double rx, double ry, double angle) |
25 | 22.5k | { |
26 | 22.5k | return Point(c.x + rx * std::cos(angle), c.y + ry * std::sin(angle)); |
27 | 22.5k | } |
28 | | |
29 | | void writeBorder(librevenge::RVNGPropertyList &propList, const char *const name, const Pen &pen) |
30 | 386k | { |
31 | 386k | if (pen.isInvisible) |
32 | 61.6k | return; |
33 | | |
34 | 325k | librevenge::RVNGString border; |
35 | | |
36 | 325k | border.sprintf("%fin", pen.width); |
37 | | |
38 | 325k | border.append(" solid"); |
39 | | |
40 | 325k | border.append(" "); |
41 | 325k | border.append(pen.color.toString()); |
42 | | |
43 | 325k | propList.insert(name, border); |
44 | 325k | } |
45 | | |
46 | | librevenge::RVNGPropertyListVector createPath(const std::vector<Curve> &curves, |
47 | | double leftOffset = 0.0, double topOffset = 0.0) |
48 | 122k | { |
49 | 122k | librevenge::RVNGPropertyListVector path; |
50 | | |
51 | 122k | for (const auto &curve : curves) |
52 | 58.2k | { |
53 | 58.2k | if (curve.points.size() < 2) |
54 | 119 | continue; |
55 | | |
56 | 58.1k | { |
57 | 58.1k | librevenge::RVNGPropertyList pathPart; |
58 | 58.1k | pathPart.insert("librevenge:path-action", "M"); |
59 | 58.1k | pathPart.insert("svg:x", curve.points[0].x - leftOffset); |
60 | 58.1k | pathPart.insert("svg:y", curve.points[0].y - topOffset); |
61 | | |
62 | 58.1k | path.append(pathPart); |
63 | 58.1k | } |
64 | | |
65 | 58.1k | size_t i = 1; |
66 | | |
67 | 58.1k | for (const auto §ionType : curve.sectionTypes) |
68 | 492k | { |
69 | 492k | librevenge::RVNGPropertyList pathPart; |
70 | | |
71 | 492k | switch (sectionType) |
72 | 492k | { |
73 | 0 | default: |
74 | 473k | case CurveType::LINE: |
75 | 473k | if (i >= curve.points.size()) |
76 | 6.45k | { |
77 | 6.45k | ZMF_DEBUG_MSG(("Unexpected end of curve points\n")); |
78 | 6.45k | break; |
79 | 6.45k | } |
80 | 466k | pathPart.insert("librevenge:path-action", "L"); |
81 | 466k | pathPart.insert("svg:x", curve.points[i].x - leftOffset); |
82 | 466k | pathPart.insert("svg:y", curve.points[i].y - topOffset); |
83 | 466k | i++; |
84 | 466k | break; |
85 | 18.9k | case CurveType::BEZIER_CURVE: |
86 | 18.9k | if (i + 2 >= curve.points.size()) |
87 | 2.32k | { |
88 | 2.32k | ZMF_DEBUG_MSG(("Unexpected end of curve points\n")); |
89 | 2.32k | break; |
90 | 2.32k | } |
91 | 16.6k | pathPart.insert("librevenge:path-action", "C"); |
92 | 16.6k | pathPart.insert("svg:x1", curve.points[i].x - leftOffset); |
93 | 16.6k | pathPart.insert("svg:y1", curve.points[i].y - topOffset); |
94 | 16.6k | pathPart.insert("svg:x2", curve.points[i + 1].x - leftOffset); |
95 | 16.6k | pathPart.insert("svg:y2", curve.points[i + 1].y - topOffset); |
96 | 16.6k | pathPart.insert("svg:x", curve.points[i + 2].x - leftOffset); |
97 | 16.6k | pathPart.insert("svg:y", curve.points[i + 2].y - topOffset); |
98 | 16.6k | i += 3; |
99 | 16.6k | break; |
100 | 492k | } |
101 | | |
102 | 492k | path.append(pathPart); |
103 | 492k | } |
104 | | |
105 | 58.1k | if (curve.closed) |
106 | 57.6k | { |
107 | 57.6k | librevenge::RVNGPropertyList pathPart; |
108 | 57.6k | pathPart.insert("librevenge:path-action", "Z"); |
109 | | |
110 | 57.6k | path.append(pathPart); |
111 | 57.6k | } |
112 | 58.1k | } |
113 | | |
114 | 122k | return path; |
115 | 122k | } |
116 | | |
117 | | librevenge::RVNGString getPathStr(const librevenge::RVNGPropertyListVector &path) |
118 | 48.6k | { |
119 | 48.6k | librevenge::RVNGString s(""); |
120 | | |
121 | 84.4k | for (unsigned long i = 0; i < path.count(); ++i) |
122 | 35.7k | { |
123 | 35.7k | if (!path[i]["librevenge:path-action"]) |
124 | 949 | continue; |
125 | | |
126 | 34.8k | std::string action = path[i]["librevenge:path-action"]->getStr().cstr(); |
127 | 34.8k | bool coordOk = path[i]["svg:x"] && path[i]["svg:y"]; |
128 | 34.8k | bool coord1Ok = coordOk && path[i]["svg:x1"] && path[i]["svg:y1"]; |
129 | 34.8k | bool coord2Ok = coord1Ok && path[i]["svg:x2"] && path[i]["svg:y2"]; |
130 | | |
131 | 34.8k | librevenge::RVNGString sElement; |
132 | | |
133 | 34.8k | switch (action[0]) |
134 | 34.8k | { |
135 | 2.88k | case 'M': |
136 | 29.4k | case 'L': |
137 | 29.4k | if (!coordOk) |
138 | 0 | { |
139 | 0 | ZMF_DEBUG_MSG(("Incorrect path coordinates\n")); |
140 | 0 | break; |
141 | 0 | } |
142 | 29.4k | sElement.sprintf("%c%lf %lf ", action[0], path[i]["svg:x"]->getDouble(), path[i]["svg:y"]->getDouble()); |
143 | 29.4k | s.append(sElement); |
144 | 29.4k | break; |
145 | 2.85k | case 'C': |
146 | 2.85k | if (!coord2Ok) |
147 | 0 | { |
148 | 0 | ZMF_DEBUG_MSG(("Incorrect path coordinates\n")); |
149 | 0 | break; |
150 | 0 | } |
151 | 2.85k | sElement.sprintf("C%lf %lf %lf %lf %lf %lf ", |
152 | 2.85k | path[i]["svg:x1"]->getDouble(), path[i]["svg:y1"]->getDouble(), |
153 | 2.85k | path[i]["svg:x2"]->getDouble(), path[i]["svg:y2"]->getDouble(), |
154 | 2.85k | path[i]["svg:x"]->getDouble(), path[i]["svg:y"]->getDouble()); |
155 | 2.85k | s.append(sElement); |
156 | 2.85k | break; |
157 | 2.52k | case 'Z': |
158 | 2.52k | s.append("Z "); |
159 | 2.52k | break; |
160 | 0 | default: |
161 | 0 | ZMF_DEBUG_MSG(("Unknown path-action %s\n", action.c_str())); |
162 | 34.8k | } |
163 | 34.8k | } |
164 | | |
165 | 48.6k | return s; |
166 | 48.6k | } |
167 | | |
168 | | void writeArrow(librevenge::RVNGPropertyList &propList, const char *const name, Arrow arrow, double penWidth) |
169 | 48.6k | { |
170 | 48.6k | using namespace boost::math::double_constants; |
171 | | |
172 | 48.6k | Point lineEnd(arrow.lineEndX, 0); |
173 | | |
174 | 48.6k | lineEnd = lineEnd.rotate(half_pi, Point(0, 0)); |
175 | | |
176 | 48.6k | double dist = 1; |
177 | | |
178 | 48.6k | for (auto &curve : arrow.curves) |
179 | 2.99k | { |
180 | 2.99k | auto minmaxY = std::minmax_element(curve.points.begin(), curve.points.end(), [](const Point &p1, const Point &p2) |
181 | 146k | { |
182 | 146k | return p1.y < p2.y; |
183 | 146k | }); |
184 | | |
185 | 2.99k | dist = std::max(dist, std::abs(minmaxY.first->y - minmaxY.second->y)); |
186 | | |
187 | 2.99k | std::transform(curve.points.begin(), curve.points.end(), curve.points.begin(), [&lineEnd](const Point &p) |
188 | 101k | { |
189 | 101k | return p.rotate(half_pi, Point(0, 0)).move(0, -lineEnd.y); |
190 | 101k | }); |
191 | 2.99k | } |
192 | | |
193 | 48.6k | librevenge::RVNGString propName; |
194 | | |
195 | 48.6k | propName.sprintf("draw:marker-%s-viewbox", name); |
196 | 48.6k | propList.insert(propName.cstr(), "-10 -10 10 10"); |
197 | 48.6k | propName.sprintf("draw:marker-%s-path", name); |
198 | 48.6k | propList.insert(propName.cstr(), getPathStr(createPath(arrow.curves))); |
199 | 48.6k | propName.sprintf("draw:marker-%s-width", name); |
200 | 48.6k | propList.insert(propName.cstr(), penWidth * dist); |
201 | 48.6k | propName.sprintf("draw:marker-%s-center", name); |
202 | 48.6k | propList.insert(propName.cstr(), true); |
203 | 48.6k | } |
204 | | |
205 | | class FillWriter : public boost::static_visitor<void> |
206 | | { |
207 | | public: |
208 | | explicit FillWriter(librevenge::RVNGPropertyList &propList, |
209 | | const boost::optional<Transparency> &transparency) |
210 | 46.9k | : m_propList(propList) |
211 | 46.9k | , m_transparency(transparency) |
212 | 46.9k | { } |
213 | | |
214 | | void operator()(const Color &color) |
215 | 9.23k | { |
216 | 9.23k | m_propList.insert("draw:fill", "solid"); |
217 | 9.23k | m_propList.insert("draw:fill-color", color.toString()); |
218 | | |
219 | 9.23k | if (m_transparency) |
220 | 126 | { |
221 | 126 | m_propList.insert("draw:opacity", get(m_transparency).opacity(), librevenge::RVNG_PERCENT); |
222 | 126 | } |
223 | 9.23k | } |
224 | | |
225 | | void operator()(const Gradient &gradient) |
226 | 34.8k | { |
227 | 34.8k | using namespace boost::math::double_constants; |
228 | | |
229 | 34.8k | if (gradient.stops.size() < 2) |
230 | 2.11k | return; |
231 | | |
232 | 32.7k | m_propList.insert("draw:fill", "gradient"); |
233 | | |
234 | 32.7k | auto stops = gradient.stops; |
235 | 32.7k | std::sort(stops.begin(), stops.end(), |
236 | 32.7k | [&gradient](const GradientStop &s1, const GradientStop &s2) |
237 | 22.2M | { |
238 | 22.2M | return gradient.type == GradientType::LINEAR ? s1.offset < s2.offset : s1.offset > s2.offset; |
239 | 22.2M | }); |
240 | 32.7k | if (gradient.type != GradientType::LINEAR) |
241 | 19.8k | { |
242 | 19.8k | for (auto &stop : stops) |
243 | 1.25M | { |
244 | 1.25M | stop.offset = 1.0 - stop.offset; |
245 | 1.25M | } |
246 | 19.8k | } |
247 | | |
248 | 32.7k | librevenge::RVNGPropertyListVector gradientVector; |
249 | 32.7k | for (const auto &stop : stops) |
250 | 2.27M | { |
251 | 2.27M | librevenge::RVNGPropertyList grad; |
252 | 2.27M | grad.insert("svg:offset", stop.offset, librevenge::RVNG_PERCENT); |
253 | 2.27M | grad.insert("svg:stop-color", stop.color.toString()); |
254 | 2.27M | grad.insert("svg:stop-opacity", m_transparency ? get(m_transparency).opacity() : 1.0, librevenge::RVNG_PERCENT); |
255 | 2.27M | gradientVector.append(grad); |
256 | 2.27M | } |
257 | | |
258 | 32.7k | switch (gradient.type) |
259 | 32.7k | { |
260 | 14.2k | default: |
261 | 27.1k | case GradientType::LINEAR: |
262 | 27.1k | m_propList.insert("draw:style", "linear"); |
263 | 27.1k | m_propList.insert("draw:angle", rad2deg(gradient.angle + half_pi)); |
264 | 27.1k | m_propList.insert("svg:linearGradient", gradientVector); |
265 | 27.1k | break; |
266 | 5.62k | case GradientType::RADIAL: |
267 | 5.62k | m_propList.insert("draw:style", "radial"); |
268 | 5.62k | m_propList.insert("draw:cx", gradient.center.x, librevenge::RVNG_PERCENT); |
269 | 5.62k | m_propList.insert("draw:cy", gradient.center.y, librevenge::RVNG_PERCENT); |
270 | 5.62k | m_propList.insert("draw:border", 0.25 - gradient.center.distance(Point(0.5, 0.5)), librevenge::RVNG_PERCENT); |
271 | 5.62k | m_propList.insert("svg:radialGradient", gradientVector); |
272 | 5.62k | break; |
273 | 32.7k | } |
274 | 32.7k | } |
275 | | |
276 | | void operator()(const ImageFill &imageFill) |
277 | 2.81k | { |
278 | 2.81k | m_propList.insert("draw:fill", "bitmap"); |
279 | | |
280 | 2.81k | m_propList.insert("draw:fill-image", imageFill.image.data); |
281 | 2.81k | m_propList.insert("librevenge:mime-type", "image/png"); |
282 | | |
283 | 2.81k | if (imageFill.tile) |
284 | 2.26k | { |
285 | 2.26k | m_propList.insert("style:repeat", "repeat"); |
286 | 2.26k | m_propList.insert("draw:fill-image-width", imageFill.tileWidth, librevenge::RVNG_INCH); |
287 | 2.26k | m_propList.insert("draw:fill-image-height", imageFill.tileHeight, librevenge::RVNG_INCH); |
288 | 2.26k | m_propList.insert("draw:fill-image-ref-point", "top-left"); |
289 | 2.26k | } |
290 | 549 | else |
291 | 549 | { |
292 | 549 | m_propList.insert("style:repeat", "stretch"); |
293 | 549 | } |
294 | | |
295 | 2.81k | if (m_transparency) |
296 | 205 | { |
297 | 205 | m_propList.insert("draw:opacity", get(m_transparency).opacity(), librevenge::RVNG_PERCENT); |
298 | 205 | } |
299 | 2.81k | } |
300 | | |
301 | | private: |
302 | | librevenge::RVNGPropertyList &m_propList; |
303 | | |
304 | | const boost::optional<Transparency> &m_transparency; |
305 | | }; |
306 | | |
307 | | } |
308 | | |
309 | | ZMFCollector::ZMFCollector(librevenge::RVNGDrawingInterface *painter) |
310 | 19.9k | : m_painter(painter) |
311 | 19.9k | , m_pageSettings() |
312 | 19.9k | , m_isDocumentStarted(false) |
313 | 19.9k | , m_isPageStarted(false) |
314 | 19.9k | , m_isLayerStarted(false) |
315 | 19.9k | , m_style() |
316 | 19.9k | { |
317 | 19.9k | } |
318 | | |
319 | | ZMFCollector::~ZMFCollector() |
320 | 19.9k | { |
321 | 19.9k | if (m_isDocumentStarted) |
322 | 18.8k | { |
323 | 18.8k | endDocument(); |
324 | 18.8k | } |
325 | 19.9k | } |
326 | | |
327 | | void ZMFCollector::startDocument() |
328 | 19.9k | { |
329 | 19.9k | if (m_isDocumentStarted) |
330 | 0 | return; |
331 | | |
332 | 19.9k | librevenge::RVNGPropertyList propList; |
333 | | |
334 | 19.9k | m_painter->startDocument(propList); |
335 | | |
336 | 19.9k | m_isDocumentStarted = true; |
337 | 19.9k | } |
338 | | |
339 | | void ZMFCollector::endDocument() |
340 | 19.9k | { |
341 | 19.9k | if (!m_isDocumentStarted) |
342 | 0 | return; |
343 | | |
344 | 19.9k | if (m_isPageStarted) |
345 | 18.3k | endPage(); |
346 | | |
347 | 19.9k | m_painter->endDocument(); |
348 | | |
349 | 19.9k | m_isDocumentStarted = false; |
350 | 19.9k | } |
351 | | |
352 | | void ZMFCollector::startPage(const ZMFPageSettings &pageSettings) |
353 | 32.5k | { |
354 | 32.5k | if (m_isPageStarted) |
355 | 0 | return; |
356 | | |
357 | 32.5k | if (m_isLayerStarted) |
358 | 0 | endLayer(); |
359 | | |
360 | 32.5k | librevenge::RVNGPropertyList propList; |
361 | | |
362 | 32.5k | propList.insert("svg:width", pageSettings.width); |
363 | 32.5k | propList.insert("svg:height", pageSettings.height); |
364 | | |
365 | 32.5k | propList.insert("draw:fill", "solid"); |
366 | 32.5k | propList.insert("draw:fill-color", pageSettings.color.toString()); |
367 | | |
368 | 32.5k | m_painter->startPage(propList); |
369 | | |
370 | 32.5k | m_pageSettings = pageSettings; |
371 | | |
372 | 32.5k | m_isPageStarted = true; |
373 | 32.5k | } |
374 | | |
375 | | void ZMFCollector::endPage() |
376 | 32.5k | { |
377 | 32.5k | if (!m_isPageStarted) |
378 | 0 | return; |
379 | | |
380 | 32.5k | m_painter->endPage(); |
381 | | |
382 | 32.5k | m_isPageStarted = false; |
383 | 32.5k | } |
384 | | |
385 | | void ZMFCollector::startLayer() |
386 | 32.3k | { |
387 | 32.3k | if (m_isLayerStarted) |
388 | 0 | return; |
389 | | |
390 | 32.3k | librevenge::RVNGPropertyList propList; |
391 | | |
392 | 32.3k | m_painter->startLayer(propList); |
393 | | |
394 | 32.3k | m_isLayerStarted = true; |
395 | 32.3k | } |
396 | | |
397 | | void ZMFCollector::endLayer() |
398 | 14.0k | { |
399 | 14.0k | if (!m_isLayerStarted) |
400 | 0 | return; |
401 | | |
402 | 14.0k | m_painter->endLayer(); |
403 | | |
404 | 14.0k | m_isLayerStarted = false; |
405 | 14.0k | } |
406 | | |
407 | | void ZMFCollector::startGroup() |
408 | 1.13k | { |
409 | 1.13k | librevenge::RVNGPropertyList propList; |
410 | | |
411 | 1.13k | m_painter->openGroup(propList); |
412 | 1.13k | } |
413 | | |
414 | | void ZMFCollector::endGroup() |
415 | 289 | { |
416 | 289 | m_painter->closeGroup(); |
417 | 289 | } |
418 | | |
419 | | void ZMFCollector::setStyle(const Style &style) |
420 | 112k | { |
421 | 112k | m_style = style; |
422 | 112k | } |
423 | | |
424 | | void ZMFCollector::collectPath(const std::vector<Curve> &curves) |
425 | 73.9k | { |
426 | 73.9k | librevenge::RVNGPropertyList pathPropList; |
427 | | |
428 | 73.9k | writeStyle(pathPropList, m_style, |
429 | 73.9k | !std::any_of(curves.begin(), curves.end(), [](const Curve &c) |
430 | 73.9k | { |
431 | 54.8k | return c.closed; |
432 | 54.8k | })); |
433 | 73.9k | m_painter->setStyle(pathPropList); |
434 | 73.9k | pathPropList.clear(); |
435 | | |
436 | 73.9k | auto path = createPath(curves, m_pageSettings.leftOffset, m_pageSettings.topOffset); |
437 | | |
438 | 73.9k | pathPropList.insert("svg:d", path); |
439 | | |
440 | 73.9k | m_painter->drawPath(pathPropList); |
441 | 73.9k | } |
442 | | |
443 | | void ZMFCollector::collectPath(const Curve &curve) |
444 | 54.6k | { |
445 | 54.6k | collectPath(std::vector<Curve> { curve }); |
446 | 54.6k | } |
447 | | |
448 | | void ZMFCollector::collectEllipse(const Point &c, double rx, double ry, double rotation) |
449 | 18.2k | { |
450 | 18.2k | librevenge::RVNGPropertyList ellipse; |
451 | | |
452 | 18.2k | writeStyle(ellipse, m_style); |
453 | 18.2k | m_painter->setStyle(ellipse); |
454 | 18.2k | ellipse.clear(); |
455 | | |
456 | 18.2k | ellipse.insert("svg:cx", pageX(c.x)); |
457 | 18.2k | ellipse.insert("svg:cy", pageY(c.y)); |
458 | 18.2k | ellipse.insert("svg:rx", rx); |
459 | 18.2k | ellipse.insert("svg:ry", ry); |
460 | 18.2k | if (!ZMF_ALMOST_ZERO(rotation)) |
461 | 18.2k | { |
462 | 18.2k | ellipse.insert("librevenge:rotate", -rad2deg(rotation)); |
463 | 18.2k | } |
464 | | |
465 | 18.2k | m_painter->drawEllipse(ellipse); |
466 | 18.2k | } |
467 | | |
468 | | void ZMFCollector::collectArc(const Point &c, double rx, double ry, double beginAngle, double endAngle, bool closed, double rotation) |
469 | 7.71k | { |
470 | 7.71k | using namespace boost::math::double_constants; |
471 | | |
472 | 7.71k | librevenge::RVNGPropertyList pathPropList; |
473 | | |
474 | 7.71k | writeStyle(pathPropList, m_style, !closed); |
475 | 7.71k | m_painter->setStyle(pathPropList); |
476 | 7.71k | pathPropList.clear(); |
477 | | |
478 | 7.71k | auto beginPoint = calculateEllipsePoint(c, rx, ry, beginAngle); |
479 | 7.71k | auto endPoint = calculateEllipsePoint(c, rx, ry, endAngle); |
480 | | |
481 | 7.71k | if (!ZMF_ALMOST_ZERO(rotation)) |
482 | 7.52k | { |
483 | 7.52k | beginPoint = beginPoint.rotate(rotation, c); |
484 | 7.52k | endPoint = endPoint.rotate(rotation, c); |
485 | 7.52k | } |
486 | | |
487 | 7.71k | double angleDiff = std::fabs(endAngle - beginAngle); |
488 | 7.71k | bool largeArc = (beginAngle < endAngle && angleDiff > pi) || |
489 | 6.69k | (beginAngle > endAngle && angleDiff < pi); |
490 | | |
491 | 7.71k | librevenge::RVNGPropertyListVector path; |
492 | | |
493 | 7.71k | { |
494 | 7.71k | librevenge::RVNGPropertyList pathStart; |
495 | 7.71k | pathStart.insert("librevenge:path-action", "M"); |
496 | 7.71k | pathStart.insert("svg:x", pageX(beginPoint.x)); |
497 | 7.71k | pathStart.insert("svg:y", pageY(beginPoint.y)); |
498 | | |
499 | 7.71k | path.append(pathStart); |
500 | 7.71k | } |
501 | | |
502 | 7.71k | { |
503 | 7.71k | librevenge::RVNGPropertyList arc; |
504 | 7.71k | arc.insert("librevenge:path-action", "A"); |
505 | 7.71k | arc.insert("svg:rx", rx); |
506 | 7.71k | arc.insert("svg:ry", ry); |
507 | 7.71k | arc.insert("librevenge:large-arc", largeArc ? 1 : 0); |
508 | 7.71k | arc.insert("librevenge:sweep", 1); |
509 | 7.71k | arc.insert("svg:x", pageX(endPoint.x)); |
510 | 7.71k | arc.insert("svg:y", pageY(endPoint.y)); |
511 | | |
512 | 7.71k | path.append(arc); |
513 | 7.71k | } |
514 | | |
515 | 7.71k | if (closed) |
516 | 1.55k | { |
517 | 1.55k | librevenge::RVNGPropertyList pathEndLine; |
518 | 1.55k | pathEndLine.insert("librevenge:path-action", "L"); |
519 | 1.55k | pathEndLine.insert("svg:x", pageX(c.x)); |
520 | 1.55k | pathEndLine.insert("svg:y", pageY(c.y)); |
521 | | |
522 | 1.55k | path.append(pathEndLine); |
523 | | |
524 | 1.55k | librevenge::RVNGPropertyList pathEnd; |
525 | 1.55k | pathEnd.insert("librevenge:path-action", "Z"); |
526 | | |
527 | 1.55k | path.append(pathEnd); |
528 | 1.55k | } |
529 | | |
530 | 7.71k | pathPropList.insert("svg:d", path); |
531 | | |
532 | 7.71k | m_painter->drawPath(pathPropList); |
533 | 7.71k | } |
534 | | |
535 | | void ZMFCollector::collectPolygon(const Point &c, double rx, double ry, uint32_t peaksCount, const Curve &peak, |
536 | | double rotation, bool mirrorHorizontal, bool mirrorVertical) |
537 | 575 | { |
538 | 575 | using namespace boost::math::double_constants; |
539 | | |
540 | 575 | if (peak.points.size() < 2) |
541 | 11 | { |
542 | 11 | return; |
543 | 11 | } |
544 | | |
545 | 564 | const double peakAngle = two_pi / peaksCount; |
546 | | |
547 | | // create a single side of the polygon in an unit square; the |
548 | | // center of the (future) polygon is (0, 0) |
549 | 564 | std::vector<Point> side; |
550 | 564 | side.reserve(peak.points.size()); |
551 | 564 | std::transform(peak.points.begin(), peak.points.end(), std::back_inserter(side), [peakAngle](const Point &p) |
552 | 7.08k | { |
553 | 7.08k | return calculateEllipsePoint(Point(0, 0), p.y, p.y, p.x * peakAngle); |
554 | 7.08k | }); |
555 | | |
556 | 564 | Curve polygonCurve; |
557 | 564 | polygonCurve.points.reserve(peak.points.size() * peaksCount); |
558 | 564 | polygonCurve.sectionTypes.reserve(peak.sectionTypes.size() * peaksCount); |
559 | | |
560 | | // generate complete polygon |
561 | 20.4k | for (uint32_t i = 0; i < peaksCount; ++i) |
562 | 19.8k | { |
563 | 19.8k | std::transform(side.begin() + (i == 0 ? 0 : 1), side.end(), std::back_inserter(polygonCurve.points), [i, peakAngle](Point p) |
564 | 361k | { |
565 | 361k | p = p.rotate(i * peakAngle, Point(0, 0)); |
566 | 361k | return p; |
567 | 361k | }); |
568 | 19.8k | std::copy(peak.sectionTypes.begin(), peak.sectionTypes.end(), std::back_inserter(polygonCurve.sectionTypes)); |
569 | 19.8k | } |
570 | | |
571 | | // fit the polygon into the bbox and mirror |
572 | 564 | for (auto &p: polygonCurve.points) |
573 | 361k | { |
574 | 361k | p.x = p.x * rx; |
575 | 361k | p.y = p.y * ry; |
576 | 361k | p = p.move(c.x, c.y); |
577 | | |
578 | 361k | p.y = -p.y; |
579 | 361k | p = p.move(0, 2 * c.y); |
580 | | |
581 | 361k | if (mirrorHorizontal) |
582 | 149k | { |
583 | 149k | p.x = -p.x; |
584 | 149k | p = p.move(2 * c.x, 0); |
585 | 149k | } |
586 | 361k | if (mirrorVertical) |
587 | 151k | { |
588 | 151k | p.y = -p.y; |
589 | 151k | p = p.move(0, 2 * c.y); |
590 | 151k | } |
591 | | |
592 | 361k | p = p.rotate(rotation, c); |
593 | 361k | } |
594 | | |
595 | 564 | polygonCurve.closed = true; |
596 | 564 | collectPath(polygonCurve); |
597 | 564 | } |
598 | | |
599 | | void ZMFCollector::collectTextObject(const Text &text, const Point &topLeft, double width, double height, |
600 | | VerticalAlignment align, double rotation) |
601 | 3.77k | { |
602 | 3.77k | librevenge::RVNGPropertyList textObjPropList; |
603 | | |
604 | 3.77k | textObjPropList.insert("svg:x", pageX(topLeft.x)); |
605 | 3.77k | textObjPropList.insert("svg:y", pageY(topLeft.y)); |
606 | 3.77k | textObjPropList.insert("svg:width", width); |
607 | 3.77k | textObjPropList.insert("svg:height", height); |
608 | | |
609 | 3.77k | switch (align) |
610 | 3.77k | { |
611 | 2.64k | case VerticalAlignment::TOP: |
612 | 2.64k | textObjPropList.insert("draw:textarea-vertical-align", "top"); |
613 | 2.64k | break; |
614 | 427 | case VerticalAlignment::MIDDLE: |
615 | 427 | textObjPropList.insert("draw:textarea-vertical-align", "middle"); |
616 | 427 | break; |
617 | 706 | case VerticalAlignment::BOTTOM: |
618 | 706 | textObjPropList.insert("draw:textarea-vertical-align", "bottom"); |
619 | 706 | break; |
620 | 3.77k | } |
621 | 3.77k | if (!ZMF_ALMOST_ZERO(rotation)) |
622 | 3.27k | { |
623 | 3.27k | textObjPropList.insert("librevenge:rotate", rad2deg(rotation)); |
624 | 3.27k | } |
625 | | |
626 | 3.77k | m_painter->startTextObject(textObjPropList); |
627 | | |
628 | 3.77k | collectText(text); |
629 | | |
630 | 3.77k | m_painter->endTextObject(); |
631 | 3.77k | } |
632 | | |
633 | | void ZMFCollector::collectText(const Text &text) |
634 | 235k | { |
635 | 235k | for (auto ¶graph : text.paragraphs) |
636 | 71.0k | { |
637 | 71.0k | librevenge::RVNGPropertyList paragraphPropList; |
638 | | |
639 | 71.0k | paragraphPropList.insert("fo:line-height", paragraph.style.lineSpacing, librevenge::RVNG_PERCENT); |
640 | | |
641 | 71.0k | switch (paragraph.style.alignment) |
642 | 71.0k | { |
643 | 67.8k | case HorizontalAlignment::LEFT: |
644 | 67.8k | paragraphPropList.insert("fo:text-align", "left"); |
645 | 67.8k | break; |
646 | 133 | case HorizontalAlignment::RIGHT: |
647 | 133 | paragraphPropList.insert("fo:text-align", "end"); |
648 | 133 | break; |
649 | 2.04k | case HorizontalAlignment::CENTER: |
650 | 2.04k | paragraphPropList.insert("fo:text-align", "center"); |
651 | 2.04k | break; |
652 | 146 | case HorizontalAlignment::BLOCK: |
653 | 1.08k | case HorizontalAlignment::FULL: |
654 | 1.08k | paragraphPropList.insert("fo:text-align", "justify"); |
655 | 1.08k | break; |
656 | 71.0k | } |
657 | | |
658 | 71.0k | m_painter->openParagraph(paragraphPropList); |
659 | | |
660 | 71.0k | for (auto &span : paragraph.spans) |
661 | 160k | { |
662 | 160k | librevenge::RVNGPropertyList spanPropList; |
663 | | |
664 | 160k | spanPropList.insert("style:font-name", span.font.name); |
665 | 160k | spanPropList.insert("fo:font-size", span.font.size, librevenge::RVNG_POINT); |
666 | 160k | spanPropList.insert("fo:font-weight", span.font.isBold ? "bold" : "normal"); |
667 | 160k | spanPropList.insert("fo:font-style", span.font.isItalic ? "italic" : "normal"); |
668 | 160k | spanPropList.insert("style:text-outline", static_cast<bool>(span.font.outline)); |
669 | | |
670 | 160k | if (span.font.fill && get(span.font.fill).type() == typeid(Color)) |
671 | 90.6k | { |
672 | 90.6k | auto color = boost::get<Color>(get(span.font.fill)); |
673 | 90.6k | spanPropList.insert("fo:color", color.toString()); |
674 | 90.6k | } |
675 | | |
676 | 160k | m_painter->openSpan(spanPropList); |
677 | | |
678 | 160k | bool wasSpace = false; |
679 | 160k | std::string curText; |
680 | | |
681 | 160k | librevenge::RVNGString::Iter iter(span.text); |
682 | 160k | iter.rewind(); |
683 | 1.98M | while (iter.next()) |
684 | 1.82M | { |
685 | 1.82M | const char *const utf8Char = iter(); |
686 | 1.82M | switch (utf8Char[0]) |
687 | 1.82M | { |
688 | | // looks like Zoner Draw doesn't allow tabs, |
689 | | // and \r (without \n) can be only at the end of paragraph |
690 | 3.36k | case '\r': |
691 | 12.2k | case '\n': |
692 | 12.2k | break; |
693 | 12.9k | case ' ': |
694 | 12.9k | if (wasSpace) |
695 | 3.50k | { |
696 | 3.50k | flushText(curText); |
697 | 3.50k | m_painter->insertSpace(); |
698 | 3.50k | } |
699 | 9.46k | else |
700 | 9.46k | { |
701 | 9.46k | wasSpace = true; |
702 | 9.46k | curText.push_back(' '); |
703 | 9.46k | } |
704 | 12.9k | break; |
705 | 1.80M | default: |
706 | 1.80M | wasSpace = false; |
707 | 1.80M | curText.append(utf8Char); |
708 | 1.80M | break; |
709 | 1.82M | } |
710 | 1.82M | } |
711 | | |
712 | 160k | flushText(curText); |
713 | | |
714 | 160k | m_painter->closeSpan(); |
715 | 160k | } |
716 | | |
717 | 71.0k | m_painter->closeParagraph(); |
718 | 71.0k | } |
719 | 235k | } |
720 | | |
721 | | void ZMFCollector::flushText(std::string &text) |
722 | 163k | { |
723 | 163k | if (!text.empty()) |
724 | 72.1k | { |
725 | 72.1k | m_painter->insertText(librevenge::RVNGString(text.c_str())); |
726 | 72.1k | text.clear(); |
727 | 72.1k | } |
728 | 163k | } |
729 | | |
730 | | void ZMFCollector::collectTable(const Table &table) |
731 | 14.7k | { |
732 | 14.7k | librevenge::RVNGPropertyList tablePropList; |
733 | | |
734 | 14.7k | tablePropList.insert("svg:x", pageX(table.topLeftPoint.x)); |
735 | 14.7k | tablePropList.insert("svg:y", pageY(table.topLeftPoint.y)); |
736 | 14.7k | tablePropList.insert("svg:width", table.width); |
737 | 14.7k | tablePropList.insert("svg:height", table.height); |
738 | | |
739 | 14.7k | librevenge::RVNGPropertyListVector columnSizes; |
740 | | |
741 | 14.7k | for (const auto &col : table.columns) |
742 | 44.7k | { |
743 | 44.7k | librevenge::RVNGPropertyList columnPropList; |
744 | 44.7k | columnPropList.insert("style:column-width", col.width); |
745 | 44.7k | columnSizes.append(columnPropList); |
746 | 44.7k | } |
747 | | |
748 | 14.7k | tablePropList.insert("librevenge:table-columns", columnSizes); |
749 | | |
750 | 14.7k | m_painter->startTableObject(tablePropList); |
751 | | |
752 | 14.7k | for (const auto &row : table.rows) |
753 | 101k | { |
754 | 101k | librevenge::RVNGPropertyList rowPropList; |
755 | 101k | rowPropList.insert("style:row-height", row.height); |
756 | 101k | m_painter->openTableRow(rowPropList); |
757 | | |
758 | 101k | for (const Cell &cell : row.cells) |
759 | 231k | { |
760 | 231k | librevenge::RVNGPropertyList cellPropList; |
761 | | |
762 | 231k | if (cell.fill && get(cell.fill).type() == typeid(Color)) |
763 | 33.9k | { |
764 | 33.9k | auto backgroundColor = boost::get<Color>(get(cell.fill)); |
765 | 33.9k | cellPropList.insert("fo:background-color", backgroundColor.toString()); |
766 | 33.9k | } |
767 | | |
768 | 231k | cellPropList.insert("draw:textarea-vertical-align", "middle"); |
769 | | |
770 | 231k | if (cell.leftBorder) |
771 | 108k | { |
772 | 108k | writeBorder(cellPropList, "fo:border-left", get(cell.leftBorder)); |
773 | 108k | } |
774 | 231k | if (cell.rightBorder) |
775 | 106k | { |
776 | 106k | writeBorder(cellPropList, "fo:border-right", get(cell.rightBorder)); |
777 | 106k | } |
778 | 231k | if (cell.topBorder) |
779 | 97.8k | { |
780 | 97.8k | writeBorder(cellPropList, "fo:border-top", get(cell.topBorder)); |
781 | 97.8k | } |
782 | 231k | if (cell.bottomBorder) |
783 | 74.8k | { |
784 | 74.8k | writeBorder(cellPropList, "fo:border-bottom", get(cell.bottomBorder)); |
785 | 74.8k | } |
786 | | |
787 | 231k | m_painter->openTableCell(cellPropList); |
788 | | |
789 | 231k | collectText(cell.text); |
790 | | |
791 | 231k | m_painter->closeTableCell(); |
792 | 231k | } |
793 | | |
794 | 101k | m_painter->closeTableRow(); |
795 | 101k | } |
796 | | |
797 | 14.7k | m_painter->endTableObject(); |
798 | 14.7k | } |
799 | | |
800 | | void ZMFCollector::collectImage(const librevenge::RVNGBinaryData &image, const Point &topLeft, double width, double height, |
801 | | double rotation, bool mirrorHorizontal, bool mirrorVertical) |
802 | 13.2k | { |
803 | 13.2k | librevenge::RVNGPropertyList propList; |
804 | | |
805 | 13.2k | writeStyle(propList, m_style); |
806 | | |
807 | 13.2k | if (m_style.transparency) |
808 | 1 | { |
809 | 1 | propList.insert("draw:opacity", get(m_style.transparency).opacity(), librevenge::RVNG_PERCENT); |
810 | 1 | } |
811 | | |
812 | 13.2k | m_painter->setStyle(propList); |
813 | 13.2k | propList.clear(); |
814 | | |
815 | 13.2k | propList.insert("svg:x", pageX(topLeft.x)); |
816 | 13.2k | propList.insert("svg:y", pageY(topLeft.y)); |
817 | 13.2k | propList.insert("svg:width", width); |
818 | 13.2k | propList.insert("svg:height", height); |
819 | 13.2k | if (!ZMF_ALMOST_ZERO(rotation)) |
820 | 1.33k | { |
821 | 1.33k | propList.insert("librevenge:rotate", rad2deg(rotation)); |
822 | 1.33k | } |
823 | 13.2k | propList.insert("draw:mirror-vertical", mirrorVertical); |
824 | 13.2k | propList.insert("draw:mirror-horizontal", mirrorHorizontal); |
825 | 13.2k | propList.insert("librevenge:mime-type", "image/png"); |
826 | 13.2k | propList.insert("office:binary-data", image); |
827 | | |
828 | 13.2k | m_painter->drawGraphicObject(propList); |
829 | 13.2k | } |
830 | | |
831 | | double ZMFCollector::pageX(double canvasX) |
832 | 67.0k | { |
833 | 67.0k | return canvasX - m_pageSettings.leftOffset; |
834 | 67.0k | } |
835 | | |
836 | | double ZMFCollector::pageY(double canvasY) |
837 | 67.0k | { |
838 | 67.0k | return canvasY - m_pageSettings.topOffset; |
839 | 67.0k | } |
840 | | |
841 | | // noFill is used to ignore Fill object even if it exists |
842 | | // some implementations (such as svg in web browsers) try to fill not closed paths if fill is specified |
843 | | void ZMFCollector::writeStyle(librevenge::RVNGPropertyList &propList, const Style &style, bool noFill) |
844 | 113k | { |
845 | 113k | propList.insert("draw:stroke", "none"); |
846 | 113k | propList.insert("draw:fill", "none"); |
847 | | |
848 | 113k | if (style.pen) |
849 | 37.4k | { |
850 | 37.4k | writePen(propList, get(style.pen)); |
851 | 37.4k | } |
852 | | |
853 | 113k | if (style.fill && !noFill) |
854 | 46.9k | { |
855 | 46.9k | writeFill(propList, get(style.fill)); |
856 | 46.9k | } |
857 | | |
858 | 113k | if (style.shadow) |
859 | 17.6k | { |
860 | 17.6k | writeShadow(propList, get(style.shadow)); |
861 | 17.6k | } |
862 | 113k | } |
863 | | |
864 | | void ZMFCollector::writePen(librevenge::RVNGPropertyList &propList, const Pen &pen) |
865 | 37.4k | { |
866 | 37.4k | propList.insert("svg:stroke-color", pen.color.toString()); |
867 | 37.4k | if (!ZMF_ALMOST_ZERO(pen.width)) |
868 | 7.94k | propList.insert("svg:stroke-width", pen.width); |
869 | | |
870 | 37.4k | if (pen.dashPattern.size() > 0) |
871 | 30.4k | { |
872 | 30.4k | double dots1 = pen.dashPattern[0]; |
873 | 30.4k | double dots2 = pen.dashPattern[0]; |
874 | 30.4k | double dist = pen.dashDistance; |
875 | 30.4k | if (pen.dashPattern.size() >= 3) |
876 | 21.8k | { |
877 | 21.8k | dist = pen.dashPattern[1]; |
878 | 21.8k | dots2 = pen.dashPattern[2]; |
879 | 21.8k | } |
880 | | |
881 | 30.4k | propList.insert("draw:stroke", "dash"); |
882 | 30.4k | propList.insert("draw:dots1", 1); |
883 | 30.4k | propList.insert("draw:dots1-length", dots1, librevenge::RVNG_PERCENT); |
884 | 30.4k | propList.insert("draw:dots2", 1); |
885 | 30.4k | propList.insert("draw:dots2-length", dots2, librevenge::RVNG_PERCENT); |
886 | 30.4k | propList.insert("draw:distance", dist, librevenge::RVNG_PERCENT); |
887 | 30.4k | } |
888 | 7.02k | else |
889 | 7.02k | { |
890 | 7.02k | propList.insert("draw:stroke", "solid"); |
891 | 7.02k | } |
892 | | |
893 | 37.4k | switch (pen.lineCapType) |
894 | 37.4k | { |
895 | 1 | default: |
896 | 36.9k | case LineCapType::BUTT: |
897 | 36.9k | propList.insert("svg:stroke-linecap", "butt"); |
898 | 36.9k | break; |
899 | 406 | case LineCapType::ROUND: |
900 | 406 | propList.insert("svg:stroke-linecap", "round"); |
901 | 406 | break; |
902 | 96 | case LineCapType::FLAT: |
903 | 96 | propList.insert("svg:stroke-linecap", "square"); |
904 | 96 | break; |
905 | 37.4k | } |
906 | | |
907 | 37.4k | switch (pen.lineJoinType) |
908 | 37.4k | { |
909 | 0 | default: |
910 | 370 | case LineJoinType::BEVEL: |
911 | 370 | propList.insert("svg:stroke-linejoin", "bevel"); |
912 | 370 | break; |
913 | 36.8k | case LineJoinType::MITER: |
914 | 36.8k | propList.insert("svg:stroke-linejoin", "miter"); |
915 | 36.8k | break; |
916 | 220 | case LineJoinType::ROUND: |
917 | 220 | propList.insert("svg:stroke-linejoin", "round"); |
918 | 220 | break; |
919 | 37.4k | } |
920 | | |
921 | 37.4k | if (m_style.transparency) |
922 | 2.69k | { |
923 | 2.69k | propList.insert("svg:stroke-opacity", get(m_style.transparency).opacity(), librevenge::RVNG_PERCENT); |
924 | 2.69k | } |
925 | | |
926 | 37.4k | if (pen.startArrow) |
927 | 22.4k | { |
928 | 22.4k | writeArrow(propList, "start", *pen.startArrow, pen.width); |
929 | 22.4k | } |
930 | 37.4k | if (pen.endArrow) |
931 | 26.2k | { |
932 | 26.2k | writeArrow(propList, "end", *pen.endArrow, pen.width); |
933 | 26.2k | } |
934 | 37.4k | } |
935 | | |
936 | | void ZMFCollector::writeFill(librevenge::RVNGPropertyList &propList, const Fill &fill) |
937 | 46.9k | { |
938 | 46.9k | FillWriter fillWriter(propList, m_style.transparency); |
939 | 46.9k | boost::apply_visitor(fillWriter, fill); |
940 | | |
941 | 46.9k | propList.insert("svg:fill-rule", "evenodd"); |
942 | 46.9k | } |
943 | | |
944 | | void ZMFCollector::writeShadow(librevenge::RVNGPropertyList &propList, const Shadow &shadow) |
945 | 17.6k | { |
946 | 17.6k | propList.insert("draw:shadow", "visible"); |
947 | 17.6k | propList.insert("draw:shadow-color", shadow.color.toString()); |
948 | 17.6k | propList.insert("draw:shadow-opacity", shadow.opacity, librevenge::RVNG_PERCENT); |
949 | 17.6k | propList.insert("draw:shadow-offset-x", shadow.offset.x); |
950 | 17.6k | propList.insert("draw:shadow-offset-y", shadow.offset.y); |
951 | 17.6k | } |
952 | | |
953 | | } |
954 | | |
955 | | /* vim:set shiftwidth=2 softtabstop=2 expandtab: */ |