Coverage Report

Created: 2026-03-12 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libqxp/src/lib/QXPContentCollector.cpp
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/*
3
 * This file is part of the libqxp 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 "QXPContentCollector.h"
11
12
#include <algorithm>
13
#include <utility>
14
#include <iterator>
15
16
#include <boost/range/adaptor/reversed.hpp>
17
#include <boost/variant.hpp>
18
19
namespace libqxp
20
{
21
22
using librevenge::RVNG_POINT;
23
using librevenge::RVNG_PERCENT;
24
using librevenge::RVNGPropertyList;
25
using librevenge::RVNGPropertyListVector;
26
using librevenge::RVNGString;
27
using std::vector;
28
using std::string;
29
30
namespace
31
{
32
33
void writeBorder(RVNGPropertyList &propList, const char *const name, const double width, const Color &color, const LineStyle *lineStyle)
34
9.55k
{
35
9.55k
  RVNGString border;
36
37
9.55k
  border.sprintf("%fpt", width);
38
39
9.55k
  border.append(" ");
40
9.55k
  if (lineStyle)
41
6.02k
  {
42
6.02k
    if (lineStyle->isStripe)
43
130
    {
44
130
      border.append("double");
45
130
    }
46
6.02k
    if (lineStyle->segmentLengths.size() == 2)
47
1.11k
    {
48
1.11k
      border.append("dotted");
49
1.11k
    }
50
6.02k
    if (lineStyle->segmentLengths.size() > 2)
51
348
    {
52
348
      border.append("dashed");
53
348
    }
54
5.67k
    else
55
5.67k
    {
56
5.67k
      border.append("solid");
57
5.67k
    }
58
6.02k
  }
59
3.52k
  else
60
3.52k
  {
61
3.52k
    border.append("solid");
62
3.52k
  }
63
64
9.55k
  border.append(" ");
65
9.55k
  border.append(color.toString());
66
67
9.55k
  propList.insert(name, border);
68
9.55k
}
69
70
void writeBorder(RVNGPropertyList &propList, const char *const name, const std::shared_ptr<ParagraphRule> &rule)
71
9.55k
{
72
9.55k
  writeBorder(propList, name, rule->width, rule->color, rule->lineStyle);
73
9.55k
}
74
75
RVNGPropertyListVector createLinePath(const vector<Point> &points, bool closed)
76
148k
{
77
148k
  RVNGPropertyListVector path;
78
79
681k
  for (size_t i = 0; i < points.size(); i++)
80
533k
  {
81
533k
    librevenge::RVNGPropertyList pathPart;
82
533k
    pathPart.insert("librevenge:path-action", i == 0 ? "M" : "L");
83
533k
    pathPart.insert("svg:x", points[i].x, RVNG_POINT);
84
533k
    pathPart.insert("svg:y", points[i].y, RVNG_POINT);
85
86
533k
    path.append(pathPart);
87
533k
  }
88
89
148k
  if (closed)
90
80.1k
  {
91
80.1k
    librevenge::RVNGPropertyList pathPart;
92
80.1k
    pathPart.insert("librevenge:path-action", "Z");
93
94
80.1k
    path.append(pathPart);
95
80.1k
  }
96
97
148k
  return path;
98
148k
}
99
100
void addBezierPath(RVNGPropertyListVector &path, const vector<Point> &points, bool canBeClosed)
101
16.3k
{
102
16.3k
  if (points.size() < 6)
103
15.2k
  {
104
15.2k
    QXP_DEBUG_MSG(("Not enough bezier points, %lu\n", points.size()));
105
15.2k
    return;
106
15.2k
  }
107
108
1.05k
  {
109
1.05k
    RVNGPropertyList pathPart;
110
1.05k
    pathPart.insert("librevenge:path-action", "M");
111
1.05k
    pathPart.insert("svg:x", points[1].x, RVNG_POINT);
112
1.05k
    pathPart.insert("svg:y", points[1].y, RVNG_POINT);
113
114
1.05k
    path.append(pathPart);
115
1.05k
  }
116
117
1.05k
  {
118
1.05k
    RVNGPropertyList pathPart;
119
1.05k
    pathPart.insert("librevenge:path-action", "C");
120
1.05k
    pathPart.insert("svg:x1", points[2].x, RVNG_POINT);
121
1.05k
    pathPart.insert("svg:y1", points[2].y, RVNG_POINT);
122
1.05k
    pathPart.insert("svg:x2", points[3].x, RVNG_POINT);
123
1.05k
    pathPart.insert("svg:y2", points[3].y, RVNG_POINT);
124
1.05k
    pathPart.insert("svg:x", points[4].x, RVNG_POINT);
125
1.05k
    pathPart.insert("svg:y", points[4].y, RVNG_POINT);
126
127
1.05k
    path.append(pathPart);
128
1.05k
  }
129
130
44.3k
  for (unsigned i = 6; i < points.size(); i += 3)
131
43.7k
  {
132
43.7k
    if (i + 1 >= points.size())
133
461
    {
134
461
      QXP_DEBUG_MSG(("Unexpected end of bezier points, %u / %lu\n", i, points.size()));
135
461
      break;
136
461
    }
137
43.2k
    RVNGPropertyList pathPart;
138
43.2k
    pathPart.insert("librevenge:path-action", "S");
139
43.2k
    pathPart.insert("svg:x1", points[i].x, RVNG_POINT);
140
43.2k
    pathPart.insert("svg:y1", points[i].y, RVNG_POINT);
141
43.2k
    pathPart.insert("svg:x2", points[3].x, RVNG_POINT);
142
43.2k
    pathPart.insert("svg:y2", points[3].y, RVNG_POINT);
143
43.2k
    pathPart.insert("svg:x", points[i + 1].x, RVNG_POINT);
144
43.2k
    pathPart.insert("svg:y", points[i + 1].y, RVNG_POINT);
145
146
43.2k
    path.append(pathPart);
147
43.2k
  }
148
149
1.05k
  if (canBeClosed && points[1] == points[points.size() - 2])
150
345
  {
151
345
    RVNGPropertyList pathPart;
152
345
    pathPart.insert("librevenge:path-action", "Z");
153
154
345
    path.append(pathPart);
155
345
  }
156
1.05k
}
157
158
void writeZIndex(librevenge::RVNGPropertyList &propList, unsigned value)
159
230k
{
160
230k
  propList.insert("draw:z-index", static_cast<int>(value));
161
230k
}
162
163
void flushText(librevenge::RVNGDrawingInterface *painter, string &text)
164
31.2k
{
165
31.2k
  if (!text.empty())
166
8.72k
  {
167
8.72k
    painter->insertText(RVNGString(text.c_str()));
168
8.72k
    text.clear();
169
8.72k
  }
170
31.2k
}
171
172
void insertText(librevenge::RVNGDrawingInterface *painter, const RVNGString &text)
173
24.9k
{
174
  // TODO: need to remember wasSpace on span change?
175
24.9k
  bool wasSpace = false;
176
24.9k
  std::string curText;
177
178
24.9k
  RVNGString::Iter iter(text);
179
24.9k
  iter.rewind();
180
111k
  while (iter.next())
181
86.3k
  {
182
86.3k
    const char *const utf8Char = iter();
183
86.3k
    switch (utf8Char[0])
184
86.3k
    {
185
405
    case '\r':
186
405
      wasSpace = false;
187
405
      break;
188
1.54k
    case '\n':
189
1.54k
      wasSpace = false;
190
1.54k
      flushText(painter, curText);
191
1.54k
      painter->insertLineBreak();
192
1.54k
      break;
193
1.44k
    case '\t':
194
1.44k
      wasSpace = false;
195
1.44k
      flushText(painter, curText);
196
1.44k
      painter->insertTab();
197
1.44k
      break;
198
5.54k
    case ' ':
199
5.54k
      if (wasSpace)
200
3.29k
      {
201
3.29k
        flushText(painter, curText);
202
3.29k
        painter->insertSpace();
203
3.29k
      }
204
2.25k
      else
205
2.25k
      {
206
2.25k
        wasSpace = true;
207
2.25k
        curText.push_back(' ');
208
2.25k
      }
209
5.54k
      break;
210
77.4k
    default:
211
77.4k
      wasSpace = false;
212
77.4k
      curText.append(utf8Char);
213
77.4k
      break;
214
86.3k
    }
215
86.3k
  }
216
217
24.9k
  flushText(painter, curText);
218
24.9k
}
219
220
void writeArrow(RVNGPropertyList &propList, const char *const name, const Arrow &arrow, double width)
221
9.09k
{
222
9.09k
  librevenge::RVNGString propName;
223
224
9.09k
  propName.sprintf("draw:marker-%s-viewbox", name);
225
9.09k
  propList.insert(propName.cstr(), arrow.viewbox.c_str());
226
9.09k
  propName.sprintf("draw:marker-%s-path", name);
227
9.09k
  propList.insert(propName.cstr(), arrow.path.c_str());
228
9.09k
  propName.sprintf("draw:marker-%s-width", name);
229
9.09k
  propList.insert(propName.cstr(), width * arrow.scale, RVNG_POINT);
230
9.09k
}
231
232
class FillWriter : public boost::static_visitor<void>
233
{
234
public:
235
  explicit FillWriter(RVNGPropertyList &propList)
236
72.1k
    : m_propList(propList)
237
72.1k
  { }
238
239
  void operator()(const Color &color)
240
53.2k
  {
241
53.2k
    m_propList.insert("draw:fill", "solid");
242
53.2k
    m_propList.insert("draw:fill-color", color.toString());
243
53.2k
  }
244
245
  void operator()(const Gradient &gradient)
246
18.8k
  {
247
18.8k
    m_propList.insert("draw:fill", "gradient");
248
18.8k
    m_propList.insert("draw:start-color", gradient.color1.toString());
249
18.8k
    m_propList.insert("draw:end-color", gradient.color2.toString());
250
251
18.8k
    switch (gradient.type)
252
18.8k
    {
253
19
    default:
254
18.6k
    case GradientType::LINEAR:
255
18.6k
      m_propList.insert("draw:style", "linear");
256
18.6k
      m_propList.insert("draw:angle", int(normalizeDegAngle(gradient.angle + 90)));
257
18.6k
      break;
258
133
    case GradientType::CIRCULAR:
259
159
    case GradientType::FULLCIRCULAR:
260
159
      m_propList.insert("draw:style", "radial");
261
159
      m_propList.insert("draw:cx", 0.5, librevenge::RVNG_PERCENT);
262
159
      m_propList.insert("draw:cy", 0.5, librevenge::RVNG_PERCENT);
263
159
      m_propList.insert("draw:border", GradientType::CIRCULAR == gradient.type ? 0.25 : 0.0, librevenge::RVNG_PERCENT);
264
159
      m_propList.insert("draw:angle", int(normalizeDegAngle(gradient.angle)));
265
159
      break;
266
13
    case GradientType::RECTANGULAR:
267
27
    case GradientType::DIAMOND:
268
27
      m_propList.insert("draw:style", "square");
269
27
      m_propList.insert("draw:cx", 0.5, librevenge::RVNG_PERCENT);
270
27
      m_propList.insert("draw:cy", 0.5, librevenge::RVNG_PERCENT);
271
27
      m_propList.insert("draw:border", 0.0, librevenge::RVNG_PERCENT);
272
27
      m_propList.insert("draw:angle", int(normalizeDegAngle(gradient.angle)));
273
27
      break;
274
18.8k
    }
275
18.8k
  }
276
277
private:
278
  librevenge::RVNGPropertyList &m_propList;
279
};
280
281
void writeTextPosition(RVNGPropertyList &propList, const double offset, const double scale = 1.0)
282
16.8k
{
283
16.8k
  RVNGString pos;
284
16.8k
  pos.sprintf("%f%% %f%%", 100 * offset, 100 * scale);
285
16.8k
  propList.insert("style:text-position", pos);
286
16.8k
}
287
288
}
289
290
QXPContentCollector::QXPContentCollector(librevenge::RVNGDrawingInterface *painter)
291
5.99k
  : m_painter(painter)
292
5.99k
  , m_isDocumentStarted(false)
293
5.99k
  , m_isCollectingFacingPage(false)
294
5.99k
  , m_currentObjectIndex(0)
295
5.99k
  , m_unprocessedPages()
296
5.99k
  , m_linkTextMap()
297
5.99k
  , m_linkIndexedTextObjectsMap()
298
5.99k
  , m_docProps()
299
5.99k
{
300
5.99k
}
301
302
QXPContentCollector::~QXPContentCollector()
303
5.99k
{
304
5.99k
  if (m_isDocumentStarted)
305
5.59k
  {
306
5.59k
    endDocument();
307
5.59k
  }
308
5.99k
}
309
310
void QXPContentCollector::startDocument()
311
5.99k
{
312
5.99k
  if (m_isDocumentStarted)
313
0
    return;
314
315
5.99k
  m_painter->startDocument(RVNGPropertyList());
316
317
5.99k
  m_isDocumentStarted = true;
318
5.99k
}
319
320
void QXPContentCollector::endDocument()
321
5.99k
{
322
5.99k
  if (!m_isDocumentStarted)
323
0
    return;
324
325
5.99k
  if (!m_unprocessedPages.empty())
326
3.79k
    endPage();
327
328
5.99k
  if (!m_unprocessedPages.empty())
329
2.59k
    draw(true);
330
331
5.99k
  m_painter->endDocument();
332
333
5.99k
  m_isDocumentStarted = false;
334
5.99k
}
335
336
void QXPContentCollector::startPage(const Page &page)
337
7.44k
{
338
7.44k
  m_unprocessedPages.push_back(CollectedPage(page.pageSettings[0]));
339
7.44k
  if (page.isFacing())
340
585
  {
341
585
    m_unprocessedPages.push_back(CollectedPage(page.pageSettings[1]));
342
585
  }
343
7.44k
  m_isCollectingFacingPage = page.isFacing();
344
7.44k
  m_currentObjectIndex = 0;
345
7.44k
}
346
347
void QXPContentCollector::endPage()
348
7.57k
{
349
7.57k
  if (!m_unprocessedPages.empty())
350
7.57k
    draw();
351
7.57k
}
352
353
void QXPContentCollector::collectDocumentProperties(const QXPDocumentProperties &props)
354
5.78k
{
355
5.78k
  m_docProps = props;
356
5.78k
}
357
358
void QXPContentCollector::collectLine(const std::shared_ptr<Line> &line)
359
34.7k
{
360
34.7k
  addObject<Line>(line, &QXPContentCollector::drawLine);
361
34.7k
}
362
363
void QXPContentCollector::collectBox(const std::shared_ptr<Box> &box)
364
10.4k
{
365
10.4k
  addObject<Box>(box, &QXPContentCollector::drawBox);
366
10.4k
}
367
368
void QXPContentCollector::collectTextBox(const std::shared_ptr<TextBox> &textbox)
369
73.8k
{
370
73.8k
  addObject<TextBox>(textbox, &QXPContentCollector::drawTextBox);
371
372
73.8k
  if (0 == textbox->linkSettings.linkId)
373
57.1k
  {
374
57.1k
    QXP_DEBUG_MSG(("Collected textbox with link ID 0"));
375
57.1k
  }
376
377
73.8k
  collectTextObject(textbox, getInsertionPage(textbox));
378
73.8k
}
379
380
void QXPContentCollector::collectTextPath(const std::shared_ptr<TextPath> &textPath)
381
33.4k
{
382
33.4k
  addObject<TextPath>(textPath, &QXPContentCollector::drawTextPath);
383
384
33.4k
  if (0 == textPath->linkSettings.linkId)
385
632
  {
386
632
    QXP_DEBUG_MSG(("Collected text path with link ID 0"));
387
632
  }
388
389
33.4k
  collectTextObject(textPath, getInsertionPage(textPath));
390
33.4k
}
391
392
void QXPContentCollector::collectGroup(const std::shared_ptr<Group> &group)
393
25.9k
{
394
25.9k
  auto collectedObj = addObject<Group>(group, &QXPContentCollector::drawGroup);
395
396
25.9k
  getInsertionPage(group).groups.push_back(collectedObj);
397
25.9k
}
398
399
void QXPContentCollector::collectText(const std::shared_ptr<Text> &text, const unsigned linkId)
400
14.1k
{
401
14.1k
  m_linkTextMap[linkId] = text;
402
403
14.1k
  auto it = m_linkIndexedTextObjectsMap.find(linkId);
404
14.1k
  if (it != m_linkIndexedTextObjectsMap.end())
405
6.14k
  {
406
6.14k
    for (const auto &indexTextbox : it->second)
407
44.5k
    {
408
44.5k
      if (!indexTextbox.second->text)
409
3.82k
      {
410
3.82k
        indexTextbox.second->text = text;
411
3.82k
      }
412
44.5k
    }
413
6.14k
  }
414
14.1k
}
415
416
QXPContentCollector::CollectedPage &QXPContentCollector::getInsertionPage(const std::shared_ptr<Object> &obj)
417
311k
{
418
311k
  if (m_isCollectingFacingPage && obj->boundingBox.left < m_unprocessedPages.back().settings.offset.left)
419
44.0k
  {
420
44.0k
    return m_unprocessedPages[m_unprocessedPages.size() - 2];
421
44.0k
  }
422
267k
  return m_unprocessedPages.back();
423
311k
}
424
425
void QXPContentCollector::draw(bool force)
426
10.1k
{
427
10.1k
  updateLinkedTexts();
428
429
10.1k
  if (hasUnfinishedLinkedTexts())
430
5.42k
  {
431
5.42k
    if (!force)
432
2.83k
    {
433
2.83k
      return;
434
2.83k
    }
435
2.59k
    QXP_DEBUG_MSG(("Drawing with unfinished linked texts\n"));
436
2.59k
  }
437
438
7.33k
  for (auto &page : m_unprocessedPages)
439
8.02k
  {
440
8.02k
    RVNGPropertyList propList;
441
8.02k
    propList.insert("svg:width", page.settings.offset.width(), RVNG_POINT);
442
8.02k
    propList.insert("svg:height", page.settings.offset.height(), RVNG_POINT);
443
8.02k
    m_painter->startPage(propList);
444
445
8.02k
    {
446
8.02k
      unsigned i = 0;
447
8.02k
      for (auto &obj : boost::adaptors::reverse(page.objects))
448
178k
      {
449
178k
        obj.second->setZIndex(i);
450
        // we can't just increment by 1 because some objects may need to create several elements (such as box + text)
451
        // also we can't just have a counter instead of this field because groups may not be consecutive
452
178k
        i += 100;
453
178k
      }
454
8.02k
    }
455
456
    // handle groups first
457
    // afaik groups that are part of other group never go before that group
458
8.02k
    for (const auto &group : page.groups)
459
25.9k
    {
460
25.9k
      group->draw(page);
461
25.9k
    }
462
463
8.02k
    for (auto &obj : page.objects)
464
178k
    {
465
178k
      obj.second->draw(page);
466
178k
    }
467
468
8.02k
    m_painter->endPage();
469
8.02k
  }
470
471
7.33k
  m_unprocessedPages.clear();
472
7.33k
}
473
474
void QXPContentCollector::collectTextObject(const std::shared_ptr<TextObject> &textObj, CollectedPage &page)
475
107k
{
476
107k
  if (textObj->linkSettings.linkedIndex > 0)
477
73.8k
  {
478
73.8k
    m_linkIndexedTextObjectsMap[textObj->linkSettings.linkId][textObj->linkSettings.linkedIndex] = textObj;
479
73.8k
  }
480
481
107k
  if (textObj->isLinked())
482
85.4k
  {
483
85.4k
    page.linkedTextObjects.push_back(textObj);
484
85.4k
  }
485
486
107k
  if (!textObj->text)
487
73.8k
  {
488
73.8k
    auto textIt = m_linkTextMap.find(textObj->linkSettings.linkId);
489
73.8k
    if (textIt != m_linkTextMap.end())
490
27.5k
    {
491
27.5k
      textObj->text = textIt->second;
492
27.5k
    }
493
73.8k
  }
494
107k
}
495
496
void QXPContentCollector::updateLinkedTexts()
497
10.1k
{
498
10.1k
  for (const auto &page : m_unprocessedPages)
499
12.4k
  {
500
12.4k
    for (const auto &textObj : page.linkedTextObjects)
501
171k
    {
502
171k
      if (textObj->linkSettings.nextLinkedIndex > 0 && !textObj->linkSettings.textLength)
503
132k
      {
504
132k
        const auto textObjectsIt = m_linkIndexedTextObjectsMap.find(textObj->linkSettings.linkId);
505
132k
        if (textObjectsIt != m_linkIndexedTextObjectsMap.end())
506
124k
        {
507
124k
          const auto &textObjects = textObjectsIt->second;
508
124k
          const auto nextTextObjectIt = textObjects.find(textObj->linkSettings.nextLinkedIndex);
509
124k
          if (nextTextObjectIt != textObjects.end())
510
1.13k
          {
511
1.13k
            textObj->linkSettings.textLength = nextTextObjectIt->second->linkSettings.offsetIntoText - textObj->linkSettings.offsetIntoText;
512
1.13k
          }
513
124k
        }
514
132k
      }
515
171k
    }
516
12.4k
  }
517
10.1k
}
518
519
bool QXPContentCollector::hasUnfinishedLinkedTexts()
520
10.1k
{
521
10.1k
  for (const auto &page : m_unprocessedPages)
522
10.7k
  {
523
10.7k
    for (const auto &textObj : page.linkedTextObjects)
524
6.20k
    {
525
6.20k
      if (!textObj->text || (textObj->linkSettings.nextLinkedIndex > 0 && !textObj->linkSettings.textLength))
526
5.42k
      {
527
5.42k
        return true;
528
5.42k
      }
529
6.20k
    }
530
10.7k
  }
531
532
4.74k
  return false;
533
10.1k
}
534
535
void QXPContentCollector::drawLine(const std::shared_ptr<Line> &line, const QXPContentCollector::CollectedPage &page)
536
68.2k
{
537
68.2k
  RVNGPropertyListVector path;
538
68.2k
  if (line->curveComponents.empty())
539
68.1k
  {
540
68.1k
    vector<Point> points =
541
68.1k
    {
542
68.1k
      page.getPoint(line->boundingBox.topLeft().rotateDeg(-line->rotation, line->boundingBox.center())),
543
68.1k
      page.getPoint(line->boundingBox.bottomRight().rotateDeg(-line->rotation, line->boundingBox.center()))
544
68.1k
    };
545
546
68.1k
    path = createLinePath(points, false);
547
68.1k
  }
548
108
  else
549
108
  {
550
108
    for (const auto &curve : line->curveComponents)
551
13.6k
    {
552
13.6k
      vector<Point> points;
553
13.6k
      points.reserve(curve.points.size());
554
13.6k
      std::transform(curve.points.begin(), curve.points.end(), std::back_inserter(points),
555
13.6k
                     [page, &line](const Point &p)
556
49.0k
      {
557
49.0k
        return page.getPoint(p.rotateDeg(-line->rotation, line->boundingBox.center()));
558
49.0k
      });
559
560
13.6k
      addBezierPath(path, points, false);
561
13.6k
    }
562
108
  }
563
564
68.2k
  RVNGPropertyList propList;
565
566
68.2k
  writeFrame(propList, line->style, line->runaround, true);
567
568
68.2k
  m_painter->setStyle(propList);
569
68.2k
  propList.clear();
570
571
68.2k
  propList.insert("svg:d", path);
572
68.2k
  writeZIndex(propList, line->zIndex);
573
574
68.2k
  m_painter->drawPath(propList);
575
68.2k
}
576
577
void QXPContentCollector::drawBox(const std::shared_ptr<Box> &box, const QXPContentCollector::CollectedPage &page)
578
84.2k
{
579
84.2k
  switch (box->boxType)
580
84.2k
  {
581
15
  default:
582
79.7k
  case BoxType::RECTANGLE:
583
79.7k
    drawRectangle(box, page);
584
79.7k
    break;
585
3.93k
  case BoxType::OVAL:
586
3.93k
    drawOval(box, page);
587
3.93k
    break;
588
404
  case BoxType::POLYGON:
589
404
    drawPolygon(box, page);
590
404
    break;
591
127
  case BoxType::BEZIER:
592
127
    drawBezierBox(box, page);
593
127
    break;
594
84.2k
  }
595
84.2k
}
596
597
void QXPContentCollector::drawRectangle(const std::shared_ptr<Box> &box, const QXPContentCollector::CollectedPage &page)
598
79.7k
{
599
79.7k
  const auto bbox = box->boundingBox.shrink(box->frame.width / 2);
600
79.7k
  vector<Point> points =
601
79.7k
  {
602
79.7k
    page.getPoint(bbox.topLeft()),
603
79.7k
    page.getPoint(bbox.topRight()),
604
79.7k
    page.getPoint(bbox.bottomRight()),
605
79.7k
    page.getPoint(bbox.bottomLeft())
606
79.7k
  };
607
608
79.7k
  if (!QXP_ALMOST_ZERO(box->rotation))
609
31.4k
  {
610
31.4k
    for (auto &p : points)
611
125k
    {
612
125k
      p = p.rotateDeg(-box->rotation, page.getPoint(box->boundingBox.center()));
613
125k
    }
614
31.4k
  }
615
616
79.7k
  auto path = createLinePath(points, true);
617
618
79.7k
  RVNGPropertyList propList;
619
620
79.7k
  writeFrame(propList, box->frame, box->runaround);
621
79.7k
  writeFill(propList, box->fill);
622
623
79.7k
  m_painter->setStyle(propList);
624
79.7k
  propList.clear();
625
626
79.7k
  propList.insert("svg:d", path);
627
79.7k
  writeZIndex(propList, box->zIndex);
628
629
79.7k
  m_painter->drawPath(propList);
630
79.7k
}
631
632
void QXPContentCollector::drawOval(const std::shared_ptr<Box> &oval, const QXPContentCollector::CollectedPage &page)
633
3.93k
{
634
3.93k
  librevenge::RVNGPropertyList propList;
635
636
3.93k
  writeFrame(propList, oval->frame, oval->runaround);
637
3.93k
  writeFill(propList, oval->fill);
638
639
3.93k
  m_painter->setStyle(propList);
640
3.93k
  propList.clear();
641
642
3.93k
  propList.insert("svg:cx", page.getX(oval->boundingBox.center().x), RVNG_POINT);
643
3.93k
  propList.insert("svg:cy", page.getY(oval->boundingBox.center().y), RVNG_POINT);
644
3.93k
  propList.insert("svg:rx", oval->boundingBox.width() / 2 - oval->frame.width / 2, RVNG_POINT);
645
3.93k
  propList.insert("svg:ry", oval->boundingBox.height() / 2 - oval->frame.width / 2, RVNG_POINT);
646
3.93k
  if (!QXP_ALMOST_ZERO(oval->rotation))
647
1.00k
  {
648
1.00k
    propList.insert("librevenge:rotate", oval->rotation);
649
1.00k
  }
650
651
3.93k
  writeZIndex(propList, oval->zIndex);
652
653
3.93k
  m_painter->drawEllipse(propList);
654
3.93k
}
655
656
void QXPContentCollector::drawPolygon(const std::shared_ptr<Box> &polygon, const QXPContentCollector::CollectedPage &page)
657
404
{
658
404
  vector<Point> points;
659
404
  points.reserve(polygon->customPoints.size());
660
404
  std::transform(polygon->customPoints.begin(), polygon->customPoints.end(), std::back_inserter(points),
661
404
                 [page, &polygon](const Point &p)
662
78.3k
  {
663
78.3k
    return page.getPoint(p.rotateDeg(-polygon->rotation, polygon->boundingBox.center()));
664
78.3k
  });
665
666
404
  auto path = createLinePath(points, true);
667
668
404
  RVNGPropertyList propList;
669
670
404
  writeFrame(propList, polygon->frame, polygon->runaround);
671
404
  writeFill(propList, polygon->fill);
672
673
404
  m_painter->setStyle(propList);
674
404
  propList.clear();
675
676
404
  propList.insert("svg:d", path);
677
404
  writeZIndex(propList, polygon->zIndex);
678
679
404
  m_painter->drawPath(propList);
680
404
}
681
682
void QXPContentCollector::drawBezierBox(const std::shared_ptr<Box> &box, const QXPContentCollector::CollectedPage &page)
683
127
{
684
127
  RVNGPropertyListVector path;
685
127
  for (const auto &curve : box->curveComponents)
686
2.66k
  {
687
2.66k
    vector<Point> points;
688
2.66k
    points.reserve(curve.points.size());
689
2.66k
    std::transform(curve.points.begin(), curve.points.end(), std::back_inserter(points),
690
2.66k
                   [page, &box](const Point &p)
691
87.5k
    {
692
87.5k
      return page.getPoint(p.rotateDeg(-box->rotation, box->boundingBox.center()));
693
87.5k
    });
694
695
2.66k
    addBezierPath(path, points, true);
696
2.66k
  }
697
698
127
  RVNGPropertyList propList;
699
700
127
  writeFrame(propList, box->frame, box->runaround);
701
127
  writeFill(propList, box->fill);
702
703
127
  m_painter->setStyle(propList);
704
127
  propList.clear();
705
706
127
  propList.insert("svg:d", path);
707
127
  writeZIndex(propList, box->zIndex);
708
709
127
  m_painter->drawPath(propList);
710
127
}
711
712
void QXPContentCollector::drawTextBox(const std::shared_ptr<TextBox> &textbox, const QXPContentCollector::CollectedPage &page)
713
73.8k
{
714
73.8k
  drawBox(textbox, page);
715
716
73.8k
  const auto bbox = textbox->boundingBox.shrink(textbox->frame.width);
717
718
73.8k
  librevenge::RVNGPropertyList textObjPropList;
719
720
73.8k
  textObjPropList.insert("svg:x", page.getX(bbox.left), RVNG_POINT);
721
73.8k
  textObjPropList.insert("svg:y", page.getY(bbox.top), RVNG_POINT);
722
73.8k
  textObjPropList.insert("svg:width", bbox.width(), RVNG_POINT);
723
73.8k
  textObjPropList.insert("svg:height", bbox.height(), RVNG_POINT);
724
725
73.8k
  textObjPropList.insert("fo:padding-top", 0, RVNG_POINT);
726
73.8k
  textObjPropList.insert("fo:padding-right", 0, RVNG_POINT);
727
73.8k
  textObjPropList.insert("fo:padding-bottom", 0, RVNG_POINT);
728
73.8k
  textObjPropList.insert("fo:padding-left", 3, RVNG_POINT);
729
730
73.8k
  switch (textbox->settings.verticalAlignment)
731
73.8k
  {
732
70.3k
  case VerticalAlignment::TOP:
733
70.3k
    textObjPropList.insert("draw:textarea-vertical-align", "top");
734
70.3k
    break;
735
502
  case VerticalAlignment::CENTER:
736
502
    textObjPropList.insert("draw:textarea-vertical-align", "middle");
737
502
    break;
738
1.28k
  case VerticalAlignment::BOTTOM:
739
1.28k
    textObjPropList.insert("draw:textarea-vertical-align", "bottom");
740
1.28k
    break;
741
1.65k
  case VerticalAlignment::JUSTIFIED:
742
1.65k
    textObjPropList.insert("draw:textarea-vertical-align", "justify");
743
1.65k
    break;
744
73.8k
  }
745
73.8k
  if (!QXP_ALMOST_ZERO(textbox->rotation))
746
27.9k
  {
747
27.9k
    textObjPropList.insert("librevenge:rotate", -textbox->rotation);
748
27.9k
  }
749
750
73.8k
  writeZIndex(textObjPropList, textbox->zIndex + 1);
751
752
73.8k
  m_painter->startTextObject(textObjPropList);
753
754
73.8k
  if (textbox->text)
755
60.9k
  {
756
60.9k
    drawText(textbox->text.get(), textbox->linkSettings);
757
60.9k
  }
758
759
73.8k
  m_painter->endTextObject();
760
73.8k
}
761
762
void QXPContentCollector::drawTextPath(const std::shared_ptr<TextPath> &textPath, const QXPContentCollector::CollectedPage &page)
763
33.4k
{
764
33.4k
  drawLine(textPath, page);
765
766
33.4k
  if (!textPath->text)
767
29.6k
  {
768
29.6k
    return;
769
29.6k
  }
770
771
3.79k
  double lineY;
772
3.79k
  switch (textPath->settings.lineAlignment)
773
3.79k
  {
774
0
  default:
775
3.48k
  case TextPathLineAlignment::TOP:
776
3.48k
    lineY = textPath->boundingBox.top - textPath->style.width / 2;
777
3.48k
    break;
778
284
  case TextPathLineAlignment::CENTER:
779
284
    lineY = textPath->boundingBox.top;
780
284
    break;
781
27
  case TextPathLineAlignment::BOTTOM:
782
27
    lineY = textPath->boundingBox.top + textPath->style.width / 2;
783
27
    break;
784
3.79k
  }
785
786
3.79k
  const double height = textPath->text.get()->maxFontSize();
787
788
3.79k
  double textY;
789
3.79k
  switch (textPath->settings.alignment)
790
3.79k
  {
791
0
  default:
792
28
  case TextPathAlignment::DESCENT:
793
1.70k
  case TextPathAlignment::BASELINE:
794
1.70k
    textY = lineY - height;
795
1.70k
    break;
796
361
  case TextPathAlignment::CENTER:
797
361
    textY = lineY - height / 2;
798
361
    break;
799
1.72k
  case TextPathAlignment::ASCENT:
800
1.72k
    textY = lineY;
801
1.72k
    break;
802
3.79k
  }
803
804
3.79k
  librevenge::RVNGPropertyList textObjPropList;
805
806
3.79k
  textObjPropList.insert("svg:x", page.getX(textPath->boundingBox.left), RVNG_POINT);
807
3.79k
  textObjPropList.insert("svg:y", page.getY(textY), RVNG_POINT);
808
  // shouldn't grow vertically
809
3.79k
  textObjPropList.insert("svg:width", textPath->boundingBox.width() + height, RVNG_POINT);
810
3.79k
  textObjPropList.insert("svg:height", height, RVNG_POINT);
811
812
3.79k
  textObjPropList.insert("fo:padding-top", 0, RVNG_POINT);
813
3.79k
  textObjPropList.insert("fo:padding-right", 0, RVNG_POINT);
814
3.79k
  textObjPropList.insert("fo:padding-bottom", 0, RVNG_POINT);
815
3.79k
  textObjPropList.insert("fo:padding-left", 0, RVNG_POINT);
816
817
3.79k
  if (!QXP_ALMOST_ZERO(textPath->rotation))
818
3.14k
  {
819
3.14k
    textObjPropList.insert("librevenge:rotate", -textPath->rotation);
820
3.14k
  }
821
822
3.79k
  writeZIndex(textObjPropList, textPath->zIndex + 1);
823
824
3.79k
  m_painter->startTextObject(textObjPropList);
825
826
3.79k
  drawText(textPath->text.get(), textPath->linkSettings);
827
828
3.79k
  m_painter->endTextObject();
829
3.79k
}
830
831
void QXPContentCollector::drawText(const std::shared_ptr<Text> &text, const LinkedTextSettings &linkSettings)
832
64.7k
{
833
64.7k
  unsigned long spanTextStart = linkSettings.offsetIntoText;
834
64.7k
  const unsigned long textEnd = linkSettings.textLength ? (spanTextStart + linkSettings.textLength.get()) : text->text.length();
835
836
64.7k
  unsigned paragraphInd = 0;
837
838
64.7k
  for (auto &paragraph : text->paragraphs)
839
50.8k
  {
840
50.8k
    if (paragraph.startIndex >= textEnd)
841
7.71k
    {
842
7.71k
      break;
843
7.71k
    }
844
43.1k
    if (spanTextStart > paragraph.endIndex())
845
16.1k
    {
846
16.1k
      continue;
847
16.1k
    }
848
849
27.0k
    librevenge::RVNGPropertyList paragraphPropList;
850
851
27.0k
    paragraphPropList.insert("fo:margin-top", paragraph.format->margin.top, RVNG_POINT);
852
27.0k
    paragraphPropList.insert("fo:margin-right", paragraph.format->margin.right, RVNG_POINT);
853
27.0k
    paragraphPropList.insert("fo:margin-bottom", paragraph.format->margin.bottom, RVNG_POINT);
854
27.0k
    paragraphPropList.insert("fo:margin-left", paragraph.format->margin.left, RVNG_POINT);
855
856
27.0k
    paragraphPropList.insert("fo:text-indent", paragraph.format->firstLineIndent, RVNG_POINT);
857
858
27.0k
    if (!QXP_ALMOST_ZERO(paragraph.format->leading) && !paragraph.format->incrementalLeading)
859
3.13k
    {
860
3.13k
      paragraphPropList.insert("fo:line-height", paragraph.format->leading, RVNG_POINT);
861
3.13k
    }
862
23.9k
    else
863
23.9k
    {
864
23.9k
      const double fontSize = text->maxFontSize(paragraph);
865
23.9k
      const double initialLeading = fontSize + (m_docProps.isIncrementalAutoLeading() ? m_docProps.autoLeading() : (fontSize * m_docProps.autoLeading()));
866
23.9k
      const double lineHeight = initialLeading + paragraph.format->leading;
867
23.9k
      paragraphPropList.insert("fo:line-height", lineHeight, RVNG_POINT);
868
23.9k
    }
869
870
27.0k
    switch (paragraph.format->alignment)
871
27.0k
    {
872
25.3k
    case HorizontalAlignment::LEFT:
873
25.3k
      paragraphPropList.insert("fo:text-align", "left");
874
25.3k
      break;
875
564
    case HorizontalAlignment::RIGHT:
876
564
      paragraphPropList.insert("fo:text-align", "end");
877
564
      break;
878
296
    case HorizontalAlignment::CENTER:
879
296
      paragraphPropList.insert("fo:text-align", "center");
880
296
      break;
881
27
    case HorizontalAlignment::JUSTIFIED:
882
818
    case HorizontalAlignment::FORCED:
883
818
      paragraphPropList.insert("fo:text-align", "justify");
884
818
      break;
885
27.0k
    }
886
27.0k
    paragraphPropList.insert("fo:text-align-last", "start");
887
888
27.0k
    if (paragraph.format->hj)
889
1.86k
    {
890
1.86k
      paragraphPropList.insert("fo:hyphenate", paragraph.format->hj->hyphenate);
891
1.86k
      if (paragraph.format->hj->maxInRow == 0)
892
1.70k
        paragraphPropList.insert("fo:hyphenation-ladder-count", "no-limit");
893
164
      else
894
164
        paragraphPropList.insert("fo:hyphenation-ladder-count", int(paragraph.format->hj->maxInRow));
895
1.86k
      paragraphPropList.insert("style:justify-single-word", paragraph.format->hj->singleWordJustify);
896
1.86k
    }
897
898
27.0k
    if (!paragraph.format->tabStops.empty())
899
11.0k
    {
900
11.0k
      RVNGPropertyListVector tabs;
901
11.0k
      for (const auto &tab : paragraph.format->tabStops)
902
168k
      {
903
168k
        RVNGPropertyList tabProps;
904
168k
        tabProps.insert("style:position", tab.position, RVNG_POINT);
905
168k
        if (!tab.fillChar.empty())
906
168k
        {
907
168k
          tabProps.insert("style:leader-text", tab.fillChar);
908
168k
        }
909
168k
        switch (tab.type)
910
168k
        {
911
159k
        case TabStopType::LEFT:
912
159k
          tabProps.insert("style:type", "left");
913
159k
          break;
914
3.06k
        case TabStopType::RIGHT:
915
3.06k
          tabProps.insert("style:type", "right");
916
3.06k
          break;
917
3.15k
        case TabStopType::CENTER:
918
3.15k
          tabProps.insert("style:type", "center");
919
3.15k
          break;
920
3.37k
        case TabStopType::ALIGN:
921
3.37k
          tabProps.insert("style:type", "char");
922
3.37k
          tabProps.insert("style:char", tab.alignChar);
923
3.37k
          break;
924
168k
        }
925
168k
        tabs.append(tabProps);
926
168k
      }
927
11.0k
      paragraphPropList.insert("librevenge:tab-stops", tabs);
928
11.0k
    }
929
930
27.0k
    if (paragraph.format->ruleAbove && paragraphInd > 0)
931
3.68k
    {
932
3.68k
      writeBorder(paragraphPropList, "fo:border-top", paragraph.format->ruleAbove);
933
3.68k
    }
934
27.0k
    if (paragraph.format->ruleBelow && paragraphInd < text->paragraphs.size() - 1)
935
5.86k
    {
936
5.86k
      writeBorder(paragraphPropList, "fo:border-bottom", paragraph.format->ruleBelow);
937
5.86k
    }
938
939
27.0k
    m_painter->openParagraph(paragraphPropList);
940
941
27.0k
    for (auto &charFormat : text->charFormats)
942
53.8k
    {
943
53.8k
      if (spanTextStart > paragraph.endIndex() || spanTextStart >= textEnd || charFormat.startIndex > paragraph.endIndex() || charFormat.startIndex >= textEnd)
944
8.50k
      {
945
8.50k
        break;
946
8.50k
      }
947
948
45.3k
      if (spanTextStart > charFormat.endIndex())
949
8.19k
      {
950
8.19k
        continue;
951
8.19k
      }
952
953
37.1k
      if (spanTextStart >= text->text.length())
954
3.09k
      {
955
3.09k
        QXP_DEBUG_MSG(("Span start %lu out of range\n", spanTextStart));
956
3.09k
        break;
957
3.09k
      }
958
959
34.0k
      const auto spanTextEnd = static_cast<unsigned long>(
960
34.0k
                                 std::min<uint64_t>({ charFormat.afterEndIndex(), paragraph.afterEndIndex(), text->text.length(), textEnd })
961
34.0k
                               );
962
963
34.0k
      if (charFormat.format->isControlChars)
964
9.11k
      {
965
9.11k
        spanTextStart = spanTextEnd;
966
9.11k
        continue;
967
9.11k
      }
968
969
24.9k
      librevenge::RVNGPropertyList spanPropList;
970
971
24.9k
      const double fontSize = std::max(charFormat.format->fontSize, 1.);
972
973
24.9k
      spanPropList.insert("style:font-name", charFormat.format->fontName);
974
24.9k
      spanPropList.insert("fo:font-size", fontSize, librevenge::RVNG_POINT);
975
24.9k
      spanPropList.insert("fo:font-weight", charFormat.format->bold ? "bold" : "normal");
976
24.9k
      spanPropList.insert("fo:font-style", charFormat.format->italic ? "italic" : "normal");
977
24.9k
      if (charFormat.format->underline || charFormat.format->wordUnderline)
978
9.26k
      {
979
9.26k
        spanPropList.insert("style:text-underline-color", "font-color");
980
9.26k
        spanPropList.insert("style:text-underline-type", "single");
981
9.26k
        spanPropList.insert("style:text-underline-style", "solid");
982
9.26k
        spanPropList.insert("style:text-underline-mode", charFormat.format->wordUnderline ? "skip-white-space" : "continuous");
983
9.26k
      }
984
24.9k
      if (charFormat.format->strike)
985
8.03k
      {
986
8.03k
        spanPropList.insert("style:text-line-through-color", "font-color");
987
8.03k
        spanPropList.insert("style:text-line-through-mode", "continuous");
988
8.03k
        spanPropList.insert("style:text-line-through-type", "single");
989
8.03k
        spanPropList.insert("style:text-line-through-style", "solid");
990
8.03k
        spanPropList.insert("style:text-line-through-width", "1pt");
991
8.03k
      }
992
24.9k
      spanPropList.insert("fo:font-variant", charFormat.format->smallCaps ? "small-caps" : "normal");
993
24.9k
      if (charFormat.format->allCaps)
994
7.85k
        spanPropList.insert("fo:text-transform", "capitalize");
995
24.9k
      spanPropList.insert("style:text-outline", charFormat.format->outline);
996
24.9k
      if (charFormat.format->shadow)
997
8.95k
        spanPropList.insert("fo:text-shadow", "1pt 1pt");
998
24.9k
      spanPropList.insert("fo:color", charFormat.format->color.toString());
999
1000
24.9k
      if (charFormat.format->subscript)
1001
8.75k
      {
1002
8.75k
        writeTextPosition(spanPropList, m_docProps.subscriptOffset + charFormat.format->baselineShift, m_docProps.subscriptVScale);
1003
8.75k
        spanPropList.insert("style:text-scale", m_docProps.subscriptHScale, librevenge::RVNG_PERCENT);
1004
8.75k
      }
1005
16.2k
      else if (charFormat.format->superscript)
1006
286
      {
1007
286
        writeTextPosition(spanPropList, m_docProps.superscriptOffset + charFormat.format->baselineShift, m_docProps.superscriptVScale);
1008
286
        spanPropList.insert("style:text-scale", m_docProps.superscriptHScale, librevenge::RVNG_PERCENT);
1009
286
      }
1010
15.9k
      else if (charFormat.format->superior)
1011
962
      {
1012
        // approximate "superior" positioning (char ascents are aligned with the cap height of the current font)
1013
962
        const double offset = (fontSize * (1.0 - m_docProps.superiorVScale)) / fontSize;
1014
962
        writeTextPosition(spanPropList, offset + charFormat.format->baselineShift, m_docProps.superiorVScale);
1015
962
        spanPropList.insert("style:text-scale", m_docProps.superiorHScale, librevenge::RVNG_PERCENT);
1016
962
      }
1017
14.9k
      else if (charFormat.format->baselineShift != 0.0)
1018
6.84k
      {
1019
6.84k
        writeTextPosition(spanPropList, charFormat.format->baselineShift);
1020
6.84k
      }
1021
1022
24.9k
      if (paragraph.format->hj)
1023
13.8k
      {
1024
13.8k
        spanPropList.insert("fo:hyphenation-remain-char-count", std::max(int(paragraph.format->hj->minBefore), 1));
1025
13.8k
        spanPropList.insert("fo:hyphenation-push-char-count", std::max(int(paragraph.format->hj->minAfter), 1));
1026
13.8k
      }
1027
1028
24.9k
      m_painter->openSpan(spanPropList);
1029
1030
24.9k
      auto sourceStr = text->text.substr(spanTextStart, spanTextEnd - spanTextStart);
1031
24.9k
      RVNGString str;
1032
24.9k
      appendCharacters(str, sourceStr.c_str(), sourceStr.length(), text->encoding);
1033
1034
24.9k
      insertText(m_painter, str);
1035
1036
24.9k
      m_painter->closeSpan();
1037
1038
24.9k
      spanTextStart = spanTextEnd;
1039
24.9k
    }
1040
1041
27.0k
    m_painter->closeParagraph();
1042
1043
27.0k
    paragraphInd++;
1044
27.0k
  }
1045
64.7k
}
1046
1047
void QXPContentCollector::drawGroup(const std::shared_ptr<Group> &group, const QXPContentCollector::CollectedPage &page)
1048
25.9k
{
1049
25.9k
  bool groupOpened = false;
1050
1051
25.9k
  for (const unsigned &ind : group->objectsIndexes)
1052
20.0k
  {
1053
20.0k
    const auto it = page.objects.find(ind);
1054
20.0k
    if (it == page.objects.end())
1055
18.0k
    {
1056
18.0k
      QXP_DEBUG_MSG(("Group element %u not found\n", ind));
1057
18.0k
      continue;
1058
18.0k
    }
1059
2.00k
    const auto &obj = it->second;
1060
1061
2.00k
    if (!groupOpened)
1062
537
    {
1063
537
      RVNGPropertyList propList;
1064
537
      writeZIndex(propList, obj->zIndex() - 1);
1065
537
      m_painter->openGroup(propList);
1066
1067
537
      groupOpened = true;
1068
537
    }
1069
1070
2.00k
    obj->draw(page);
1071
2.00k
  }
1072
1073
25.9k
  if (groupOpened)
1074
537
  {
1075
537
    m_painter->closeGroup();
1076
537
  }
1077
25.9k
}
1078
1079
void QXPContentCollector::writeFill(librevenge::RVNGPropertyList &propList, const boost::optional<Fill> &fill)
1080
84.2k
{
1081
84.2k
  propList.insert("draw:fill", "none");
1082
1083
84.2k
  if (fill)
1084
72.1k
  {
1085
72.1k
    FillWriter fillWriter(propList);
1086
72.1k
    boost::apply_visitor(fillWriter, fill.get());
1087
72.1k
  }
1088
84.2k
}
1089
1090
void QXPContentCollector::writeFrame(librevenge::RVNGPropertyList &propList, const Frame &frame, const bool runaround, const bool allowHairline)
1091
152k
{
1092
152k
  propList.insert("draw:stroke", "none");
1093
1094
152k
  if (frame.color && (allowHairline || !QXP_ALMOST_ZERO(frame.width)))
1095
96.4k
  {
1096
96.4k
    propList.insert("draw:stroke", "solid");
1097
96.4k
    propList.insert("svg:stroke-color", frame.color->toString());
1098
96.4k
    propList.insert("svg:stroke-width", frame.width, RVNG_POINT);
1099
1100
96.4k
    if (frame.lineStyle)
1101
24.3k
    {
1102
24.3k
      if (frame.lineStyle->segmentLengths.size() > 1 && !frame.lineStyle->isStripe)
1103
4.81k
      {
1104
4.81k
        const double dots1 = frame.lineStyle->segmentLengths[0];
1105
4.81k
        const double dist = frame.lineStyle->segmentLengths[1];
1106
4.81k
        const double dots2 = frame.lineStyle->segmentLengths[frame.lineStyle->segmentLengths.size() >= 3 ? 2 : 0];
1107
4.81k
        const double scale = frame.lineStyle->isProportional ? frame.lineStyle->patternLength : 1.0;
1108
4.81k
        const auto unit = frame.lineStyle->isProportional ? RVNG_PERCENT : RVNG_POINT;
1109
1110
4.81k
        propList.insert("draw:stroke", "dash");
1111
4.81k
        propList.insert("draw:dots1", 1);
1112
4.81k
        propList.insert("draw:dots1-length", dots1 * scale, unit);
1113
4.81k
        propList.insert("draw:dots2", 1);
1114
4.81k
        propList.insert("draw:dots2-length", dots2 * scale, unit);
1115
4.81k
        propList.insert("draw:distance", dist * scale, unit);
1116
4.81k
      }
1117
1118
24.3k
      switch (frame.lineStyle->endcapType)
1119
24.3k
      {
1120
0
      default:
1121
24.2k
      case LineCapType::BUTT:
1122
24.2k
        propList.insert("svg:stroke-linecap", "butt");
1123
24.2k
        break;
1124
24
      case LineCapType::ROUND:
1125
24
        propList.insert("svg:stroke-linecap", "round");
1126
24
        break;
1127
2
      case LineCapType::RECT:
1128
2
        propList.insert("svg:stroke-linecap", "square");
1129
2
        break;
1130
24.3k
      }
1131
1132
24.3k
      switch (frame.lineStyle->joinType)
1133
24.3k
      {
1134
0
      default:
1135
2
      case LineJoinType::BEVEL:
1136
2
        propList.insert("svg:stroke-linejoin", "bevel");
1137
2
        break;
1138
24.2k
      case LineJoinType::MITER:
1139
24.2k
        propList.insert("svg:stroke-linejoin", "miter");
1140
24.2k
        break;
1141
29
      case LineJoinType::ROUND:
1142
29
        propList.insert("svg:stroke-linejoin", "round");
1143
29
        break;
1144
24.3k
      }
1145
24.3k
    }
1146
1147
96.4k
    if (frame.startArrow)
1148
4.39k
    {
1149
4.39k
      writeArrow(propList, "start", *frame.startArrow, frame.width);
1150
4.39k
    }
1151
96.4k
    if (frame.endArrow)
1152
4.69k
    {
1153
4.69k
      writeArrow(propList, "end", *frame.endArrow, frame.width);
1154
4.69k
    }
1155
96.4k
  }
1156
1157
152k
  if (runaround)
1158
98.8k
  {
1159
98.8k
    propList.insert("style:wrap", "biggest");
1160
98.8k
  }
1161
152k
}
1162
1163
double QXPContentCollector::CollectedPage::getX(const double x) const
1164
877k
{
1165
877k
  return x - settings.offset.left;
1166
877k
}
1167
1168
double QXPContentCollector::CollectedPage::getX(const std::shared_ptr<Object> &obj) const
1169
0
{
1170
0
  return getX(obj->boundingBox.left);
1171
0
}
1172
1173
double QXPContentCollector::CollectedPage::getY(const double y) const
1174
877k
{
1175
877k
  return y - settings.offset.top;
1176
877k
}
1177
1178
double QXPContentCollector::CollectedPage::getY(const std::shared_ptr<Object> &obj) const
1179
0
{
1180
0
  return getY(obj->boundingBox.top);
1181
0
}
1182
1183
Point QXPContentCollector::CollectedPage::getPoint(const Point &p) const
1184
796k
{
1185
796k
  return Point(getX(p.x), getY(p.y));
1186
796k
}
1187
1188
1189
}
1190
1191
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */