Coverage Report

Created: 2026-09-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libetonyek/src/lib/PAGCollector.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 libetonyek 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 "PAGCollector.h"
11
12
#include <cassert>
13
#include <memory>
14
15
#include "IWORKDocumentInterface.h"
16
#include "IWORKOutputElements.h"
17
#include "IWORKPath.h"
18
#include "IWORKProperties.h"
19
#include "IWORKTable.h"
20
#include "IWORKText.h"
21
#include "PAGProperties.h"
22
#include "libetonyek_utils.h"
23
24
namespace libetonyek
25
{
26
27
using librevenge::RVNGPropertyList;
28
29
using std::string;
30
31
namespace
32
{
33
34
typedef void (IWORKDocumentInterface::*OpenFunction)(const RVNGPropertyList &);
35
typedef void (IWORKDocumentInterface::*CloseFunction)();
36
typedef const std::string &(*PickFunction)(const IWORKPageMaster &);
37
38
const std::string &pickHeader(const IWORKPageMaster &pageMaster)
39
0
{
40
0
  return pageMaster.m_header;
41
0
}
42
43
const std::string &pickFooter(const IWORKPageMaster &pageMaster)
44
0
{
45
0
  return pageMaster.m_footer;
46
0
}
47
48
void writeHeaderFooter(
49
  IWORKDocumentInterface *const document, const IWORKHeaderFooterMap_t &hfMap,
50
  const string &name, const string &occurrence,
51
  const OpenFunction open, const CloseFunction close)
52
0
{
53
0
  assert(document);
54
0
  if (name.empty())
55
0
    return;
56
57
0
  const IWORKHeaderFooterMap_t::const_iterator it = hfMap.find(name);
58
0
  if ((it != hfMap.end()) && !it->second.empty())
59
0
  {
60
0
    RVNGPropertyList props;
61
0
    props.insert("librevenge:occurrence", occurrence.c_str());
62
0
    (document->*open)(props);
63
0
    it->second.write(document);
64
0
    (document->*close)();
65
0
  }
66
0
}
67
68
void writeHeadersFooters(
69
  IWORKDocumentInterface *const document, const IWORKStylePtr_t &style, const IWORKHeaderFooterMap_t &hfMap,
70
  const PickFunction pick, const OpenFunction open, const CloseFunction close)
71
0
{
72
0
  assert(bool(style));
73
74
0
  using namespace property;
75
0
  const string odd((style->has<OddPageMaster>()) ? pick(style->get<OddPageMaster>()) : "");
76
0
  const string even((style->has<EvenPageMaster>()) ? pick(style->get<EvenPageMaster>()) : "");
77
0
  const string first((style->has<FirstPageMaster>()) ? pick(style->get<FirstPageMaster>()) : "");
78
79
0
  if (odd == even)
80
0
  {
81
0
    writeHeaderFooter(document, hfMap, odd, "both", open, close);
82
0
  }
83
0
  else
84
0
  {
85
0
    writeHeaderFooter(document, hfMap, odd, "odd", open, close);
86
0
    writeHeaderFooter(document, hfMap, even, "even", open, close);
87
0
  }
88
0
  writeHeaderFooter(document, hfMap, first, "first", open, close);
89
0
}
90
91
}
92
93
PAGCollector::PAGCollector(IWORKDocumentInterface *const document)
94
0
  : IWORKCollector(document)
95
0
  , m_pageDimensions()
96
0
  , m_currentSectionStyle()
97
0
  , m_firstPageSpan(true)
98
0
  , m_pubInfo()
99
0
  , m_pageGroups()
100
0
  , m_page(0)
101
0
  , m_attachmentPosition()
102
0
  , m_annotations()
103
0
{
104
0
}
105
106
void PAGCollector::collectAnnotation(const std::string &name)
107
0
{
108
0
  IWORKOutputElements &elements = m_annotations[name];
109
0
  if (!elements.empty())
110
0
  {
111
0
    ETONYEK_DEBUG_MSG(("PAGCollector::collectAnnotation '%s' already exists, overwriting\n", name.c_str()));
112
0
    elements.clear();
113
0
  }
114
0
  if (bool(m_currentText))
115
0
  {
116
0
    librevenge::RVNGPropertyList propList;
117
0
    elements.addOpenComment(propList);
118
0
    m_currentText->draw(elements);
119
0
    elements.addCloseComment();
120
0
    m_currentText.reset();
121
0
  }
122
0
  else
123
0
  {
124
0
    ETONYEK_DEBUG_MSG(("PAGCollector::sendAnnotation: called without text\n"));
125
0
  }
126
0
}
127
128
void PAGCollector::collectPublicationInfo(const PAGPublicationInfo &pubInfo)
129
0
{
130
0
  m_pubInfo = pubInfo;
131
0
}
132
133
void PAGCollector::collectTextBody()
134
0
{
135
  // It seems that this is never used, as Pages always inserts all text
136
  // into a section. But better safe than sorry.
137
0
  flushPageSpan(false);
138
0
}
139
140
void PAGCollector::collectAttachmentPosition(const IWORKPosition &position)
141
0
{
142
0
  m_attachmentPosition = position;
143
0
}
144
145
void PAGCollector::startDocument()
146
0
{
147
0
  IWORKCollector::startDocument(librevenge::RVNGPropertyList());
148
0
}
149
150
void PAGCollector::setPageDimensions(const IWORKPrintInfo &dimensions)
151
0
{
152
0
  m_pageDimensions=dimensions;
153
0
}
154
155
void PAGCollector::openSection(const std::string &style)
156
0
{
157
0
  if (!m_stylesheetStack.empty())
158
0
  {
159
0
    const IWORKStyleMap_t::iterator it = m_stylesheetStack.top()->m_styles.find(style);
160
0
    if (it != m_stylesheetStack.top()->m_styles.end())
161
0
    {
162
0
      m_currentSectionStyle = it->second;
163
0
    }
164
0
    else
165
0
    {
166
0
      ETONYEK_DEBUG_MSG(("PAGCollector::openSection: style '%s' not found\n", style.c_str()));
167
0
    }
168
0
  }
169
0
  else
170
0
  {
171
0
    ETONYEK_DEBUG_MSG(("PAGCollector::openSection: no stylesheet is available\n"));
172
0
  }
173
0
}
174
175
void PAGCollector::openSection(const IWORKStylePtr_t &style)
176
0
{
177
0
  m_currentSectionStyle=style;
178
0
}
179
180
void PAGCollector::closeSection()
181
0
{
182
0
  flushPageSpan();
183
0
}
184
185
void PAGCollector::sendAnnotation(const std::string &name)
186
0
{
187
0
  if (m_annotations.find(name)==m_annotations.end())
188
0
  {
189
0
    ETONYEK_DEBUG_MSG(("PAGCollector::sendAnnotation can not find annotation'%s'\n", name.c_str()));
190
0
    m_currentText.reset();
191
0
    return;
192
0
  }
193
0
  if (bool(m_currentText))
194
0
  {
195
0
    m_currentText->insertInlineContent(m_annotations.find(name)->second);
196
0
    m_currentText.reset();
197
0
  }
198
0
  else
199
0
  {
200
0
    ETONYEK_DEBUG_MSG(("PAGCollector::sendAnnotation: called without text\n"));
201
0
  }
202
0
}
203
204
void PAGCollector::openPageGroup(const boost::optional<int> &page)
205
0
{
206
0
  getOutputManager().push();
207
0
  if (page)
208
0
    m_page = get(page);
209
0
  else
210
0
    ++m_page;
211
0
}
212
213
void PAGCollector::closePageGroup()
214
0
{
215
0
  typedef std::pair<PageGroupsMap_t::const_iterator, bool> Result_t;
216
0
  const IWORKOutputID_t id = getOutputManager().save();
217
0
  const Result_t result = m_pageGroups.insert(PageGroupsMap_t::value_type(m_page, id));
218
0
  if (!result.second)
219
0
  {
220
0
    ETONYEK_DEBUG_MSG(("PAGCollector::closePageGroup: Page group for page %u already exists\n", m_page));
221
0
  }
222
0
  getOutputManager().pop();
223
0
}
224
225
void PAGCollector::drawTable()
226
0
{
227
0
  assert(bool(m_currentTable));
228
0
  assert(!m_levelStack.empty());
229
  // FIXME: in .odt files, table can not appear in group
230
0
  int prevGroupLevel=getOpenGroupLevel();
231
0
  for (int level=0; level<prevGroupLevel; ++level)
232
0
    closeGroup();
233
234
0
  RVNGPropertyList frameProps;
235
0
  librevenge::RVNGPropertyList props;
236
237
  // TODO: I am not sure this is the default for Pages...
238
0
  props.insert("table:align", "center");
239
240
0
  const IWORKGeometryPtr_t geometry(m_levelStack.top().m_geometry);
241
0
  if (m_inAttachments)
242
0
  {
243
0
    if (geometry)
244
0
    {
245
0
      const glm::dvec3 dim(m_levelStack.top().m_trafo * glm::dvec3(geometry->m_naturalSize.m_width, 0, 0));
246
0
      props.insert("style:width", pt2in(dim[0]));
247
0
    }
248
0
  }
249
0
  else
250
0
  {
251
0
    fillShapeProperties(frameProps);
252
253
0
    const glm::dmat3 trafo = m_levelStack.top().m_trafo;
254
0
    const glm::dvec3 pos(trafo * glm::dvec3(0, 0, 1));
255
0
    frameProps.insert("svg:x", pos[0], librevenge::RVNG_POINT);
256
0
    frameProps.insert("svg:y", pos[1], librevenge::RVNG_POINT);
257
0
    if (geometry)
258
0
    {
259
0
      const glm::dvec3 dim(trafo * glm::dvec3(geometry->m_naturalSize.m_width, geometry->m_naturalSize.m_height, 0));
260
0
      frameProps.insert("svg:width", pt2in(dim[0]), librevenge::RVNG_INCH);
261
0
      frameProps.insert("svg:height", pt2in(dim[1]), librevenge::RVNG_INCH);
262
0
    }
263
0
    if (bool(m_currentTable->getStyle()))
264
0
      fillWrapProps(m_currentTable->getStyle(), frameProps, m_currentTable->getOrder());
265
0
  }
266
267
0
  if (m_inAttachments && !prevGroupLevel)
268
0
    m_currentTable->draw(props, m_outputManager.getCurrent(), true);
269
0
  else
270
0
  {
271
0
    frameProps.insert("draw:fill", "none");
272
0
    frameProps.insert("draw:stroke", "none");
273
0
    getOutputManager().getCurrent().addOpenFrame(frameProps);
274
0
    getOutputManager().getCurrent().addStartTextObject(RVNGPropertyList());
275
0
    m_currentTable->draw(props, m_outputManager.getCurrent(), true);
276
0
    getOutputManager().getCurrent().addEndTextObject();
277
0
    getOutputManager().getCurrent().addCloseFrame();
278
0
  }
279
0
  for (int level=0; level<prevGroupLevel; ++level)
280
0
    openGroup();
281
0
}
282
283
void PAGCollector::drawMedia(const double x, const double y, const librevenge::RVNGPropertyList &data)
284
0
{
285
0
  if (!data["office:binary-data"] || !data["librevenge:mime-type"])
286
0
  {
287
0
    ETONYEK_DEBUG_MSG(("PAGCollector::drawMedia: oops can not find the picture\n"));
288
0
    return;
289
0
  }
290
0
  RVNGPropertyList frameProps(data);
291
0
  fillShapeProperties(frameProps);
292
0
  if (m_inAttachments && m_attachmentPosition)
293
0
  {
294
0
    frameProps.insert("svg:x", pt2in(get(m_attachmentPosition).m_x));
295
0
    frameProps.insert("svg:y", pt2in(get(m_attachmentPosition).m_y));
296
0
  }
297
0
  else
298
0
  {
299
0
    frameProps.insert("svg:x", pt2in(x));
300
0
    frameProps.insert("svg:y", pt2in(y));
301
0
  }
302
0
  frameProps.remove("librevenge:mime-type");
303
0
  frameProps.remove("office:binary-data");
304
305
0
  RVNGPropertyList binaryObjectProps;
306
0
  binaryObjectProps.insert("librevenge:mime-type", data["librevenge:mime-type"]->clone());
307
0
  binaryObjectProps.insert("office:binary-data", data["office:binary-data"]->clone());
308
309
0
  getOutputManager().getCurrent().addOpenFrame(frameProps);
310
0
  getOutputManager().getCurrent().addInsertBinaryObject(binaryObjectProps);
311
0
  getOutputManager().getCurrent().addCloseFrame();
312
0
}
313
314
void PAGCollector::fillShapeProperties(librevenge::RVNGPropertyList &props)
315
0
{
316
0
  if (m_inAttachments)
317
0
  {
318
0
    props.insert("text:anchor-type", "as-char");
319
0
    props.insert("style:vertical-pos", "bottom");
320
0
    props.insert("style:vertical-rel", "text");
321
0
  }
322
0
  else
323
0
  {
324
0
    props.insert("text:anchor-type", "page");
325
0
    props.insert("text:anchor-page-number", m_page);
326
0
    props.insert("style:vertical-pos", "from-top");
327
0
    props.insert("style:vertical-rel", "page");
328
0
  }
329
0
}
330
331
void PAGCollector::drawTextBox(const IWORKTextPtr_t &text, const glm::dmat3 &trafo, const IWORKGeometryPtr_t &boundingBox, const librevenge::RVNGPropertyList &style)
332
0
{
333
0
  if (!bool(text) || text->empty())
334
0
    return;
335
336
0
  librevenge::RVNGPropertyList props(style);
337
338
0
  glm::dvec3 vec = trafo * glm::dvec3(0, 0, 1);
339
340
0
  props.insert("svg:x", pt2in(vec[0]));
341
0
  props.insert("svg:y", pt2in(vec[1]));
342
343
0
  if (bool(boundingBox))
344
0
  {
345
0
    double w = boundingBox->m_naturalSize.m_width;
346
0
    double h = boundingBox->m_naturalSize.m_height;
347
0
    vec = trafo * glm::dvec3(w, h, 0);
348
349
0
    if (vec[0]>0)
350
0
      props.insert("svg:width", pt2in(vec[0]));
351
0
    if (vec[1]>0)
352
0
      props.insert("svg:height", pt2in(vec[1]));
353
0
  }
354
355
0
  fillShapeProperties(props);
356
357
0
  IWORKOutputElements &elements = m_outputManager.getCurrent();
358
0
  elements.addOpenFrame(props);
359
0
  elements.addStartTextObject(librevenge::RVNGPropertyList());
360
0
  text->draw(elements);
361
0
  elements.addEndTextObject();
362
0
  elements.addCloseFrame();
363
0
}
364
365
void PAGCollector::flushPageSpan(const bool writeEmpty)
366
0
{
367
0
  if (m_firstPageSpan)
368
0
  {
369
0
    RVNGPropertyList metadata;
370
0
    fillMetadata(metadata);
371
0
    m_document->setDocumentMetaData(metadata);
372
0
    writePageGroupsObjects();
373
0
    m_firstPageSpan = false;
374
0
  }
375
376
0
  librevenge::RVNGPropertyList props;
377
378
0
  if (m_pageDimensions)
379
0
  {
380
0
    IWORKPrintInfo const &page=get(m_pageDimensions);
381
0
    if (page.m_width)
382
0
      props.insert("fo:page-width", get(page.m_width), librevenge::RVNG_POINT);
383
0
    if (page.m_height)
384
0
      props.insert("fo:page-height", get(page.m_height), librevenge::RVNG_POINT);
385
0
    if (page.m_orientation)
386
0
    {
387
0
      switch (get(page.m_orientation))
388
0
      {
389
0
      case 0:
390
0
        props.insert("style:print-orientation", "portrait");
391
0
        break;
392
0
      case 1:
393
0
        props.insert("style:print-orientation", "landscape");
394
0
        break;
395
0
      default:
396
0
        ETONYEK_DEBUG_MSG(("PAGCollector::flushPageSpan: unexpected orientation\n"));
397
0
        break;
398
0
      }
399
0
    }
400
0
    if (page.m_marginBottom)
401
0
      props.insert("fo:margin-bottom", get(page.m_marginBottom), librevenge::RVNG_POINT);
402
0
    if (page.m_marginLeft)
403
0
      props.insert("fo:margin-left", get(page.m_marginLeft), librevenge::RVNG_POINT);
404
0
    if (page.m_marginRight)
405
0
      props.insert("fo:margin-right", get(page.m_marginRight), librevenge::RVNG_POINT);
406
0
    if (page.m_marginTop)
407
0
      props.insert("fo:margin-top", get(page.m_marginTop), librevenge::RVNG_POINT);
408
    //TODO set also the header/footer height here
409
0
  }
410
0
  if (m_currentSectionStyle && m_currentSectionStyle->has<property::Fill>())
411
0
  {
412
0
    librevenge::RVNGPropertyList fillProps;
413
0
    writeFill(m_currentSectionStyle->get<property::Fill>(), fillProps);
414
0
    if (fillProps["draw:fill-color"] && fillProps["draw:fill"] && fillProps["draw:fill"]->getStr()=="solid")
415
0
      props.insert("fo:background-color", fillProps["draw:fill-color"]->clone());
416
0
    else
417
0
    {
418
0
      ETONYEK_DEBUG_MSG(("PAGCollector::flushPageSpan: unimplemented background\n"));
419
0
    }
420
0
  }
421
422
0
  IWORKOutputElements text;
423
0
  if (bool(m_currentText))
424
0
  {
425
0
    m_currentText->draw(text);
426
0
    m_currentText.reset();
427
0
  }
428
429
0
  if (!text.empty() || writeEmpty)
430
0
  {
431
0
    m_document->openPageSpan(props);
432
0
    if (m_currentSectionStyle)
433
0
    {
434
0
      writeHeadersFooters(m_document, m_currentSectionStyle, m_headers, pickHeader,
435
0
                          &IWORKDocumentInterface::openHeader, &IWORKDocumentInterface::closeHeader);
436
0
      writeHeadersFooters(m_document, m_currentSectionStyle, m_footers, pickFooter,
437
0
                          &IWORKDocumentInterface::openFooter, &IWORKDocumentInterface::closeFooter);
438
0
    }
439
0
    if (text.empty())
440
0
    {
441
      // let force an empty paragraph to be inserted
442
0
      text.addOpenParagraph(librevenge::RVNGPropertyList());
443
0
      text.addCloseParagraph();
444
0
    }
445
0
    text.write(m_document);
446
0
    m_document->closePageSpan();
447
0
  }
448
449
0
  m_currentSectionStyle.reset();
450
0
}
451
452
void PAGCollector::writePageGroupsObjects()
453
0
{
454
0
  for (PageGroupsMap_t::const_iterator it = m_pageGroups.begin(); it != m_pageGroups.end(); ++it)
455
0
    getOutputManager().get(it->second).write(m_document);
456
0
}
457
458
PAGFootnoteKind PAGCollector::getFootnoteKind() const
459
0
{
460
0
  return m_pubInfo.m_footnoteKind;
461
0
}
462
463
}
464
465
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */