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 "IWORKCollector.h"
11 
12 #include <algorithm>
13 #include <cassert>
14 #include <cmath>
15 #include <cstring>
16 #include <functional>
17 #include <memory>
18 
19 #include "IWORKDocumentInterface.h"
20 #include "IWORKOutputElements.h"
21 #include "IWORKPath.h"
22 #include "IWORKProperties.h"
23 #include "IWORKRecorder.h"
24 #include "IWORKShape.h"
25 #include "IWORKTable.h"
26 #include "IWORKText.h"
27 #include "libetonyek_utils.h"
28 
29 namespace libetonyek
30 {
31 
32 
33 using librevenge::RVNGPropertyList;
34 using librevenge::RVNG_PERCENT;
35 using librevenge::RVNG_POINT;
36 
37 using namespace std::placeholders;
38 
39 using std::make_shared;
40 using std::shared_ptr;
41 
42 namespace
43 {
makePoint(const double x,const double y)44 librevenge::RVNGPropertyList makePoint(const double x, const double y)
45 {
46   librevenge::RVNGPropertyList props;
47 
48   props.insert("svg:x", pt2in(x));
49   props.insert("svg:y", pt2in(y));
50 
51   return props;
52 }
53 
54 struct FillWriter : public boost::static_visitor<void>
55 {
FillWriterlibetonyek::__anone8f7bf810111::FillWriter56   explicit FillWriter(RVNGPropertyList &props)
57     : m_props(props), m_opacity(1)
58   {
59   }
60 
getOpacitylibetonyek::__anone8f7bf810111::FillWriter61   double getOpacity() const
62   {
63     return m_opacity;
64   }
operator ()libetonyek::__anone8f7bf810111::FillWriter65   void operator()(const IWORKColor &color) const
66   {
67     m_props.insert("draw:fill", "solid");
68     m_props.insert("draw:fill-color", makeColor(color));
69     m_opacity=color.m_alpha;
70   }
71 
operator ()libetonyek::__anone8f7bf810111::FillWriter72   void operator()(const IWORKGradient &gradient) const
73   {
74     if (gradient.m_stops.empty())
75       return;
76     m_props.insert("draw:fill", "gradient");
77     switch (gradient.m_type)
78     {
79     case IWORK_GRADIENT_TYPE_LINEAR :
80       m_props.insert("draw:style", "linear");
81       break;
82     case IWORK_GRADIENT_TYPE_RADIAL :
83       m_props.insert("draw:style", "radial");
84       // TODO: store sf:start to retrieve the center position
85       m_props.insert("draw:cx", 0.5, RVNG_PERCENT);
86       m_props.insert("draw:cy", 0.5, RVNG_PERCENT);
87       break;
88     default:
89       break;
90     }
91     // TODO: use svg:linearGradient/svg:radialGradient?
92     if (gradient.m_stops.front().m_fraction<=0 && gradient.m_stops.back().m_fraction>=1)
93     {
94       IWORKGradientStop const &start= gradient.m_type==IWORK_GRADIENT_TYPE_LINEAR ? gradient.m_stops.front() : gradient.m_stops.back();
95       IWORKGradientStop const &end= gradient.m_type==IWORK_GRADIENT_TYPE_LINEAR ? gradient.m_stops.back() : gradient.m_stops.front();
96       m_props.insert("draw:start-color", makeColor(start.m_color));
97       m_props.insert("draw:start-intensity", start.m_color.m_alpha, RVNG_PERCENT);
98       m_props.insert("draw:end-color", makeColor(end.m_color));
99       m_props.insert("draw:end-intensity", end.m_color.m_alpha, RVNG_PERCENT);
100     }
101     else
102     {
103       librevenge::RVNGPropertyListVector gradientVector;
104       int firstVal=gradient.m_type==IWORK_GRADIENT_TYPE_LINEAR ? 1 : 0;
105       for (int s=0; s < 2; ++s)
106       {
107         IWORKGradientStop const &stop= s==firstVal ? gradient.m_stops.front() : gradient.m_stops.back();
108         librevenge::RVNGPropertyList grad;
109         grad.insert("svg:offset", firstVal==0 ? stop.m_fraction : 1.-stop.m_fraction, librevenge::RVNG_PERCENT);
110         grad.insert("svg:stop-color", makeColor(stop.m_color));
111         grad.insert("svg:stop-opacity", stop.m_color.m_alpha, librevenge::RVNG_PERCENT);
112         gradientVector.append(grad);
113       }
114       if (gradient.m_type==IWORK_GRADIENT_TYPE_RADIAL)
115         m_props.insert("svg:radialGradient", gradientVector);
116       else
117         m_props.insert("svg:linearGradient", gradientVector);
118     }
119     // the axis of the gradient in Keynote is clockwise to the horizontal axis
120     m_props.insert("draw:angle", rad2deg(/*etonyek_two_pi - */gradient.m_angle + etonyek_half_pi));
121   }
122 
operator ()libetonyek::__anone8f7bf810111::FillWriter123   void operator()(const IWORKMediaContent &bitmap) const
124   {
125     if (bitmap.m_data && bitmap.m_data->m_stream)
126     {
127       const unsigned long length = getLength(bitmap.m_data->m_stream);
128       unsigned long readBytes = 0;
129       bitmap.m_data->m_stream->seek(0, librevenge::RVNG_SEEK_SET);
130       const unsigned char *const bytes = bitmap.m_data->m_stream->read(length, readBytes);
131       if (readBytes == length)
132       {
133         m_props.insert("draw:fill", "bitmap");
134         m_props.insert("draw:fill-image", librevenge::RVNGBinaryData(bytes, length));
135         m_props.insert("librevenge:mime-type", "jpg"); // TODO: fix
136         switch (bitmap.m_type)
137         {
138         case IWORK_IMAGE_TYPE_ORIGINAL_SIZE :
139           m_props.insert("style:repeat", "no-repeat");
140           break;
141         case IWORK_IMAGE_TYPE_STRETCH :
142         case IWORK_IMAGE_TYPE_SCALE_TO_FILL :
143         case IWORK_IMAGE_TYPE_SCALE_TO_FIT :
144           m_props.insert("style:repeat", "stretch");
145           break;
146         case IWORK_IMAGE_TYPE_TILE :
147           m_props.insert("style:repeat", "repeat");
148           break;
149         default:
150           break;
151         }
152         if (bitmap.m_size)
153         {
154           m_props.insert("draw:fill-image-width", get(bitmap.m_size).m_width, RVNG_POINT);
155           m_props.insert("draw:fill-image-height", get(bitmap.m_size).m_height, RVNG_POINT);
156         }
157         return;
158       }
159     }
160     // can happen if data is a path to an Iwork's file
161     if (bitmap.m_fillColor)
162       (*this)(get(bitmap.m_fillColor));
163     else
164     {
165       static bool first=true;
166       if (first)
167       {
168         ETONYEK_DEBUG_MSG(("FillWriter::operator()(IWORKMediaContent)[IWORKCollector.cpp]: can not retrieve some pictures\n"));
169         first=false;
170       }
171       m_props.insert("draw:fill", "none");
172     }
173   }
174 
175 private:
176   RVNGPropertyList &m_props;
177   //! the opacity
178   mutable double m_opacity;
179 };
180 
181 }
182 
Level()183 IWORKCollector::Level::Level()
184   : m_geometry()
185   , m_graphicStyle()
186   , m_trafo(1)
187   , m_previousTrafo(1)
188 {
189 }
190 
IWORKCollector(IWORKDocumentInterface * const document)191 IWORKCollector::IWORKCollector(IWORKDocumentInterface *const document)
192   : m_document(document)
193   , m_recorder()
194   , m_levelStack()
195   , m_styleStack()
196   , m_stylesheetStack()
197   , m_outputManager()
198   , m_newStyles()
199   , m_currentTable()
200   , m_currentText()
201   , m_headers()
202   , m_footers()
203   , m_pathStack()
204   , m_currentPath()
205   , m_attachmentStack()
206   , m_inAttachment(false)
207   , m_inAttachments(false)
208   , m_currentData()
209   , m_currentUnfiltered()
210   , m_currentFiltered()
211   , m_currentLeveled()
212   , m_currentContent()
213   , m_metadata()
214   , m_accumulateTransform(true)
215   , m_groupLevel(0)
216   , m_groupOpenLevel(0)
217 {
218 }
219 
~IWORKCollector()220 IWORKCollector::~IWORKCollector()
221 {
222   assert(m_levelStack.empty());
223   assert(m_stylesheetStack.empty());
224   assert(0 == m_groupLevel && 0 == m_groupOpenLevel);
225 
226   assert(!m_currentPath);
227   assert(!m_currentText);
228 }
229 
setRecorder(const std::shared_ptr<IWORKRecorder> & recorder)230 void IWORKCollector::setRecorder(const std::shared_ptr<IWORKRecorder> &recorder)
231 {
232   m_recorder = recorder;
233 }
234 
collectStyle(const IWORKStylePtr_t & style)235 void IWORKCollector::collectStyle(const IWORKStylePtr_t &style)
236 {
237   if (bool(m_recorder))
238   {
239     m_recorder->collectStyle(style);
240     return;
241   }
242 
243   if (bool(style))
244     m_newStyles.push_back(style);
245 }
246 
setGraphicStyle(const IWORKStylePtr_t & style)247 void IWORKCollector::setGraphicStyle(const IWORKStylePtr_t &style)
248 {
249   if (bool(m_recorder))
250   {
251     m_recorder->setGraphicStyle(style);
252     return;
253   }
254 
255   if (!m_levelStack.empty())
256   {
257     m_levelStack.top().m_graphicStyle = style;
258     m_styleStack.set(style);
259   }
260 }
261 
setAccumulateTransformTo(bool accumulate)262 void IWORKCollector::setAccumulateTransformTo(bool accumulate)
263 {
264   m_accumulateTransform=accumulate;
265 }
266 
collectGeometry(const IWORKGeometryPtr_t & geometry)267 void IWORKCollector::collectGeometry(const IWORKGeometryPtr_t &geometry)
268 {
269   if (bool(m_recorder))
270   {
271     m_recorder->collectGeometry(geometry);
272     return;
273   }
274 
275   assert(!m_levelStack.empty());
276 
277   m_levelStack.top().m_geometry = geometry;
278   m_levelStack.top().m_previousTrafo = m_levelStack.top().m_trafo;
279   if (m_accumulateTransform)
280     m_levelStack.top().m_trafo *= makeTransformation(*geometry);
281   else
282     m_levelStack.top().m_trafo = makeTransformation(*geometry);
283 }
284 
collectBezier(const IWORKPathPtr_t & path)285 void IWORKCollector::collectBezier(const IWORKPathPtr_t &path)
286 {
287   if (bool(m_recorder))
288     m_recorder->collectPath(path);
289   else
290   {
291     m_currentPath = path;
292     if (m_currentPath) m_currentPath->closePath(true);
293   }
294 }
295 
collectImage(const IWORKMediaContentPtr_t & image,const IWORKGeometryPtr_t & cropGeometry,const boost::optional<int> & order,bool locked)296 void IWORKCollector::collectImage(const IWORKMediaContentPtr_t &image, const IWORKGeometryPtr_t &cropGeometry, const boost::optional<int> &order, bool locked)
297 {
298   if (bool(m_recorder))
299   {
300     m_recorder->collectImage(image, cropGeometry, order, locked);
301     return;
302   }
303 
304   assert(!m_levelStack.empty());
305 
306   const IWORKMediaPtr_t media(new IWORKMedia());
307   media->m_geometry = m_levelStack.top().m_geometry;
308   media->m_cropGeometry = cropGeometry;
309   media->m_order = order;
310   media->m_locked = locked;
311   media->m_style = m_levelStack.top().m_graphicStyle;
312   media->m_content = image;
313   m_levelStack.top().m_geometry.reset();
314   m_levelStack.top().m_graphicStyle.reset();
315 
316   drawMedia(media);
317 }
318 
collectLine(const IWORKLinePtr_t & line)319 void IWORKCollector::collectLine(const IWORKLinePtr_t &line)
320 {
321   if (bool(m_recorder))
322   {
323     m_recorder->collectLine(line);
324     return;
325   }
326 
327   assert(!m_levelStack.empty());
328 
329   line->m_geometry = m_levelStack.top().m_geometry;
330   m_levelStack.top().m_geometry.reset();
331   line->m_style = m_levelStack.top().m_graphicStyle;
332   m_levelStack.top().m_graphicStyle.reset();
333 
334   drawLine(line);
335 }
336 
collectShape(const boost::optional<int> & order,const boost::optional<unsigned> & resizeFlags,bool locked)337 void IWORKCollector::collectShape(const boost::optional<int> &order, const boost::optional<unsigned> &resizeFlags, bool locked)
338 {
339   if (bool(m_recorder))
340   {
341     m_recorder->collectShape(order, resizeFlags, locked);
342     return;
343   }
344 
345   assert(!m_levelStack.empty());
346 
347   const IWORKShapePtr_t shape(new IWORKShape());
348 
349   if (!m_currentPath)
350   {
351     ETONYEK_DEBUG_MSG(("IWORKCollector::collectShape: the path is empty\n"));
352   }
353   shape->m_path = m_currentPath;
354   m_currentPath.reset();
355 
356   shape->m_geometry = m_levelStack.top().m_geometry;
357   m_levelStack.top().m_geometry.reset();
358 
359   if (bool(m_currentText))
360   {
361     shape->m_text = m_currentText;
362     m_currentText.reset();
363   }
364 
365   shape->m_order = order;
366   shape->m_resizeFlags=resizeFlags;
367   shape->m_locked = locked;
368   shape->m_style = m_levelStack.top().m_graphicStyle;
369   m_levelStack.top().m_graphicStyle.reset();
370 
371   drawShape(shape);
372 }
373 
collectBezierPath()374 void IWORKCollector::collectBezierPath()
375 {
376   // nothing needed
377 }
378 
collectPolygonPath(const IWORKSize & size,const unsigned edges)379 void IWORKCollector::collectPolygonPath(const IWORKSize &size, const unsigned edges)
380 {
381   const IWORKPathPtr_t path(makePolygonPath(size, edges));
382   if (bool(m_recorder))
383     m_recorder->collectPath(path);
384   else
385     m_currentPath = path;
386 }
387 
collectRoundedRectanglePath(const IWORKSize & size,const double radius)388 void IWORKCollector::collectRoundedRectanglePath(const IWORKSize &size, const double radius)
389 {
390   const IWORKPathPtr_t path(makeRoundedRectanglePath(size, radius));
391   if (bool(m_recorder))
392     m_recorder->collectPath(path);
393   else
394     m_currentPath = path;
395 }
396 
collectArrowPath(const IWORKSize & size,const double headWidth,const double stemRelYPos,bool const doubleSided)397 void IWORKCollector::collectArrowPath(const IWORKSize &size, const double headWidth, const double stemRelYPos, bool const doubleSided)
398 {
399   IWORKPathPtr_t path;
400   if (doubleSided)
401     path = makeDoubleArrowPath(size, headWidth, stemRelYPos);
402   else
403     path = makeArrowPath(size, headWidth, stemRelYPos);
404   if (bool(m_recorder))
405     m_recorder->collectPath(path);
406   else
407     m_currentPath = path;
408 }
409 
collectStarPath(const IWORKSize & size,const unsigned points,const double innerRadius)410 void IWORKCollector::collectStarPath(const IWORKSize &size, const unsigned points, const double innerRadius)
411 {
412   const IWORKPathPtr_t path(makeStarPath(size, points, innerRadius));
413   if (bool(m_recorder))
414     m_recorder->collectPath(path);
415   else
416     m_currentPath = path;
417 }
418 
collectConnectionPath(const IWORKConnectionPath & cPath)419 void IWORKCollector::collectConnectionPath(const IWORKConnectionPath &cPath)
420 {
421   const IWORKPathPtr_t path=cPath.getPath();
422   if (bool(m_recorder))
423     m_recorder->collectPath(path);
424   else
425     m_currentPath = path;
426 }
427 
collectCalloutPath(const IWORKSize & size,const double radius,const double tailSize,const double tailX,const double tailY,bool quoteBubble)428 void IWORKCollector::collectCalloutPath(const IWORKSize &size, const double radius, const double tailSize, const double tailX, const double tailY, bool quoteBubble)
429 {
430   IWORKPathPtr_t path;
431   if (quoteBubble)
432     path = makeQuoteBubblePath(size, radius, tailSize, tailX, tailY);
433   else
434     path = makeCalloutPath(size, radius, tailSize, tailX, tailY);
435   if (bool(m_recorder))
436     m_recorder->collectPath(path);
437   else
438     m_currentPath = path;
439 }
440 
collectMedia(const IWORKMediaContentPtr_t & content,const IWORKGeometryPtr_t & cropGeometry,const boost::optional<int> & order)441 void IWORKCollector::collectMedia(const IWORKMediaContentPtr_t &content, const IWORKGeometryPtr_t &cropGeometry, const boost::optional<int> &order)
442 {
443   if (bool(m_recorder))
444   {
445     m_recorder->collectMedia(content, cropGeometry, order);
446     return;
447   }
448 
449   assert(!m_levelStack.empty());
450 
451   const IWORKMediaPtr_t media(new IWORKMedia());
452   media->m_geometry = m_levelStack.top().m_geometry;
453   media->m_cropGeometry = cropGeometry;
454   media->m_style = m_levelStack.top().m_graphicStyle;
455   media->m_order = order;
456   media->m_content = content;
457 
458   m_levelStack.top().m_geometry.reset();
459   m_levelStack.top().m_graphicStyle.reset();
460 
461   drawMedia(media);
462 }
463 
collectStylesheet(const IWORKStylesheetPtr_t & stylesheet)464 void IWORKCollector::collectStylesheet(const IWORKStylesheetPtr_t &stylesheet)
465 {
466   if (bool(m_recorder))
467   {
468     m_recorder->collectStylesheet(stylesheet);
469     return;
470   }
471 
472   for_each(m_newStyles.begin(), m_newStyles.end(), std::bind(&IWORKStyle::link, _1, stylesheet));
473   m_newStyles.clear();
474 }
475 
collectMetadata(const IWORKMetadata & metadata)476 void IWORKCollector::collectMetadata(const IWORKMetadata &metadata)
477 {
478   m_metadata = metadata;
479 }
480 
collectHeader(const std::string & name)481 void IWORKCollector::collectHeader(const std::string &name)
482 {
483   collectHeaderFooter(name, m_headers);
484 }
485 
collectFooter(const std::string & name)486 void IWORKCollector::collectFooter(const std::string &name)
487 {
488   collectHeaderFooter(name, m_footers);
489 }
490 
collectTable(const std::shared_ptr<IWORKTable> & table)491 void IWORKCollector::collectTable(const std::shared_ptr<IWORKTable> &table)
492 {
493   if (bool(m_recorder))
494   {
495     m_recorder->collectTable(table);
496     return;
497   }
498 
499   assert(!m_currentTable);
500   m_currentTable = table;
501   drawTable();
502   m_currentTable.reset();
503 }
504 
collectText(const std::shared_ptr<IWORKText> & text)505 void IWORKCollector::collectText(const std::shared_ptr<IWORKText> &text)
506 {
507   if (bool(m_recorder))
508   {
509     m_recorder->collectText(text);
510     return;
511   }
512 
513   assert(!m_currentText);
514   m_currentText = text;
515 }
516 
collectStickyNote()517 void IWORKCollector::collectStickyNote()
518 {
519   ETONYEK_DEBUG_MSG(("IWORKCollector::collectStickyNote: not implemented\n"));
520 }
521 
startDocument(const librevenge::RVNGPropertyList & props)522 void IWORKCollector::startDocument(const librevenge::RVNGPropertyList &props)
523 {
524   m_document->startDocument(props);
525 }
526 
endDocument()527 void IWORKCollector::endDocument()
528 {
529   assert(m_levelStack.empty());
530   assert(m_pathStack.empty());
531   assert(0 == m_groupLevel && 0 == m_groupOpenLevel);
532 
533   assert(!m_currentPath);
534   assert(!m_currentText);
535 
536   m_document->endDocument();
537 }
538 
startGroup()539 void IWORKCollector::startGroup()
540 {
541   if (bool(m_recorder))
542   {
543     m_recorder->startGroup();
544     return;
545   }
546 
547   ++m_groupLevel;
548 }
549 
endGroup()550 void IWORKCollector::endGroup()
551 {
552   if (bool(m_recorder))
553   {
554     m_recorder->endGroup();
555     return;
556   }
557 
558   assert(m_groupLevel > 0);
559 
560   --m_groupLevel;
561 }
562 
openGroup()563 void IWORKCollector::openGroup()
564 {
565   if (bool(m_recorder))
566   {
567     m_recorder->openGroup();
568     return;
569   }
570 
571   assert(m_groupLevel > 0);
572   m_outputManager.getCurrent().addOpenGroup(librevenge::RVNGPropertyList());
573   ++m_groupOpenLevel;
574 }
575 
closeGroup()576 void IWORKCollector::closeGroup()
577 {
578   if (bool(m_recorder))
579   {
580     m_recorder->closeGroup();
581     return;
582   }
583   assert(m_groupLevel > 0 && m_groupOpenLevel > 0);
584   m_outputManager.getCurrent().addCloseGroup();
585   --m_groupOpenLevel;
586 }
587 
createTable(const IWORKTableNameMapPtr_t & tableNameMap,const IWORKLanguageManager & langManager) const588 std::shared_ptr<IWORKTable> IWORKCollector::createTable(const IWORKTableNameMapPtr_t &tableNameMap, const IWORKLanguageManager &langManager) const
589 {
590   return shared_ptr<IWORKTable>(new IWORKTable(tableNameMap, langManager));
591 }
592 
createText(const IWORKLanguageManager & langManager,bool discardEmptyContent,bool allowListInsertion) const593 std::shared_ptr<IWORKText> IWORKCollector::createText(const IWORKLanguageManager &langManager, bool discardEmptyContent, bool allowListInsertion) const
594 {
595   return make_shared<IWORKText>(langManager, discardEmptyContent, allowListInsertion);
596 }
597 
startLevel()598 void IWORKCollector::startLevel()
599 {
600   if (bool(m_recorder))
601   {
602     m_recorder->startLevel();
603     return;
604   }
605 
606   glm::dmat3 currentTrafo(1), prevTrafo(1);
607   if (!m_levelStack.empty())
608   {
609     currentTrafo = m_levelStack.top().m_trafo;
610     prevTrafo = m_levelStack.top().m_previousTrafo;
611   }
612   m_levelStack.push(Level());
613   m_levelStack.top().m_trafo = currentTrafo;
614   m_levelStack.top().m_previousTrafo = prevTrafo;
615 
616   pushStyle();
617 }
618 
endLevel()619 void IWORKCollector::endLevel()
620 {
621   if (bool(m_recorder))
622   {
623     m_recorder->endLevel();
624     return;
625   }
626 
627   assert(!m_levelStack.empty());
628   m_levelStack.pop();
629 
630   popStyle();
631 }
632 
startAttachment()633 void IWORKCollector::startAttachment()
634 {
635   if (bool(m_recorder))
636   {
637     m_recorder->startAttachment();
638     return;
639   }
640 
641   m_attachmentStack.push(m_inAttachment);
642   m_inAttachment=true;
643 
644   m_pathStack.push(m_currentPath);
645   m_currentPath.reset();
646   startLevel();
647 }
648 
endAttachment()649 void IWORKCollector::endAttachment()
650 {
651   if (bool(m_recorder))
652   {
653     m_recorder->endAttachment();
654     return;
655   }
656 
657   assert(!m_attachmentStack.empty());
658   if (!m_attachmentStack.empty())
659   {
660     m_inAttachment=m_attachmentStack.top();
661     m_attachmentStack.pop();
662   }
663   assert(!m_pathStack.empty());
664   if (!m_pathStack.empty())
665   {
666     m_currentPath=m_pathStack.top();
667     m_pathStack.pop();
668   }
669   endLevel();
670 }
671 
startAttachments()672 void IWORKCollector::startAttachments()
673 {
674   assert(!m_inAttachments);
675   m_inAttachments = true;
676 }
677 
endAttachments()678 void IWORKCollector::endAttachments()
679 {
680   assert(m_inAttachments);
681   m_inAttachments = false;
682 }
683 
pushStyle()684 void IWORKCollector::pushStyle()
685 {
686   m_styleStack.push();
687 }
688 
popStyle()689 void IWORKCollector::popStyle()
690 {
691   m_styleStack.pop();
692 }
693 
pushStylesheet(const IWORKStylesheetPtr_t & stylesheet)694 void IWORKCollector::pushStylesheet(const IWORKStylesheetPtr_t &stylesheet)
695 {
696   if (bool(m_recorder))
697   {
698     m_recorder->pushStylesheet(stylesheet);
699     return;
700   }
701 
702   m_stylesheetStack.push(stylesheet);
703 }
704 
popStylesheet()705 void IWORKCollector::popStylesheet()
706 {
707   if (bool(m_recorder))
708   {
709     m_recorder->popStylesheet();
710     return;
711   }
712 
713   assert(!m_stylesheetStack.empty());
714 
715   m_stylesheetStack.pop();
716 }
717 
collectHeaderFooter(const std::string & name,IWORKHeaderFooterMap_t & map)718 void IWORKCollector::collectHeaderFooter(const std::string &name, IWORKHeaderFooterMap_t &map)
719 {
720   IWORKOutputElements &elements = map[name];
721   if (!elements.empty())
722   {
723     ETONYEK_DEBUG_MSG(("IWORKCollector::collectHeaderFooter: '%s' already exists, overwriting\n", name.c_str()));
724   }
725   if (bool(m_currentText))
726   {
727     m_currentText->draw(elements);
728     m_currentText.reset();
729   }
730 }
731 
fillMetadata(librevenge::RVNGPropertyList & props)732 void IWORKCollector::fillMetadata(librevenge::RVNGPropertyList &props)
733 {
734   if (!m_metadata.m_title.empty())
735     props.insert("dc:subject", m_metadata.m_title.c_str());
736   if (!m_metadata.m_author.empty())
737     props.insert("meta:initial-creator", m_metadata.m_author.c_str());
738   if (!m_metadata.m_keywords.empty())
739     props.insert("meta:keyword", m_metadata.m_keywords.c_str());
740   if (!m_metadata.m_comment.empty())
741     props.insert("librevenge:comments", m_metadata.m_comment.c_str());
742 }
743 
fillGraphicProps(const IWORKStylePtr_t style,librevenge::RVNGPropertyList & props,bool isSurface,bool isFrame)744 void IWORKCollector::fillGraphicProps(const IWORKStylePtr_t style, librevenge::RVNGPropertyList &props,
745                                       bool isSurface, bool isFrame)
746 {
747   assert(bool(style));
748 
749   using namespace property;
750 
751   double opacity=style->has<Opacity>() ? style->get<Opacity>() : 1.;
752   if (isSurface && style->has<Fill>())
753   {
754     FillWriter fillWriter(props);
755     apply_visitor(fillWriter, style->get<Fill>());
756     opacity*=fillWriter.getOpacity();
757   }
758   else
759     props.insert("draw:fill", "none");
760 
761   if (style->has<Stroke>())
762   {
763     const IWORKStroke &stroke = style->get<Stroke>();
764     IWORKStrokeType type = stroke.m_pattern.m_type;
765     if ((type == IWORK_STROKE_TYPE_DASHED) && stroke.m_pattern.m_values.size() < 2)
766       type = IWORK_STROKE_TYPE_SOLID;
767 
768     if (isFrame)
769     {
770       std::string bType("none");
771       switch (type)
772       {
773       case IWORK_STROKE_TYPE_NONE :
774         break;
775       case IWORK_STROKE_TYPE_SOLID :
776         bType="solid";
777         break;
778       case IWORK_STROKE_TYPE_DASHED :
779         bType=(stroke.m_pattern.m_values[0]<0.1 ? "dotted" : "dashed");
780         break;
781       case IWORK_STROKE_TYPE_AUTO :
782         bType=style->has<Fill>() ? "none" : "solid";
783         break;
784       default:
785         ETONYEK_DEBUG_MSG(("IWORKCollector::fillGraphicProps: unexpected stroke type\n"));
786         break;
787       }
788       if (bType!="none")
789       {
790         std::stringstream s;
791         s << stroke.m_width << "pt " << bType << " " << makeColor(stroke.m_color).cstr();
792         props.insert("fo:border", s.str().c_str());
793       }
794       else
795         props.insert("fo:border", "none");
796     }
797     else
798     {
799       switch (type)
800       {
801       case IWORK_STROKE_TYPE_NONE :
802         props.insert("draw:stroke", "none");
803         break;
804       case IWORK_STROKE_TYPE_SOLID :
805         props.insert("draw:stroke", "solid");
806         break;
807       case IWORK_STROKE_TYPE_DASHED :
808         props.insert("draw:stroke", "dash");
809         props.insert("draw:dots1", 1);
810         props.insert("draw:dots1-length", stroke.m_pattern.m_values[0], RVNG_PERCENT);
811         props.insert("draw:dots2", 1);
812         props.insert("draw:dots2-length", stroke.m_pattern.m_values[0], RVNG_PERCENT);
813         props.insert("draw:distance", stroke.m_pattern.m_values[1], RVNG_PERCENT);
814         break;
815       case IWORK_STROKE_TYPE_AUTO :
816         if (style->has<Fill>())
817           props.insert("draw:stroke", "none");
818         else
819           props.insert("draw:stroke", "solid");
820         break;
821       default:
822         ETONYEK_DEBUG_MSG(("IWORKCollector::fillGraphicProps: unexpected stroke type\n"));
823         break;
824       }
825 
826       props.insert("svg:stroke-width", pt2in(stroke.m_width));
827       props.insert("svg:stroke-color", makeColor(stroke.m_color));
828 
829       switch (stroke.m_cap)
830       {
831       default :
832       case IWORK_LINE_CAP_NONE :
833 
834       case IWORK_LINE_CAP_BUTT :
835         props.insert("svg:stroke-linecap", "butt");
836         break;
837       case IWORK_LINE_CAP_ROUND :
838         props.insert("svg:stroke-linecap", "round");
839         break;
840       }
841 
842       switch (stroke.m_join)
843       {
844       case IWORK_LINE_JOIN_MITER :
845         props.insert("svg:stroke-linejoin", "miter");
846         break;
847       case IWORK_LINE_JOIN_ROUND :
848         props.insert("svg:stroke-linejoin", "round");
849         break;
850       case IWORK_LINE_JOIN_NONE :
851       default :
852         props.insert("svg:stroke-linejoin", "none");
853       }
854     }
855   }
856 
857   for (int marker=0; marker<2; ++marker)
858   {
859     if ((marker==0 && !style->has<TailLineEnd>()) || (marker==1 && !style->has<HeadLineEnd>()))
860       continue;
861     const IWORKMarker &lineEnd=marker==0 ? style->get<TailLineEnd>() : style->get<HeadLineEnd>();
862     try
863     {
864       if (lineEnd.m_path)
865       {
866         IWORKPathPtr_t path;
867         path = std::make_shared<IWORKPath>(get(lineEnd.m_path));
868         /*if (lineEnd.m_filled) path->closePath(false); */
869         (*path)*=glm::dmat3(-1,0,0,0,-1,0,0,0,1);
870         std::string finalStr=path->str();
871         double bdbox[4];
872         path->computeBoundingBox(bdbox[0],bdbox[1],bdbox[2],bdbox[3]);
873         if (!finalStr.empty())
874         {
875           props.insert(marker==0 ? "draw:marker-start-path" : "draw:marker-end-path", finalStr.c_str());
876           std::stringstream s;
877           // viewbox's componant must be integer...
878           s << std::floor(bdbox[0]) << " " << std::floor(bdbox[1]) << " " << std::ceil(bdbox[2]) << " " << std::ceil(bdbox[3]);
879           props.insert(marker==0 ? "draw:marker-start-viewbox" : "draw:marker-end-viewbox", s.str().c_str());
880           if (lineEnd.m_scale>0 && bdbox[2]>bdbox[0]) // unsure
881             props.insert(marker==0 ? "draw:marker-start-width" : "draw:marker-end-width",
882                          1.5*(bdbox[2]-bdbox[0])*lineEnd.m_scale, librevenge::RVNG_POINT);
883           props.insert(marker==0 ? "draw:marker-start-center" : "draw:marker-end-center", true);
884         }
885       }
886     }
887     catch (const IWORKPath::InvalidException &)
888     {
889       ETONYEK_DEBUG_MSG(("IWORKCollector::fillGraphicProps: '%s' is not a valid path\n", get(lineEnd.m_path).c_str()));
890     }
891   }
892   if (style->has<Shadow>())
893   {
894     const IWORKShadow &shadow = style->get<Shadow>();
895     props.insert("draw:shadow", shadow.m_visible ? "visible" : "hidden");
896     props.insert("draw:shadow-color", makeColor(shadow.m_color));
897     props.insert("draw:shadow-opacity", shadow.m_opacity, RVNG_PERCENT);
898     const double angle = deg2rad(shadow.m_angle);
899     props.insert("draw:shadow-offset-x", shadow.m_offset * std::cos(angle), RVNG_POINT);
900     props.insert("draw:shadow-offset-y", shadow.m_offset * std::sin(angle), RVNG_POINT);
901   }
902 
903   if (opacity<1)
904   {
905     props.insert("draw:opacity", opacity, RVNG_PERCENT);
906     props.insert("draw:image-opacity", opacity, RVNG_PERCENT);
907   }
908 }
909 
fillLayoutProps(const IWORKStylePtr_t layoutStyle,librevenge::RVNGPropertyList & props)910 void IWORKCollector::fillLayoutProps(const IWORKStylePtr_t layoutStyle, librevenge::RVNGPropertyList &props)
911 {
912   if (!layoutStyle) return;
913   if (layoutStyle->has<property::VerticalAlignment>())
914   {
915     const IWORKVerticalAlignment align = layoutStyle->get<property::VerticalAlignment>();
916     switch (align)
917     {
918     case IWORK_VERTICAL_ALIGNMENT_TOP:
919       props.insert("draw:textarea-vertical-align", "top");
920       break;
921     case IWORK_VERTICAL_ALIGNMENT_MIDDLE:
922       props.insert("draw:textarea-vertical-align", "middle");
923       break;
924     case IWORK_VERTICAL_ALIGNMENT_BOTTOM:
925       props.insert("draw:textarea-vertical-align", "bottom");
926       break;
927     default:
928       ETONYEK_DEBUG_MSG(("IWORKCollector::fillLayoutProps: find unknown vertical alignment\n"));
929       break;
930     }
931   }
932   if (layoutStyle->has<property::Padding>())
933   {
934     const IWORKPadding padding = layoutStyle->get<property::Padding>();
935     if (padding.m_bottom)
936       props.insert("fo:padding-bottom", get(padding.m_bottom)>0 ? get(padding.m_bottom) : 0, librevenge::RVNG_POINT);
937     if (padding.m_left)
938       props.insert("fo:padding-left", get(padding.m_left)>0 ? get(padding.m_left) : 0, librevenge::RVNG_POINT);
939     if (padding.m_right)
940       props.insert("fo:padding-right", get(padding.m_right)>0 ? get(padding.m_right) : 0, librevenge::RVNG_POINT);
941     if (padding.m_top)
942       props.insert("fo:padding-top", get(padding.m_top)>0 ? get(padding.m_top)>0 : 0, librevenge::RVNG_POINT);
943   }
944 }
945 
fillTextAutoSizeProps(const boost::optional<unsigned> & resizeFlags,const IWORKGeometryPtr_t & boundingBox,librevenge::RVNGPropertyList & props)946 void IWORKCollector::fillTextAutoSizeProps(const boost::optional<unsigned> &resizeFlags, const IWORKGeometryPtr_t &boundingBox, librevenge::RVNGPropertyList &props)
947 {
948   if (!resizeFlags) return;
949   unsigned const flags=get(resizeFlags)&3;
950   // checkme: set "draw:auto-grow-width" to true seems to create too many problems
951   if ((flags&1)==1 && boundingBox && boundingBox->m_naturalSize.m_width>0)
952     props.insert("draw:auto-grow-width",false);
953   if ((flags&2)==0)
954     props.insert("draw:auto-grow-height",true);
955   else if (boundingBox && boundingBox->m_naturalSize.m_height>0)
956   {
957     props.insert("draw:auto-grow-height",false);
958     // if ((flags&1)==0) props.insert("draw:auto-grow-width",true); checkme
959 
960     // the following seems only use in Impress
961     props.insert("draw:fit-to-size",true);
962     props.insert("style:shrink-to-fit",true);
963   }
964 }
965 
fillWrapProps(const IWORKStylePtr_t style,librevenge::RVNGPropertyList & props,const boost::optional<int> & order)966 void IWORKCollector::fillWrapProps(const IWORKStylePtr_t style, librevenge::RVNGPropertyList &props,
967                                    const boost::optional<int> &order)
968 {
969   if (order)
970   {
971     if (get(order)>=0)
972       props.insert("draw:z-index", get(order)+1);
973     else   // background
974     {
975       props.insert("draw:z-index", -get(order));
976       props.insert("style:wrap", "run-through");
977       props.insert("style:run-through", "background");
978       return;
979     }
980   }
981   if (!bool(style) || !style->has<property::ExternalTextWrap>())
982     return;
983   const IWORKExternalTextWrap &wrap = style->get<property::ExternalTextWrap>();
984   switch (wrap.m_floatingType)
985   {
986   case IWORK_WRAP_TYPE_DIRECTIONAL :
987     switch (wrap.m_direction)
988     {
989     case IWORK_WRAP_DIRECTION_BOTH :
990       if (wrap.m_aligned)
991         props.insert("style:wrap", "parallel");
992       else
993         props.insert("style:wrap", "dynamic");
994       break;
995     case IWORK_WRAP_DIRECTION_LEFT :
996       props.insert("style:wrap", "left");
997       break;
998     case IWORK_WRAP_DIRECTION_RIGHT :
999       props.insert("style:wrap", "right");
1000       break;
1001     default:
1002       ETONYEK_DEBUG_MSG(("IWORKCollector::fillWrapProps: unknown direction\n"));
1003     }
1004     break;
1005   case IWORK_WRAP_TYPE_LARGEST :
1006     props.insert("style:wrap", "biggest");
1007     break;
1008   case IWORK_WRAP_TYPE_NEITHER :
1009     props.insert("style:wrap", "none");
1010     break;
1011   default:
1012     ETONYEK_DEBUG_MSG(("IWORKCollector::fillWrapProps: unknown wrap type\n"));
1013   }
1014 }
1015 
getOutputManager()1016 IWORKOutputManager &IWORKCollector::getOutputManager()
1017 {
1018   return m_outputManager;
1019 }
1020 
drawLine(const IWORKLinePtr_t & line)1021 void IWORKCollector::drawLine(const IWORKLinePtr_t &line)
1022 {
1023   // TODO: transform the line
1024   IWORKOutputElements &elements = m_outputManager.getCurrent();
1025   double origPos[2], endPos[2];
1026   if (line->m_x1 && line->m_y1 && line->m_x2 && line->m_y2)
1027   {
1028     origPos[0]=get(line->m_x1);
1029     origPos[1]=get(line->m_y1);
1030     endPos[0]=get(line->m_x2);
1031     endPos[1]=get(line->m_y2);
1032   }
1033   else if (line->m_geometry && !line->m_x1 && !line->m_y1 && !line->m_x2 && !line->m_y2)
1034   {
1035     IWORKGeometry const &geometry=*line->m_geometry;
1036     origPos[0]=geometry.m_position.m_x;
1037     origPos[1]=geometry.m_position.m_y;
1038     double dir[2]= {geometry.m_size.m_width, geometry.m_size.m_height};
1039     double finalDir[2];
1040     if (geometry.m_angle)
1041     {
1042       const double angle = get(geometry.m_angle);
1043       double c = std::cos(angle);
1044       double s = std::sin(angle);
1045       finalDir[0]=dir[0]*c-dir[1]*s;
1046       finalDir[1]=dir[0]*s+dir[1]*c;
1047     }
1048     else
1049     {
1050       finalDir[0]=dir[0];
1051       finalDir[1]=dir[1];
1052     }
1053     // geometry.m_position is always the boundary top left point
1054     if (finalDir[0]<0)
1055     {
1056       endPos[0]=origPos[0];
1057       origPos[0]=endPos[0]-finalDir[0];
1058     }
1059     else
1060       endPos[0]=origPos[0]+finalDir[0];
1061     if (finalDir[1]<0)
1062     {
1063       endPos[1]=origPos[1];
1064       origPos[1]=endPos[1]-finalDir[1];
1065     }
1066     else
1067       endPos[1]=origPos[1]+finalDir[1];
1068     if (m_accumulateTransform)
1069     {
1070       const glm::dmat3 trafo = m_levelStack.top().m_previousTrafo;
1071       glm::dvec3 pos = trafo * glm::dvec3(origPos[0], origPos[1], 1);
1072       origPos[0]=pos[0];
1073       origPos[1]=pos[1];
1074       pos = trafo * glm::dvec3(endPos[0], endPos[1], 1);
1075       endPos[0]=pos[0];
1076       endPos[1]=pos[1];
1077     }
1078   }
1079   else
1080   {
1081     ETONYEK_DEBUG_MSG(("drawLine[IWORKCollector]: line is missing head or tail point\n"));
1082     return;
1083   }
1084   librevenge::RVNGPropertyList props;
1085   if (bool(line->m_style))
1086     fillGraphicProps(line->m_style, props, false);
1087   elements.addSetStyle(props);
1088 
1089   librevenge::RVNGPropertyListVector vertices;
1090   vertices.append(makePoint(origPos[0],origPos[1]));
1091   vertices.append(makePoint(endPos[0],endPos[1]));
1092 
1093   librevenge::RVNGPropertyList points;
1094   points.insert("svg:points", vertices);
1095   fillShapeProperties(points);
1096 
1097   elements.addDrawPolyline(points);
1098 }
1099 
drawMedia(const IWORKMediaPtr_t & media)1100 void IWORKCollector::drawMedia(const IWORKMediaPtr_t &media)
1101 {
1102   if (bool(media)
1103       && bool(media->m_geometry)
1104       && bool(media->m_content)
1105       && bool(media->m_content->m_data)
1106       && bool(media->m_content->m_data->m_stream))
1107   {
1108     const glm::dmat3 trafo = m_levelStack.top().m_trafo;
1109     const RVNGInputStreamPtr_t input = media->m_content->m_data->m_stream;
1110 
1111     std::string mimetype(media->m_content->m_data->m_mimeType);
1112     if (mimetype.empty())
1113       mimetype = detectMimetype(input);
1114     if (!mimetype.empty())
1115     {
1116       input->seek(0, librevenge::RVNG_SEEK_END);
1117       const auto size = (unsigned long) input->tell();
1118       input->seek(0, librevenge::RVNG_SEEK_SET);
1119 
1120       unsigned long readBytes = 0;
1121       const unsigned char *const bytes = input->read(size, readBytes);
1122       if (readBytes != size)
1123         throw GenericException();
1124 
1125       librevenge::RVNGPropertyList props;
1126       glm::dvec3 pos = trafo * glm::dvec3(0, 0, 1);
1127       glm::dvec3 dim = trafo * glm::dvec3(media->m_geometry->m_size.m_width, media->m_geometry->m_size.m_height, 0);
1128       if (media->m_cropGeometry)
1129       {
1130         /* cropping seems to pose problem to LibreOffice because
1131            sometimes it does not use the real picture size to clip
1132            the picture (or I make some mistakes).
1133 
1134            So for now, we only reset the origin and resize the picture to its final size */
1135         pos = glm::dvec3(media->m_cropGeometry->m_position.m_x, media->m_cropGeometry->m_position.m_y, 1);
1136         dim = glm::dvec3(media->m_cropGeometry->m_size.m_width, media->m_cropGeometry->m_size.m_height, 0);
1137         if (m_accumulateTransform)
1138         {
1139           pos = m_levelStack.top().m_previousTrafo * pos;
1140           dim = trafo * dim;
1141         }
1142       }
1143 
1144       // check if the image is flipped, ...
1145       if (dim[0]<0 && dim[1]<0)
1146       {
1147         props.insert("librevenge:rotate", 180, librevenge::RVNG_GENERIC);
1148         pos[0]+=dim[0];
1149         pos[1]+=dim[1];
1150         dim[0]*=-1;
1151         dim[1]*=-1;
1152       }
1153       else if (dim[0]<0)
1154       {
1155         props.insert("draw:mirror-horizontal", true);
1156         pos[0]+=dim[0];
1157         dim[0]*=-1;
1158       }
1159       else if (dim[1]<0)
1160       {
1161         props.insert("draw:mirror-vertical", true);
1162         pos[1]+=dim[1];
1163         dim[1]*=-1;
1164       }
1165 
1166       if (bool(media->m_style))
1167       {
1168         fillGraphicProps(media->m_style, props, false, false);
1169         fillWrapProps(media->m_style, props, media->m_order);
1170       }
1171       props.insert("librevenge:mime-type", mimetype.c_str());
1172       props.insert("office:binary-data", librevenge::RVNGBinaryData(bytes, size));
1173       props.insert("svg:width", pt2in(dim[0]));
1174       props.insert("svg:height", pt2in(dim[1]));
1175       drawMedia(pos[0], pos[1], props);
1176     }
1177   }
1178 }
1179 
drawShape(const IWORKShapePtr_t & shape)1180 void IWORKCollector::drawShape(const IWORKShapePtr_t &shape)
1181 {
1182   if (!bool(shape) || !bool(shape->m_path))
1183   {
1184     ETONYEK_DEBUG_MSG(("IWORKCollector::drawShape: can not find the shape\n"));
1185     return;
1186   }
1187   const glm::dmat3 trafo = m_levelStack.top().m_trafo;
1188   IWORKOutputElements &elements = m_outputManager.getCurrent();
1189 
1190   const IWORKPath path = *shape->m_path * trafo;
1191   bool isRectangle=path.isRectangle();
1192   bool hasText=bool(shape->m_text) && !shape->m_text->empty();
1193   bool createOnlyTextbox= hasText && isRectangle;
1194   librevenge::RVNGPropertyList styleProps;
1195 
1196   if (bool(shape->m_style))
1197   {
1198     fillGraphicProps(shape->m_style, styleProps, true, createOnlyTextbox && createFrameStylesForTextBox());
1199     fillWrapProps(shape->m_style, styleProps, shape->m_order);
1200   }
1201   if (shape->m_locked) // CHECKME: maybe also content
1202     styleProps.insert("style:protect", "position size");
1203   if (createOnlyTextbox)
1204   {
1205     IWORKStylePtr_t layoutStyle=shape->m_text->getLayoutStyle();
1206     if (!layoutStyle && bool(shape->m_style) && shape->m_style->has<property::LayoutStyle>())
1207       layoutStyle=shape->m_style->get<property::LayoutStyle>();
1208     fillLayoutProps(layoutStyle, styleProps);
1209     fillTextAutoSizeProps(shape->m_resizeFlags,shape->m_geometry,styleProps);
1210     return drawTextBox(shape->m_text, trafo, shape->m_geometry, styleProps);
1211   }
1212 
1213   librevenge::RVNGPropertyList shapeProps;
1214   librevenge::RVNGPropertyListVector vec;
1215   path.write(vec);
1216   shapeProps.insert("svg:d", vec);
1217   fillShapeProperties(shapeProps);
1218 
1219   elements.addSetStyle(styleProps);
1220   elements.addDrawPath(shapeProps);
1221 
1222   if (hasText)
1223   {
1224     librevenge::RVNGPropertyList props;
1225     IWORKStylePtr_t layoutStyle=shape->m_text->getLayoutStyle();
1226     if (!layoutStyle && bool(shape->m_style) && shape->m_style->has<property::LayoutStyle>())
1227       layoutStyle=shape->m_style->get<property::LayoutStyle>();
1228     fillLayoutProps(layoutStyle, props);
1229     fillTextAutoSizeProps(shape->m_resizeFlags,shape->m_geometry,props);
1230     drawTextBox(shape->m_text, trafo, shape->m_geometry, props);
1231   }
1232 }
1233 
writeFill(const IWORKFill & fill,librevenge::RVNGPropertyList & props)1234 void IWORKCollector::writeFill(const IWORKFill &fill, librevenge::RVNGPropertyList &props)
1235 {
1236   apply_visitor(FillWriter(props), fill);
1237 }
1238 
1239 } // namespace libetonyek
1240 
1241 /* vim:set shiftwidth=2 softtabstop=2 expandtab: */
1242