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