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