Coverage Report

Created: 2026-04-29 07:28

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