1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  * This file is part of the libqxp 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 "QXPParser.h"
11 
12 #include "QXPContentCollector.h"
13 #include "QXPHeader.h"
14 
15 #include <cmath>
16 #include <memory>
17 
18 namespace libqxp
19 {
20 
21 using librevenge::RVNGStringStream;
22 using std::make_shared;
23 
QXPParser(const std::shared_ptr<librevenge::RVNGInputStream> & input,librevenge::RVNGDrawingInterface * painter,const std::shared_ptr<QXPHeader> & header)24 QXPParser::QXPParser(const std::shared_ptr<librevenge::RVNGInputStream> &input, librevenge::RVNGDrawingInterface *painter, const std::shared_ptr<QXPHeader> &header)
25   : m_input(input)
26   , m_painter(painter)
27   , be(header->isBigEndian())
28   , m_blockParser(input, header)
29   , m_textParser(input, header)
30   , m_colors()
31   , m_fonts()
32   , m_charFormats()
33   , m_paragraphFormats()
34   , m_lineStyles()
35   , m_arrows()
36   , m_hjs()
37   , m_header(header)
38 {
39   // default colors, in case parsing fails
40   m_colors[0] = Color(255, 255, 255); // white
41   m_colors[1] = Color(0, 0, 0); // black
42   m_colors[2] = Color(255, 0, 0); // red
43   m_colors[3] = Color(0, 255, 0); // green
44   m_colors[4] = Color(0, 0, 255); // blue
45   m_colors[5] = Color(1, 160, 198); // cyan
46   m_colors[6] = Color(239, 4, 127); // magenta
47   m_colors[7] = Color(255, 255, 0); // yellow
48   m_colors[8] = Color(0, 0, 0); // registration
49 
50   // customm dashes are available only from 4.0
51   m_lineStyles[0] = LineStyle({}, true, 1.0, LineCapType::BUTT, LineJoinType::MITER);
52   m_lineStyles[1] = LineStyle({0.6, 0.4}, true, 5.0, LineCapType::BUTT, LineJoinType::MITER);
53   m_lineStyles[2] = LineStyle({0.75, 0.25}, true, 4.0, LineCapType::BUTT, LineJoinType::MITER);
54   m_lineStyles[3] = LineStyle({0.5455, 0.1818, 0.0909, 0.1818}, true, 11.0, LineCapType::BUTT, LineJoinType::MITER);
55   m_lineStyles[4] = LineStyle({0.0, 1.0}, true, 2.0, LineCapType::ROUND, LineJoinType::MITER);
56 
57   m_arrows = std::vector<Arrow>( // should be in ctor, but breaks astyle
58   {
59     // does viewbox has any effect?
60     Arrow("m9 0 l-9 25 l6 -1.5 l6 0 l6 1.5 z", "0 0 18 25", 3),
61     Arrow("m9 5 l-9 -5 l0 20 l6 10 l6 0 l6 -10 l0 -20 z", "0 0 18 35", 2.5)
62   });
63 }
64 
parse()65 bool QXPParser::parse()
66 {
67   QXPContentCollector collector(m_painter);
68 
69   collector.startDocument();
70 
71   auto docStream = m_blockParser.getChain(3);
72   if (!parseDocument(docStream, collector))
73     return false;
74 
75   if (!parsePages(docStream, collector))
76     return false;
77 
78   collector.endDocument();
79 
80   return true;
81 }
82 
getColor(unsigned id,Color defaultColor) const83 Color QXPParser::getColor(unsigned id, Color defaultColor) const
84 {
85   auto it = m_colors.find(id);
86   if (it == m_colors.end())
87   {
88     QXP_DEBUG_MSG(("Color %u not found\n", id));
89     return defaultColor;
90   }
91   return (*it).second;
92 }
93 
getLineStyle(unsigned id) const94 const LineStyle *QXPParser::getLineStyle(unsigned id) const
95 {
96   auto it = m_lineStyles.find(id);
97   if (it == m_lineStyles.end())
98   {
99     QXP_DEBUG_MSG(("Line style %u not found\n", id));
100     return nullptr;
101   }
102   return &it->second;
103 }
104 
getFont(int id,std::string defaultFont) const105 std::string QXPParser::getFont(int id, std::string defaultFont) const
106 {
107   auto it = m_fonts.find(id);
108   if (it == m_fonts.end())
109   {
110     QXP_DEBUG_MSG(("Font %d not found\n", id));
111     return defaultFont;
112   }
113   return (*it).second;
114 }
115 
skipRecord(const std::shared_ptr<librevenge::RVNGInputStream> & stream)116 void QXPParser::skipRecord(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
117 {
118   unsigned length = readU32(stream, be);
119   if (length > 0)
120   {
121     skip(stream, length);
122   }
123 }
124 
parseFonts(const std::shared_ptr<librevenge::RVNGInputStream> & stream)125 void QXPParser::parseFonts(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
126 {
127   const unsigned end = readRecordEndOffset(stream);
128 
129   try
130   {
131     unsigned count = readU16(stream, be);
132     for (unsigned i = 0; i < count; ++i)
133     {
134       int index = readS16(stream, be);
135       if (m_header->version() >= QXP_4)
136       {
137         skip(stream, 2);
138       }
139       auto name = readPlatformString(stream, be);
140       readPlatformString(stream, be); // skip
141 
142       m_fonts[index] = name;
143     }
144   }
145   catch (...)
146   {
147     QXP_DEBUG_MSG(("Failed to parse fonts, offset %ld\n", stream->tell()));
148   }
149 
150   seek(stream, end);
151 }
152 
parseHJs(const std::shared_ptr<librevenge::RVNGInputStream> & stream)153 void QXPParser::parseHJs(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
154 {
155   parseCollection(stream, [=]()
156   {
157     m_hjs.push_back(parseHJ(stream));
158   });
159 }
160 
parseCharFormats(const std::shared_ptr<librevenge::RVNGInputStream> & stream)161 void QXPParser::parseCharFormats(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
162 {
163   m_charFormats.clear();
164   parseCollection(stream, [=]()
165   {
166     m_charFormats.emplace_back(make_shared<CharFormat>(parseCharFormat(stream)));
167   });
168 }
169 
parseHJProps(const std::shared_ptr<librevenge::RVNGInputStream> & stream,HJ & result)170 void QXPParser::parseHJProps(const std::shared_ptr<librevenge::RVNGInputStream> &stream, HJ &result)
171 {
172   skip(stream, 1);
173   result.minBefore = readU8(stream);
174   result.minAfter = readU8(stream);
175   result.maxInRow = readU8(stream);
176   skip(stream, 4);
177   result.singleWordJustify = readU8(stream) == 0;
178   skip(stream, 1);
179   result.hyphenate = readU8(stream) != 0;
180   skip(stream, 33);
181 }
182 
parseCommonCharFormatProps(const std::shared_ptr<librevenge::RVNGInputStream> & stream,CharFormat & result)183 void QXPParser::parseCommonCharFormatProps(const std::shared_ptr<librevenge::RVNGInputStream> &stream, CharFormat &result)
184 {
185   const int fontIndex = readS16(stream, be);
186   result.fontName = getFont(fontIndex).c_str();
187 
188   const unsigned flags = readU16(stream, be);
189   convertCharFormatFlags(flags, result);
190 
191   result.fontSize = readFraction(stream, be);
192 }
193 
parseTabStop(const std::shared_ptr<librevenge::RVNGInputStream> & stream)194 TabStop QXPParser::parseTabStop(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
195 {
196   TabStop tabStop;
197 
198   const uint8_t type = readU8(stream);
199   tabStop.type = convertTabStopType(type);
200 
201   const uint8_t alignChar = readU8(stream);
202   tabStop.alignChar.clear();
203   switch (alignChar)
204   {
205   case 1:
206     tabStop.alignChar.append('.');
207     break;
208   case 2:
209     tabStop.alignChar.append(',');
210     break;
211   default:
212     tabStop.alignChar.append(char(alignChar));
213     break;
214   }
215 
216   tabStop.fillChar.clear();
217   tabStop.fillChar.append(char(readU16(stream, be)));
218 
219   tabStop.position = readFraction(stream, be);
220 
221   return tabStop;
222 }
223 
parseParagraphFormats(const std::shared_ptr<librevenge::RVNGInputStream> & stream)224 void QXPParser::parseParagraphFormats(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
225 {
226   m_paragraphFormats.clear();
227   parseCollection(stream, [=]()
228   {
229     m_paragraphFormats.emplace_back(make_shared<ParagraphFormat>(parseParagraphFormat(stream)));
230   });
231 }
232 
parseCollection(const std::shared_ptr<librevenge::RVNGInputStream> stream,std::function<void ()> itemHandler)233 void QXPParser::parseCollection(const std::shared_ptr<librevenge::RVNGInputStream> stream, std::function<void()> itemHandler)
234 {
235   const unsigned end = readRecordEndOffset(stream);
236 
237   try
238   {
239     while (stream->tell() < end)
240     {
241       itemHandler();
242     }
243   }
244   catch (...)
245   {
246     QXP_DEBUG_MSG(("Failed to parse collection, offset %ld\n", stream->tell()));
247   }
248 
249   seek(stream, end);
250 }
251 
parsePageSettings(const std::shared_ptr<librevenge::RVNGInputStream> & stream)252 std::vector<PageSettings> QXPParser::parsePageSettings(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
253 {
254   skip(stream, 6);
255   const unsigned count = readU16(stream, be);
256   if (count == 0 || count > 2)
257   {
258     QXP_DEBUG_MSG(("Invalid page settings blocks count %u\n", count));
259     throw ParseError();
260   }
261   skip(stream, 2);
262 
263   std::vector<PageSettings> pages;
264   pages.resize(count);
265   for (auto &page : pages)
266   {
267     page.offset.top = readFraction(stream, be);
268     page.offset.left = readFraction(stream, be);
269     page.offset.bottom = readFraction(stream, be);
270     page.offset.right = readFraction(stream, be);
271     skip(stream, 36);
272     skip(stream, m_header->version() >= QXP_4 ? 12 : 8);
273   }
274 
275   for (unsigned i = 0; i <= count; ++i)
276   {
277     const unsigned length = readU32(stream, be);
278     skip(stream, length + 4);
279   }
280 
281   if (!be)
282   {
283     skip(stream, 4);
284   }
285   const unsigned nameLength = readU32(stream, be);
286   skip(stream, nameLength);
287 
288   return pages;
289 }
290 
parseText(unsigned index,unsigned linkId,QXPCollector & collector)291 std::shared_ptr<Text> QXPParser::parseText(unsigned index, unsigned linkId, QXPCollector &collector)
292 {
293   try
294   {
295     auto text = m_textParser.parseText(index, m_charFormats, m_paragraphFormats);
296     collector.collectText(text, linkId);
297     return text;
298   }
299   catch (...)
300   {
301     QXP_DEBUG_MSG(("Failed to parse text %u\n", index));
302     return make_shared<Text>();
303   }
304 }
305 
readRecordEndOffset(const std::shared_ptr<librevenge::RVNGInputStream> & stream)306 uint32_t QXPParser::readRecordEndOffset(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
307 {
308   unsigned length = readU32(stream, be);
309   return stream->tell() + length;
310 }
311 
readColorComp(const std::shared_ptr<librevenge::RVNGInputStream> & stream)312 uint8_t QXPParser::readColorComp(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
313 {
314   return uint8_t(std::round(255 * readFloat16(stream, be)));
315 }
316 
readObjectBBox(const std::shared_ptr<librevenge::RVNGInputStream> & stream)317 Rect QXPParser::readObjectBBox(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
318 {
319   Rect bbox;
320   bbox.top = readFraction(stream, be);
321   bbox.left = readFraction(stream, be);
322   bbox.bottom = readFraction(stream, be);
323   bbox.right = readFraction(stream, be);
324   return bbox;
325 }
326 
readGradient(const std::shared_ptr<librevenge::RVNGInputStream> & stream,const Color & color1)327 Gradient QXPParser::readGradient(const std::shared_ptr<librevenge::RVNGInputStream> &stream, const Color &color1)
328 {
329   Gradient gradient;
330   gradient.color1 = color1;
331 
332   skip(stream, m_header->version() >= QXP_4 ? 20 : 14);
333 
334   const uint8_t type = readU16(stream, be) & 0xff;
335   switch (type)
336   {
337   default:
338     QXP_DEBUG_MSG(("Unknown gradient type %u\n", type));
339     QXP_FALLTHROUGH; // pick a default
340   case 0x10:
341     gradient.type = GradientType::LINEAR;
342     break;
343   case 0x18:
344     gradient.type = GradientType::MIDLINEAR;
345     break;
346   case 0x19:
347     gradient.type = GradientType::RECTANGULAR;
348     break;
349   case 0x1a:
350     gradient.type = GradientType::DIAMOND;
351     break;
352   case 0x1b:
353     gradient.type = GradientType::CIRCULAR;
354     break;
355   case 0x1c:
356     gradient.type = GradientType::FULLCIRCULAR;
357     break;
358   }
359   skip(stream, 4);
360 
361   unsigned colorId;
362   if (m_header->version() >= QXP_4)
363   {
364     colorId = readU16(stream, be);
365   }
366   else
367   {
368     colorId = readU8(stream);
369     skip(stream, 1);
370   }
371   const double shade = readFraction(stream, be);
372   gradient.color2 = getColor(colorId).applyShade(shade);
373 
374   gradient.angle = readFraction(stream, be);
375   skip(stream, 4);
376 
377   return gradient;
378 }
379 
readHorAlign(const std::shared_ptr<librevenge::RVNGInputStream> & stream)380 HorizontalAlignment QXPParser::readHorAlign(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
381 {
382   const uint8_t align = readU8(stream);
383   switch (align)
384   {
385   default:
386     QXP_DEBUG_MSG(("Unknown hor. align %u\n", align));
387     QXP_FALLTHROUGH; // pick a default
388   case 0:
389     return HorizontalAlignment::LEFT;
390   case 1:
391     return HorizontalAlignment::CENTER;
392   case 2:
393     return HorizontalAlignment::RIGHT;
394   case 3:
395     return HorizontalAlignment::JUSTIFIED;
396   case 4:
397     return HorizontalAlignment::FORCED;
398   }
399 }
400 
readVertAlign(const std::shared_ptr<librevenge::RVNGInputStream> & stream)401 VerticalAlignment QXPParser::readVertAlign(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
402 {
403   const uint8_t align = readU8(stream);
404   switch (align)
405   {
406   default:
407     QXP_DEBUG_MSG(("Unknown vert. align %u\n", align));
408     QXP_FALLTHROUGH; // pick a default
409   case 0:
410     return VerticalAlignment::TOP;
411   case 1:
412     return VerticalAlignment::CENTER;
413   case 2:
414     return VerticalAlignment::BOTTOM;
415   case 3:
416     return VerticalAlignment::JUSTIFIED;
417   }
418 }
419 
readYX(const std::shared_ptr<librevenge::RVNGInputStream> & stream)420 Point QXPParser::readYX(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
421 {
422   Point p;
423   p.y = readFraction(stream, be);
424   p.x = readFraction(stream, be);
425   return p;
426 }
427 
readParagraphRule(const std::shared_ptr<librevenge::RVNGInputStream> & stream)428 std::shared_ptr<ParagraphRule> QXPParser::readParagraphRule(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
429 {
430   auto rule = make_shared<ParagraphRule>();
431 
432   rule->width = readFraction(stream, be);
433 
434   const unsigned styleIndex = m_header->version() >= QXP_4 ? readU16(stream, be) : readU8(stream);
435   rule->lineStyle = getLineStyle(styleIndex);
436 
437   const unsigned colorId = m_header->version() >= QXP_4 ? readU16(stream, be) : readU8(stream);
438   const double shade = readFraction(stream, be);
439   rule->color = getColor(colorId).applyShade(shade);
440 
441   rule->leftMargin = readFraction(stream, be);
442   rule->rightMargin = readFraction(stream, be);
443   rule->offset = readFraction(stream, be);
444 
445   return rule;
446 }
447 
readParagraphFlags(const std::shared_ptr<librevenge::RVNGInputStream> & stream,bool & incrementalLeading,bool & ruleAbove,bool & ruleBelow)448 uint8_t QXPParser::readParagraphFlags(const std::shared_ptr<librevenge::RVNGInputStream> &stream, bool &incrementalLeading, bool &ruleAbove, bool &ruleBelow)
449 {
450   const uint8_t flags = readU8(stream);
451   if (be)
452   {
453     ruleBelow = flags & 0x2;
454     ruleAbove = flags & 0x4;
455     incrementalLeading = flags & 0x20;
456   }
457   else
458   {
459     incrementalLeading = flags & 0x4;
460     ruleAbove = flags & 0x20;
461     ruleBelow = flags & 0x40;
462   }
463   return flags;
464 }
465 
readObjectFlags(const std::shared_ptr<librevenge::RVNGInputStream> & stream,bool & noColor)466 uint8_t QXPParser::readObjectFlags(const std::shared_ptr<librevenge::RVNGInputStream> &stream, bool &noColor)
467 {
468   const uint8_t flags = readU8(stream);
469   if (be)
470   {
471     noColor = flags & 0x80;
472   }
473   else
474   {
475     noColor = flags & 0x1;
476   }
477   return flags;
478 }
479 
setArrow(const unsigned index,Frame & frame) const480 void QXPParser::setArrow(const unsigned index, Frame &frame) const
481 {
482   switch (index)
483   {
484   case 1:
485     frame.endArrow = &m_arrows[0];
486     break;
487   case 2:
488     frame.startArrow = &m_arrows[0];
489     break;
490   case 3:
491     frame.startArrow = &m_arrows[1];
492     frame.endArrow = &m_arrows[0];
493     break;
494   case 4:
495     frame.startArrow = &m_arrows[0];
496     frame.endArrow = &m_arrows[1];
497     break;
498   case 5:
499     frame.startArrow = &m_arrows[0];
500     frame.endArrow = &m_arrows[0];
501     break;
502   }
503 }
504 
skipFileInfo(const std::shared_ptr<librevenge::RVNGInputStream> & stream)505 void QXPParser::skipFileInfo(const std::shared_ptr<librevenge::RVNGInputStream> &stream)
506 {
507   const unsigned length = readU32(stream, be);
508   if (length > 0)
509   {
510     skip(stream, length);
511   }
512 }
513 
convertCharFormatFlags(unsigned flags,CharFormat & format)514 void QXPParser::convertCharFormatFlags(unsigned flags, CharFormat &format)
515 {
516   format.bold = flags & 0x1;
517   format.italic = flags & 0x2;
518   format.underline = flags & 0x4;
519   format.outline = flags & 0x8;
520   format.shadow = flags & 0x10;
521   format.superscript = flags & 0x20;
522   format.subscript = flags & 0x40;
523   format.superior = flags & 0x100;
524   format.strike = flags & 0x200;
525   format.allCaps = flags & 0x400;
526   format.smallCaps = flags & 0x800;
527   format.wordUnderline = flags & 0x1000;
528 }
529 
convertTabStopType(unsigned type)530 TabStopType QXPParser::convertTabStopType(unsigned type)
531 {
532   switch (type)
533   {
534   default:
535     QXP_DEBUG_MSG(("Unknown tab stop type %u\n", type));
536     QXP_FALLTHROUGH; // pick a default
537   case 0:
538     return TabStopType::LEFT;
539   case 1:
540     return TabStopType::CENTER;
541   case 2:
542     return TabStopType::RIGHT;
543   case 3:
544     return TabStopType::ALIGN;
545   }
546 }
547 
548 }
549 
550 /* vim:set shiftwidth=2 softtabstop=2 expandtab: */
551