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