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