Coverage Report

Created: 2026-07-20 07:04

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
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 &sectionType : 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 &paragraph : 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: */