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