Coverage Report

Created: 2026-10-28 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 &sectionType : 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 &paragraph : 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: */