/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 | 18.8k | { |
26 | 18.8k | return Point(c.x + rx * std::cos(angle), c.y + ry * std::sin(angle)); |
27 | 18.8k | } |
28 | | |
29 | | void writeBorder(librevenge::RVNGPropertyList &propList, const char *const name, const Pen &pen) |
30 | 386k | { |
31 | 386k | if (pen.isInvisible) |
32 | 62.5k | return; |
33 | | |
34 | 323k | librevenge::RVNGString border; |
35 | | |
36 | 323k | border.sprintf("%fin", pen.width); |
37 | | |
38 | 323k | border.append(" solid"); |
39 | | |
40 | 323k | border.append(" "); |
41 | 323k | border.append(pen.color.toString()); |
42 | | |
43 | 323k | propList.insert(name, border); |
44 | 323k | } |
45 | | |
46 | | librevenge::RVNGPropertyListVector createPath(const std::vector<Curve> &curves, |
47 | | double leftOffset = 0.0, double topOffset = 0.0) |
48 | 129k | { |
49 | 129k | librevenge::RVNGPropertyListVector path; |
50 | | |
51 | 129k | for (const auto &curve : curves) |
52 | 73.0k | { |
53 | 73.0k | if (curve.points.size() < 2) |
54 | 2.75k | continue; |
55 | | |
56 | 70.3k | { |
57 | 70.3k | librevenge::RVNGPropertyList pathPart; |
58 | 70.3k | pathPart.insert("librevenge:path-action", "M"); |
59 | 70.3k | pathPart.insert("svg:x", curve.points[0].x - leftOffset); |
60 | 70.3k | pathPart.insert("svg:y", curve.points[0].y - topOffset); |
61 | | |
62 | 70.3k | path.append(pathPart); |
63 | 70.3k | } |
64 | | |
65 | 70.3k | size_t i = 1; |
66 | | |
67 | 70.3k | for (const auto §ionType : curve.sectionTypes) |
68 | 11.7M | { |
69 | 11.7M | librevenge::RVNGPropertyList pathPart; |
70 | | |
71 | 11.7M | switch (sectionType) |
72 | 11.7M | { |
73 | 0 | default: |
74 | 11.5M | case CurveType::LINE: |
75 | 11.5M | if (i >= curve.points.size()) |
76 | 11.2M | { |
77 | 11.2M | ZMF_DEBUG_MSG(("Unexpected end of curve points\n")); |
78 | 11.2M | break; |
79 | 11.2M | } |
80 | 387k | pathPart.insert("librevenge:path-action", "L"); |
81 | 387k | pathPart.insert("svg:x", curve.points[i].x - leftOffset); |
82 | 387k | pathPart.insert("svg:y", curve.points[i].y - topOffset); |
83 | 387k | i++; |
84 | 387k | break; |
85 | 169k | case CurveType::BEZIER_CURVE: |
86 | 169k | if (i + 2 >= curve.points.size()) |
87 | 159k | { |
88 | 159k | ZMF_DEBUG_MSG(("Unexpected end of curve points\n")); |
89 | 159k | break; |
90 | 159k | } |
91 | 10.5k | pathPart.insert("librevenge:path-action", "C"); |
92 | 10.5k | pathPart.insert("svg:x1", curve.points[i].x - leftOffset); |
93 | 10.5k | pathPart.insert("svg:y1", curve.points[i].y - topOffset); |
94 | 10.5k | pathPart.insert("svg:x2", curve.points[i + 1].x - leftOffset); |
95 | 10.5k | pathPart.insert("svg:y2", curve.points[i + 1].y - topOffset); |
96 | 10.5k | pathPart.insert("svg:x", curve.points[i + 2].x - leftOffset); |
97 | 10.5k | pathPart.insert("svg:y", curve.points[i + 2].y - topOffset); |
98 | 10.5k | i += 3; |
99 | 10.5k | break; |
100 | 11.7M | } |
101 | | |
102 | 11.7M | path.append(pathPart); |
103 | 11.7M | } |
104 | | |
105 | 70.3k | if (curve.closed) |
106 | 66.5k | { |
107 | 66.5k | librevenge::RVNGPropertyList pathPart; |
108 | 66.5k | pathPart.insert("librevenge:path-action", "Z"); |
109 | | |
110 | 66.5k | path.append(pathPart); |
111 | 66.5k | } |
112 | 70.3k | } |
113 | | |
114 | 129k | return path; |
115 | 129k | } |
116 | | |
117 | | librevenge::RVNGString getPathStr(const librevenge::RVNGPropertyListVector &path) |
118 | 53.8k | { |
119 | 53.8k | librevenge::RVNGString s(""); |
120 | | |
121 | 1.25M | for (unsigned long i = 0; i < path.count(); ++i) |
122 | 1.20M | { |
123 | 1.20M | if (!path[i]["librevenge:path-action"]) |
124 | 1.04M | continue; |
125 | | |
126 | 162k | std::string action = path[i]["librevenge:path-action"]->getStr().cstr(); |
127 | 162k | bool coordOk = path[i]["svg:x"] && path[i]["svg:y"]; |
128 | 162k | bool coord1Ok = coordOk && path[i]["svg:x1"] && path[i]["svg:y1"]; |
129 | 162k | bool coord2Ok = coord1Ok && path[i]["svg:x2"] && path[i]["svg:y2"]; |
130 | | |
131 | 162k | librevenge::RVNGString sElement; |
132 | | |
133 | 162k | switch (action[0]) |
134 | 162k | { |
135 | 15.0k | case 'M': |
136 | 144k | case 'L': |
137 | 144k | if (!coordOk) |
138 | 0 | { |
139 | 0 | ZMF_DEBUG_MSG(("Incorrect path coordinates\n")); |
140 | 0 | break; |
141 | 0 | } |
142 | 144k | sElement.sprintf("%c%lf %lf ", action[0], path[i]["svg:x"]->getDouble(), path[i]["svg:y"]->getDouble()); |
143 | 144k | s.append(sElement); |
144 | 144k | break; |
145 | 6.25k | case 'C': |
146 | 6.25k | if (!coord2Ok) |
147 | 0 | { |
148 | 0 | ZMF_DEBUG_MSG(("Incorrect path coordinates\n")); |
149 | 0 | break; |
150 | 0 | } |
151 | 6.25k | sElement.sprintf("C%lf %lf %lf %lf %lf %lf ", |
152 | 6.25k | path[i]["svg:x1"]->getDouble(), path[i]["svg:y1"]->getDouble(), |
153 | 6.25k | path[i]["svg:x2"]->getDouble(), path[i]["svg:y2"]->getDouble(), |
154 | 6.25k | path[i]["svg:x"]->getDouble(), path[i]["svg:y"]->getDouble()); |
155 | 6.25k | s.append(sElement); |
156 | 6.25k | break; |
157 | 11.7k | case 'Z': |
158 | 11.7k | s.append("Z "); |
159 | 11.7k | break; |
160 | 0 | default: |
161 | 0 | ZMF_DEBUG_MSG(("Unknown path-action %s\n", action.c_str())); |
162 | 162k | } |
163 | 162k | } |
164 | | |
165 | 53.8k | return s; |
166 | 53.8k | } |
167 | | |
168 | | void writeArrow(librevenge::RVNGPropertyList &propList, const char *const name, Arrow arrow, double penWidth) |
169 | 53.8k | { |
170 | 53.8k | using namespace boost::math::double_constants; |
171 | | |
172 | 53.8k | Point lineEnd(arrow.lineEndX, 0); |
173 | | |
174 | 53.8k | lineEnd = lineEnd.rotate(half_pi, Point(0, 0)); |
175 | | |
176 | 53.8k | double dist = 1; |
177 | | |
178 | 53.8k | for (auto &curve : arrow.curves) |
179 | 17.4k | { |
180 | 17.4k | auto minmaxY = std::minmax_element(curve.points.begin(), curve.points.end(), [](const Point &p1, const Point &p2) |
181 | 587k | { |
182 | 587k | return p1.y < p2.y; |
183 | 587k | }); |
184 | | |
185 | 17.4k | dist = std::max(dist, std::abs(minmaxY.first->y - minmaxY.second->y)); |
186 | | |
187 | 17.4k | std::transform(curve.points.begin(), curve.points.end(), curve.points.begin(), [&lineEnd](const Point &p) |
188 | 413k | { |
189 | 413k | return p.rotate(half_pi, Point(0, 0)).move(0, -lineEnd.y); |
190 | 413k | }); |
191 | 17.4k | } |
192 | | |
193 | 53.8k | librevenge::RVNGString propName; |
194 | | |
195 | 53.8k | propName.sprintf("draw:marker-%s-viewbox", name); |
196 | 53.8k | propList.insert(propName.cstr(), "-10 -10 10 10"); |
197 | 53.8k | propName.sprintf("draw:marker-%s-path", name); |
198 | 53.8k | propList.insert(propName.cstr(), getPathStr(createPath(arrow.curves))); |
199 | 53.8k | propName.sprintf("draw:marker-%s-width", name); |
200 | 53.8k | propList.insert(propName.cstr(), penWidth * dist); |
201 | 53.8k | propName.sprintf("draw:marker-%s-center", name); |
202 | 53.8k | propList.insert(propName.cstr(), true); |
203 | 53.8k | } |
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 | 47.4k | : m_propList(propList) |
211 | 47.4k | , m_transparency(transparency) |
212 | 47.4k | { } |
213 | | |
214 | | void operator()(const Color &color) |
215 | 9.93k | { |
216 | 9.93k | m_propList.insert("draw:fill", "solid"); |
217 | 9.93k | m_propList.insert("draw:fill-color", color.toString()); |
218 | | |
219 | 9.93k | if (m_transparency) |
220 | 92 | { |
221 | 92 | m_propList.insert("draw:opacity", get(m_transparency).opacity(), librevenge::RVNG_PERCENT); |
222 | 92 | } |
223 | 9.93k | } |
224 | | |
225 | | void operator()(const Gradient &gradient) |
226 | 33.9k | { |
227 | 33.9k | using namespace boost::math::double_constants; |
228 | | |
229 | 33.9k | if (gradient.stops.size() < 2) |
230 | 1.48k | return; |
231 | | |
232 | 32.4k | m_propList.insert("draw:fill", "gradient"); |
233 | | |
234 | 32.4k | auto stops = gradient.stops; |
235 | 32.4k | std::sort(stops.begin(), stops.end(), |
236 | 32.4k | [&gradient](const GradientStop &s1, const GradientStop &s2) |
237 | 27.1M | { |
238 | 27.1M | return gradient.type == GradientType::LINEAR ? s1.offset < s2.offset : s1.offset > s2.offset; |
239 | 27.1M | }); |
240 | 32.4k | if (gradient.type != GradientType::LINEAR) |
241 | 18.9k | { |
242 | 18.9k | for (auto &stop : stops) |
243 | 1.43M | { |
244 | 1.43M | stop.offset = 1.0 - stop.offset; |
245 | 1.43M | } |
246 | 18.9k | } |
247 | | |
248 | 32.4k | librevenge::RVNGPropertyListVector gradientVector; |
249 | 32.4k | for (const auto &stop : stops) |
250 | 2.94M | { |
251 | 2.94M | librevenge::RVNGPropertyList grad; |
252 | 2.94M | grad.insert("svg:offset", stop.offset, librevenge::RVNG_PERCENT); |
253 | 2.94M | grad.insert("svg:stop-color", stop.color.toString()); |
254 | 2.94M | grad.insert("svg:stop-opacity", m_transparency ? get(m_transparency).opacity() : 1.0, librevenge::RVNG_PERCENT); |
255 | 2.94M | gradientVector.append(grad); |
256 | 2.94M | } |
257 | | |
258 | 32.4k | switch (gradient.type) |
259 | 32.4k | { |
260 | 13.6k | 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.31k | case GradientType::RADIAL: |
267 | 5.31k | m_propList.insert("draw:style", "radial"); |
268 | 5.31k | m_propList.insert("draw:cx", gradient.center.x, librevenge::RVNG_PERCENT); |
269 | 5.31k | m_propList.insert("draw:cy", gradient.center.y, librevenge::RVNG_PERCENT); |
270 | 5.31k | m_propList.insert("draw:border", 0.25 - gradient.center.distance(Point(0.5, 0.5)), librevenge::RVNG_PERCENT); |
271 | 5.31k | m_propList.insert("svg:radialGradient", gradientVector); |
272 | 5.31k | break; |
273 | 32.4k | } |
274 | 32.4k | } |
275 | | |
276 | | void operator()(const ImageFill &imageFill) |
277 | 3.58k | { |
278 | 3.58k | m_propList.insert("draw:fill", "bitmap"); |
279 | | |
280 | 3.58k | m_propList.insert("draw:fill-image", imageFill.image.data); |
281 | 3.58k | m_propList.insert("librevenge:mime-type", "image/png"); |
282 | | |
283 | 3.58k | if (imageFill.tile) |
284 | 3.14k | { |
285 | 3.14k | m_propList.insert("style:repeat", "repeat"); |
286 | 3.14k | m_propList.insert("draw:fill-image-width", imageFill.tileWidth, librevenge::RVNG_INCH); |
287 | 3.14k | m_propList.insert("draw:fill-image-height", imageFill.tileHeight, librevenge::RVNG_INCH); |
288 | 3.14k | m_propList.insert("draw:fill-image-ref-point", "top-left"); |
289 | 3.14k | } |
290 | 443 | else |
291 | 443 | { |
292 | 443 | m_propList.insert("style:repeat", "stretch"); |
293 | 443 | } |
294 | | |
295 | 3.58k | if (m_transparency) |
296 | 235 | { |
297 | 235 | m_propList.insert("draw:opacity", get(m_transparency).opacity(), librevenge::RVNG_PERCENT); |
298 | 235 | } |
299 | 3.58k | } |
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 | 20.3k | : m_painter(painter) |
311 | 20.3k | , m_pageSettings() |
312 | 20.3k | , m_isDocumentStarted(false) |
313 | 20.3k | , m_isPageStarted(false) |
314 | 20.3k | , m_isLayerStarted(false) |
315 | 20.3k | , m_style() |
316 | 20.3k | { |
317 | 20.3k | } |
318 | | |
319 | | ZMFCollector::~ZMFCollector() |
320 | 20.3k | { |
321 | 20.3k | if (m_isDocumentStarted) |
322 | 19.3k | { |
323 | 19.3k | endDocument(); |
324 | 19.3k | } |
325 | 20.3k | } |
326 | | |
327 | | void ZMFCollector::startDocument() |
328 | 20.3k | { |
329 | 20.3k | if (m_isDocumentStarted) |
330 | 0 | return; |
331 | | |
332 | 20.3k | librevenge::RVNGPropertyList propList; |
333 | | |
334 | 20.3k | m_painter->startDocument(propList); |
335 | | |
336 | 20.3k | m_isDocumentStarted = true; |
337 | 20.3k | } |
338 | | |
339 | | void ZMFCollector::endDocument() |
340 | 20.3k | { |
341 | 20.3k | if (!m_isDocumentStarted) |
342 | 0 | return; |
343 | | |
344 | 20.3k | if (m_isPageStarted) |
345 | 18.7k | endPage(); |
346 | | |
347 | 20.3k | m_painter->endDocument(); |
348 | | |
349 | 20.3k | m_isDocumentStarted = false; |
350 | 20.3k | } |
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 | 13.6k | { |
399 | 13.6k | if (!m_isLayerStarted) |
400 | 0 | return; |
401 | | |
402 | 13.6k | m_painter->endLayer(); |
403 | | |
404 | 13.6k | m_isLayerStarted = false; |
405 | 13.6k | } |
406 | | |
407 | | void ZMFCollector::startGroup() |
408 | 804 | { |
409 | 804 | librevenge::RVNGPropertyList propList; |
410 | | |
411 | 804 | m_painter->openGroup(propList); |
412 | 804 | } |
413 | | |
414 | | void ZMFCollector::endGroup() |
415 | 256 | { |
416 | 256 | m_painter->closeGroup(); |
417 | 256 | } |
418 | | |
419 | | void ZMFCollector::setStyle(const Style &style) |
420 | 128k | { |
421 | 128k | m_style = style; |
422 | 128k | } |
423 | | |
424 | | void ZMFCollector::collectPath(const std::vector<Curve> &curves) |
425 | 75.4k | { |
426 | 75.4k | librevenge::RVNGPropertyList pathPropList; |
427 | | |
428 | 75.4k | writeStyle(pathPropList, m_style, |
429 | 75.4k | !std::any_of(curves.begin(), curves.end(), [](const Curve &c) |
430 | 75.4k | { |
431 | 55.3k | return c.closed; |
432 | 55.3k | })); |
433 | 75.4k | m_painter->setStyle(pathPropList); |
434 | 75.4k | pathPropList.clear(); |
435 | | |
436 | 75.4k | auto path = createPath(curves, m_pageSettings.leftOffset, m_pageSettings.topOffset); |
437 | | |
438 | 75.4k | pathPropList.insert("svg:d", path); |
439 | | |
440 | 75.4k | m_painter->drawPath(pathPropList); |
441 | 75.4k | } |
442 | | |
443 | | void ZMFCollector::collectPath(const Curve &curve) |
444 | 54.1k | { |
445 | 54.1k | collectPath(std::vector<Curve> { curve }); |
446 | 54.1k | } |
447 | | |
448 | | void ZMFCollector::collectEllipse(const Point &c, double rx, double ry, double rotation) |
449 | 21.7k | { |
450 | 21.7k | librevenge::RVNGPropertyList ellipse; |
451 | | |
452 | 21.7k | writeStyle(ellipse, m_style); |
453 | 21.7k | m_painter->setStyle(ellipse); |
454 | 21.7k | ellipse.clear(); |
455 | | |
456 | 21.7k | ellipse.insert("svg:cx", pageX(c.x)); |
457 | 21.7k | ellipse.insert("svg:cy", pageY(c.y)); |
458 | 21.7k | ellipse.insert("svg:rx", rx); |
459 | 21.7k | ellipse.insert("svg:ry", ry); |
460 | 21.7k | if (!ZMF_ALMOST_ZERO(rotation)) |
461 | 21.6k | { |
462 | 21.6k | ellipse.insert("librevenge:rotate", -rad2deg(rotation)); |
463 | 21.6k | } |
464 | | |
465 | 21.7k | m_painter->drawEllipse(ellipse); |
466 | 21.7k | } |
467 | | |
468 | | void ZMFCollector::collectArc(const Point &c, double rx, double ry, double beginAngle, double endAngle, bool closed, double rotation) |
469 | 8.12k | { |
470 | 8.12k | using namespace boost::math::double_constants; |
471 | | |
472 | 8.12k | librevenge::RVNGPropertyList pathPropList; |
473 | | |
474 | 8.12k | writeStyle(pathPropList, m_style, !closed); |
475 | 8.12k | m_painter->setStyle(pathPropList); |
476 | 8.12k | pathPropList.clear(); |
477 | | |
478 | 8.12k | auto beginPoint = calculateEllipsePoint(c, rx, ry, beginAngle); |
479 | 8.12k | auto endPoint = calculateEllipsePoint(c, rx, ry, endAngle); |
480 | | |
481 | 8.12k | if (!ZMF_ALMOST_ZERO(rotation)) |
482 | 7.91k | { |
483 | 7.91k | beginPoint = beginPoint.rotate(rotation, c); |
484 | 7.91k | endPoint = endPoint.rotate(rotation, c); |
485 | 7.91k | } |
486 | | |
487 | 8.12k | double angleDiff = std::fabs(endAngle - beginAngle); |
488 | 8.12k | bool largeArc = (beginAngle < endAngle && angleDiff > pi) || |
489 | 7.21k | (beginAngle > endAngle && angleDiff < pi); |
490 | | |
491 | 8.12k | librevenge::RVNGPropertyListVector path; |
492 | | |
493 | 8.12k | { |
494 | 8.12k | librevenge::RVNGPropertyList pathStart; |
495 | 8.12k | pathStart.insert("librevenge:path-action", "M"); |
496 | 8.12k | pathStart.insert("svg:x", pageX(beginPoint.x)); |
497 | 8.12k | pathStart.insert("svg:y", pageY(beginPoint.y)); |
498 | | |
499 | 8.12k | path.append(pathStart); |
500 | 8.12k | } |
501 | | |
502 | 8.12k | { |
503 | 8.12k | librevenge::RVNGPropertyList arc; |
504 | 8.12k | arc.insert("librevenge:path-action", "A"); |
505 | 8.12k | arc.insert("svg:rx", rx); |
506 | 8.12k | arc.insert("svg:ry", ry); |
507 | 8.12k | arc.insert("librevenge:large-arc", largeArc ? 1 : 0); |
508 | 8.12k | arc.insert("librevenge:sweep", 1); |
509 | 8.12k | arc.insert("svg:x", pageX(endPoint.x)); |
510 | 8.12k | arc.insert("svg:y", pageY(endPoint.y)); |
511 | | |
512 | 8.12k | path.append(arc); |
513 | 8.12k | } |
514 | | |
515 | 8.12k | if (closed) |
516 | 1.74k | { |
517 | 1.74k | librevenge::RVNGPropertyList pathEndLine; |
518 | 1.74k | pathEndLine.insert("librevenge:path-action", "L"); |
519 | 1.74k | pathEndLine.insert("svg:x", pageX(c.x)); |
520 | 1.74k | pathEndLine.insert("svg:y", pageY(c.y)); |
521 | | |
522 | 1.74k | path.append(pathEndLine); |
523 | | |
524 | 1.74k | librevenge::RVNGPropertyList pathEnd; |
525 | 1.74k | pathEnd.insert("librevenge:path-action", "Z"); |
526 | | |
527 | 1.74k | path.append(pathEnd); |
528 | 1.74k | } |
529 | | |
530 | 8.12k | pathPropList.insert("svg:d", path); |
531 | | |
532 | 8.12k | m_painter->drawPath(pathPropList); |
533 | 8.12k | } |
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 | 12.2k | { |
538 | 12.2k | using namespace boost::math::double_constants; |
539 | | |
540 | 12.2k | if (peak.points.size() < 2) |
541 | 11.7k | { |
542 | 11.7k | return; |
543 | 11.7k | } |
544 | | |
545 | 561 | 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 | 561 | std::vector<Point> side; |
550 | 561 | side.reserve(peak.points.size()); |
551 | 561 | std::transform(peak.points.begin(), peak.points.end(), std::back_inserter(side), [peakAngle](const Point &p) |
552 | 2.58k | { |
553 | 2.58k | return calculateEllipsePoint(Point(0, 0), p.y, p.y, p.x * peakAngle); |
554 | 2.58k | }); |
555 | | |
556 | 561 | Curve polygonCurve; |
557 | 561 | polygonCurve.points.reserve(peak.points.size() * peaksCount); |
558 | 561 | polygonCurve.sectionTypes.reserve(peak.sectionTypes.size() * peaksCount); |
559 | | |
560 | | // generate complete polygon |
561 | 27.8k | for (uint32_t i = 0; i < peaksCount; ++i) |
562 | 27.3k | { |
563 | 27.3k | std::transform(side.begin() + (i == 0 ? 0 : 1), side.end(), std::back_inserter(polygonCurve.points), [i, peakAngle](Point p) |
564 | 110k | { |
565 | 110k | p = p.rotate(i * peakAngle, Point(0, 0)); |
566 | 110k | return p; |
567 | 110k | }); |
568 | 27.3k | std::copy(peak.sectionTypes.begin(), peak.sectionTypes.end(), std::back_inserter(polygonCurve.sectionTypes)); |
569 | 27.3k | } |
570 | | |
571 | | // fit the polygon into the bbox and mirror |
572 | 561 | for (auto &p: polygonCurve.points) |
573 | 110k | { |
574 | 110k | p.x = p.x * rx; |
575 | 110k | p.y = p.y * ry; |
576 | 110k | p = p.move(c.x, c.y); |
577 | | |
578 | 110k | p.y = -p.y; |
579 | 110k | p = p.move(0, 2 * c.y); |
580 | | |
581 | 110k | if (mirrorHorizontal) |
582 | 46.5k | { |
583 | 46.5k | p.x = -p.x; |
584 | 46.5k | p = p.move(2 * c.x, 0); |
585 | 46.5k | } |
586 | 110k | if (mirrorVertical) |
587 | 44.6k | { |
588 | 44.6k | p.y = -p.y; |
589 | 44.6k | p = p.move(0, 2 * c.y); |
590 | 44.6k | } |
591 | | |
592 | 110k | p = p.rotate(rotation, c); |
593 | 110k | } |
594 | | |
595 | 561 | polygonCurve.closed = true; |
596 | 561 | collectPath(polygonCurve); |
597 | 561 | } |
598 | | |
599 | | void ZMFCollector::collectTextObject(const Text &text, const Point &topLeft, double width, double height, |
600 | | VerticalAlignment align, double rotation) |
601 | 3.50k | { |
602 | 3.50k | librevenge::RVNGPropertyList textObjPropList; |
603 | | |
604 | 3.50k | textObjPropList.insert("svg:x", pageX(topLeft.x)); |
605 | 3.50k | textObjPropList.insert("svg:y", pageY(topLeft.y)); |
606 | 3.50k | textObjPropList.insert("svg:width", width); |
607 | 3.50k | textObjPropList.insert("svg:height", height); |
608 | | |
609 | 3.50k | switch (align) |
610 | 3.50k | { |
611 | 2.48k | case VerticalAlignment::TOP: |
612 | 2.48k | textObjPropList.insert("draw:textarea-vertical-align", "top"); |
613 | 2.48k | break; |
614 | 464 | case VerticalAlignment::MIDDLE: |
615 | 464 | textObjPropList.insert("draw:textarea-vertical-align", "middle"); |
616 | 464 | break; |
617 | 557 | case VerticalAlignment::BOTTOM: |
618 | 557 | textObjPropList.insert("draw:textarea-vertical-align", "bottom"); |
619 | 557 | break; |
620 | 3.50k | } |
621 | 3.50k | if (!ZMF_ALMOST_ZERO(rotation)) |
622 | 2.98k | { |
623 | 2.98k | textObjPropList.insert("librevenge:rotate", rad2deg(rotation)); |
624 | 2.98k | } |
625 | | |
626 | 3.50k | m_painter->startTextObject(textObjPropList); |
627 | | |
628 | 3.50k | collectText(text); |
629 | | |
630 | 3.50k | m_painter->endTextObject(); |
631 | 3.50k | } |
632 | | |
633 | | void ZMFCollector::collectText(const Text &text) |
634 | 202k | { |
635 | 202k | for (auto ¶graph : text.paragraphs) |
636 | 56.9k | { |
637 | 56.9k | librevenge::RVNGPropertyList paragraphPropList; |
638 | | |
639 | 56.9k | paragraphPropList.insert("fo:line-height", paragraph.style.lineSpacing, librevenge::RVNG_PERCENT); |
640 | | |
641 | 56.9k | switch (paragraph.style.alignment) |
642 | 56.9k | { |
643 | 56.2k | case HorizontalAlignment::LEFT: |
644 | 56.2k | paragraphPropList.insert("fo:text-align", "left"); |
645 | 56.2k | break; |
646 | 126 | case HorizontalAlignment::RIGHT: |
647 | 126 | paragraphPropList.insert("fo:text-align", "end"); |
648 | 126 | break; |
649 | 35 | case HorizontalAlignment::CENTER: |
650 | 35 | paragraphPropList.insert("fo:text-align", "center"); |
651 | 35 | break; |
652 | 176 | case HorizontalAlignment::BLOCK: |
653 | 480 | case HorizontalAlignment::FULL: |
654 | 480 | paragraphPropList.insert("fo:text-align", "justify"); |
655 | 480 | break; |
656 | 56.9k | } |
657 | | |
658 | 56.9k | m_painter->openParagraph(paragraphPropList); |
659 | | |
660 | 56.9k | for (auto &span : paragraph.spans) |
661 | 137k | { |
662 | 137k | librevenge::RVNGPropertyList spanPropList; |
663 | | |
664 | 137k | spanPropList.insert("style:font-name", span.font.name); |
665 | 137k | spanPropList.insert("fo:font-size", span.font.size, librevenge::RVNG_POINT); |
666 | 137k | spanPropList.insert("fo:font-weight", span.font.isBold ? "bold" : "normal"); |
667 | 137k | spanPropList.insert("fo:font-style", span.font.isItalic ? "italic" : "normal"); |
668 | 137k | spanPropList.insert("style:text-outline", static_cast<bool>(span.font.outline)); |
669 | | |
670 | 137k | if (span.font.fill && get(span.font.fill).type() == typeid(Color)) |
671 | 75.0k | { |
672 | 75.0k | auto color = boost::get<Color>(get(span.font.fill)); |
673 | 75.0k | spanPropList.insert("fo:color", color.toString()); |
674 | 75.0k | } |
675 | | |
676 | 137k | m_painter->openSpan(spanPropList); |
677 | | |
678 | 137k | bool wasSpace = false; |
679 | 137k | std::string curText; |
680 | | |
681 | 137k | librevenge::RVNGString::Iter iter(span.text); |
682 | 137k | iter.rewind(); |
683 | 1.01M | while (iter.next()) |
684 | 881k | { |
685 | 881k | const char *const utf8Char = iter(); |
686 | 881k | switch (utf8Char[0]) |
687 | 881k | { |
688 | | // looks like Zoner Draw doesn't allow tabs, |
689 | | // and \r (without \n) can be only at the end of paragraph |
690 | 3.32k | case '\r': |
691 | 8.34k | case '\n': |
692 | 8.34k | break; |
693 | 8.38k | case ' ': |
694 | 8.38k | if (wasSpace) |
695 | 2.86k | { |
696 | 2.86k | flushText(curText); |
697 | 2.86k | m_painter->insertSpace(); |
698 | 2.86k | } |
699 | 5.52k | else |
700 | 5.52k | { |
701 | 5.52k | wasSpace = true; |
702 | 5.52k | curText.push_back(' '); |
703 | 5.52k | } |
704 | 8.38k | break; |
705 | 865k | default: |
706 | 865k | wasSpace = false; |
707 | 865k | curText.append(utf8Char); |
708 | 865k | break; |
709 | 881k | } |
710 | 881k | } |
711 | | |
712 | 137k | flushText(curText); |
713 | | |
714 | 137k | m_painter->closeSpan(); |
715 | 137k | } |
716 | | |
717 | 56.9k | m_painter->closeParagraph(); |
718 | 56.9k | } |
719 | 202k | } |
720 | | |
721 | | void ZMFCollector::flushText(std::string &text) |
722 | 140k | { |
723 | 140k | if (!text.empty()) |
724 | 60.0k | { |
725 | 60.0k | m_painter->insertText(librevenge::RVNGString(text.c_str())); |
726 | 60.0k | text.clear(); |
727 | 60.0k | } |
728 | 140k | } |
729 | | |
730 | | void ZMFCollector::collectTable(const Table &table) |
731 | 15.6k | { |
732 | 15.6k | librevenge::RVNGPropertyList tablePropList; |
733 | | |
734 | 15.6k | tablePropList.insert("svg:x", pageX(table.topLeftPoint.x)); |
735 | 15.6k | tablePropList.insert("svg:y", pageY(table.topLeftPoint.y)); |
736 | 15.6k | tablePropList.insert("svg:width", table.width); |
737 | 15.6k | tablePropList.insert("svg:height", table.height); |
738 | | |
739 | 15.6k | librevenge::RVNGPropertyListVector columnSizes; |
740 | | |
741 | 15.6k | for (const auto &col : table.columns) |
742 | 40.8k | { |
743 | 40.8k | librevenge::RVNGPropertyList columnPropList; |
744 | 40.8k | columnPropList.insert("style:column-width", col.width); |
745 | 40.8k | columnSizes.append(columnPropList); |
746 | 40.8k | } |
747 | | |
748 | 15.6k | tablePropList.insert("librevenge:table-columns", columnSizes); |
749 | | |
750 | 15.6k | m_painter->startTableObject(tablePropList); |
751 | | |
752 | 15.6k | for (const auto &row : table.rows) |
753 | 116k | { |
754 | 116k | librevenge::RVNGPropertyList rowPropList; |
755 | 116k | rowPropList.insert("style:row-height", row.height); |
756 | 116k | m_painter->openTableRow(rowPropList); |
757 | | |
758 | 116k | for (const Cell &cell : row.cells) |
759 | 199k | { |
760 | 199k | librevenge::RVNGPropertyList cellPropList; |
761 | | |
762 | 199k | if (cell.fill && get(cell.fill).type() == typeid(Color)) |
763 | 31.2k | { |
764 | 31.2k | auto backgroundColor = boost::get<Color>(get(cell.fill)); |
765 | 31.2k | cellPropList.insert("fo:background-color", backgroundColor.toString()); |
766 | 31.2k | } |
767 | | |
768 | 199k | cellPropList.insert("draw:textarea-vertical-align", "middle"); |
769 | | |
770 | 199k | if (cell.leftBorder) |
771 | 107k | { |
772 | 107k | writeBorder(cellPropList, "fo:border-left", get(cell.leftBorder)); |
773 | 107k | } |
774 | 199k | if (cell.rightBorder) |
775 | 109k | { |
776 | 109k | writeBorder(cellPropList, "fo:border-right", get(cell.rightBorder)); |
777 | 109k | } |
778 | 199k | if (cell.topBorder) |
779 | 97.7k | { |
780 | 97.7k | writeBorder(cellPropList, "fo:border-top", get(cell.topBorder)); |
781 | 97.7k | } |
782 | 199k | if (cell.bottomBorder) |
783 | 71.5k | { |
784 | 71.5k | writeBorder(cellPropList, "fo:border-bottom", get(cell.bottomBorder)); |
785 | 71.5k | } |
786 | | |
787 | 199k | m_painter->openTableCell(cellPropList); |
788 | | |
789 | 199k | collectText(cell.text); |
790 | | |
791 | 199k | m_painter->closeTableCell(); |
792 | 199k | } |
793 | | |
794 | 116k | m_painter->closeTableRow(); |
795 | 116k | } |
796 | | |
797 | 15.6k | m_painter->endTableObject(); |
798 | 15.6k | } |
799 | | |
800 | | void ZMFCollector::collectImage(const librevenge::RVNGBinaryData &image, const Point &topLeft, double width, double height, |
801 | | double rotation, bool mirrorHorizontal, bool mirrorVertical) |
802 | 12.5k | { |
803 | 12.5k | librevenge::RVNGPropertyList propList; |
804 | | |
805 | 12.5k | writeStyle(propList, m_style); |
806 | | |
807 | 12.5k | if (m_style.transparency) |
808 | 1 | { |
809 | 1 | propList.insert("draw:opacity", get(m_style.transparency).opacity(), librevenge::RVNG_PERCENT); |
810 | 1 | } |
811 | | |
812 | 12.5k | m_painter->setStyle(propList); |
813 | 12.5k | propList.clear(); |
814 | | |
815 | 12.5k | propList.insert("svg:x", pageX(topLeft.x)); |
816 | 12.5k | propList.insert("svg:y", pageY(topLeft.y)); |
817 | 12.5k | propList.insert("svg:width", width); |
818 | 12.5k | propList.insert("svg:height", height); |
819 | 12.5k | if (!ZMF_ALMOST_ZERO(rotation)) |
820 | 1.33k | { |
821 | 1.33k | propList.insert("librevenge:rotate", rad2deg(rotation)); |
822 | 1.33k | } |
823 | 12.5k | propList.insert("draw:mirror-vertical", mirrorVertical); |
824 | 12.5k | propList.insert("draw:mirror-horizontal", mirrorHorizontal); |
825 | 12.5k | propList.insert("librevenge:mime-type", "image/png"); |
826 | 12.5k | propList.insert("office:binary-data", image); |
827 | | |
828 | 12.5k | m_painter->drawGraphicObject(propList); |
829 | 12.5k | } |
830 | | |
831 | | double ZMFCollector::pageX(double canvasX) |
832 | 71.3k | { |
833 | 71.3k | return canvasX - m_pageSettings.leftOffset; |
834 | 71.3k | } |
835 | | |
836 | | double ZMFCollector::pageY(double canvasY) |
837 | 71.3k | { |
838 | 71.3k | return canvasY - m_pageSettings.topOffset; |
839 | 71.3k | } |
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 | 117k | { |
845 | 117k | propList.insert("draw:stroke", "none"); |
846 | 117k | propList.insert("draw:fill", "none"); |
847 | | |
848 | 117k | if (style.pen) |
849 | 40.3k | { |
850 | 40.3k | writePen(propList, get(style.pen)); |
851 | 40.3k | } |
852 | | |
853 | 117k | if (style.fill && !noFill) |
854 | 47.4k | { |
855 | 47.4k | writeFill(propList, get(style.fill)); |
856 | 47.4k | } |
857 | | |
858 | 117k | if (style.shadow) |
859 | 18.2k | { |
860 | 18.2k | writeShadow(propList, get(style.shadow)); |
861 | 18.2k | } |
862 | 117k | } |
863 | | |
864 | | void ZMFCollector::writePen(librevenge::RVNGPropertyList &propList, const Pen &pen) |
865 | 40.3k | { |
866 | 40.3k | propList.insert("svg:stroke-color", pen.color.toString()); |
867 | 40.3k | if (!ZMF_ALMOST_ZERO(pen.width)) |
868 | 9.49k | propList.insert("svg:stroke-width", pen.width); |
869 | | |
870 | 40.3k | if (pen.dashPattern.size() > 0) |
871 | 32.8k | { |
872 | 32.8k | double dots1 = pen.dashPattern[0]; |
873 | 32.8k | double dots2 = pen.dashPattern[0]; |
874 | 32.8k | double dist = pen.dashDistance; |
875 | 32.8k | if (pen.dashPattern.size() >= 3) |
876 | 22.1k | { |
877 | 22.1k | dist = pen.dashPattern[1]; |
878 | 22.1k | dots2 = pen.dashPattern[2]; |
879 | 22.1k | } |
880 | | |
881 | 32.8k | propList.insert("draw:stroke", "dash"); |
882 | 32.8k | propList.insert("draw:dots1", 1); |
883 | 32.8k | propList.insert("draw:dots1-length", dots1, librevenge::RVNG_PERCENT); |
884 | 32.8k | propList.insert("draw:dots2", 1); |
885 | 32.8k | propList.insert("draw:dots2-length", dots2, librevenge::RVNG_PERCENT); |
886 | 32.8k | propList.insert("draw:distance", dist, librevenge::RVNG_PERCENT); |
887 | 32.8k | } |
888 | 7.52k | else |
889 | 7.52k | { |
890 | 7.52k | propList.insert("draw:stroke", "solid"); |
891 | 7.52k | } |
892 | | |
893 | 40.3k | switch (pen.lineCapType) |
894 | 40.3k | { |
895 | 3 | default: |
896 | 39.1k | case LineCapType::BUTT: |
897 | 39.1k | propList.insert("svg:stroke-linecap", "butt"); |
898 | 39.1k | break; |
899 | 184 | case LineCapType::ROUND: |
900 | 184 | propList.insert("svg:stroke-linecap", "round"); |
901 | 184 | break; |
902 | 1.02k | case LineCapType::FLAT: |
903 | 1.02k | propList.insert("svg:stroke-linecap", "square"); |
904 | 1.02k | break; |
905 | 40.3k | } |
906 | | |
907 | 40.3k | switch (pen.lineJoinType) |
908 | 40.3k | { |
909 | 0 | default: |
910 | 637 | case LineJoinType::BEVEL: |
911 | 637 | propList.insert("svg:stroke-linejoin", "bevel"); |
912 | 637 | break; |
913 | 39.5k | case LineJoinType::MITER: |
914 | 39.5k | propList.insert("svg:stroke-linejoin", "miter"); |
915 | 39.5k | break; |
916 | 151 | case LineJoinType::ROUND: |
917 | 151 | propList.insert("svg:stroke-linejoin", "round"); |
918 | 151 | break; |
919 | 40.3k | } |
920 | | |
921 | 40.3k | if (m_style.transparency) |
922 | 3.00k | { |
923 | 3.00k | propList.insert("svg:stroke-opacity", get(m_style.transparency).opacity(), librevenge::RVNG_PERCENT); |
924 | 3.00k | } |
925 | | |
926 | 40.3k | if (pen.startArrow) |
927 | 24.9k | { |
928 | 24.9k | writeArrow(propList, "start", *pen.startArrow, pen.width); |
929 | 24.9k | } |
930 | 40.3k | if (pen.endArrow) |
931 | 28.8k | { |
932 | 28.8k | writeArrow(propList, "end", *pen.endArrow, pen.width); |
933 | 28.8k | } |
934 | 40.3k | } |
935 | | |
936 | | void ZMFCollector::writeFill(librevenge::RVNGPropertyList &propList, const Fill &fill) |
937 | 47.4k | { |
938 | 47.4k | FillWriter fillWriter(propList, m_style.transparency); |
939 | 47.4k | boost::apply_visitor(fillWriter, fill); |
940 | | |
941 | 47.4k | propList.insert("svg:fill-rule", "evenodd"); |
942 | 47.4k | } |
943 | | |
944 | | void ZMFCollector::writeShadow(librevenge::RVNGPropertyList &propList, const Shadow &shadow) |
945 | 18.2k | { |
946 | 18.2k | propList.insert("draw:shadow", "visible"); |
947 | 18.2k | propList.insert("draw:shadow-color", shadow.color.toString()); |
948 | 18.2k | propList.insert("draw:shadow-opacity", shadow.opacity, librevenge::RVNG_PERCENT); |
949 | 18.2k | propList.insert("draw:shadow-offset-x", shadow.offset.x); |
950 | 18.2k | propList.insert("draw:shadow-offset-y", shadow.offset.y); |
951 | 18.2k | } |
952 | | |
953 | | } |
954 | | |
955 | | /* vim:set shiftwidth=2 softtabstop=2 expandtab: */ |