Coverage Report

Created: 2026-06-13 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libpagemaker/src/lib/PMDParser.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 libpagemaker 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 "PMDParser.h"
11
12
#include <algorithm>
13
#include <cassert>
14
#include <iterator>
15
#include <limits>
16
#include <memory>
17
#include <set>
18
#include <stdint.h>
19
#include <string>
20
#include <vector>
21
22
#include <boost/iterator/iterator_facade.hpp>
23
#include <boost/optional.hpp>
24
25
#include <librevenge/librevenge.h>
26
27
#include "PMDCollector.h"
28
#include "PMDExceptions.h"
29
#include "PMDRecord.h"
30
#include "PMDTypes.h"
31
#include "Units.h"
32
#include "constants.h"
33
#include "geometry.h"
34
#include "libpagemaker_utils.h"
35
#include "offsets.h"
36
37
namespace libpagemaker
38
{
39
40
namespace
41
{
42
43
void readDims(librevenge::RVNGInputStream *input, bool bigEndian, int16_t &x, int16_t &y)
44
0
{
45
0
  int16_t dim1 = readS16(input, bigEndian);
46
0
  int16_t dim2 = readS16(input, bigEndian);
47
0
  x = bigEndian ? dim2 : dim1;
48
0
  y = bigEndian ? dim1 : dim2;
49
0
}
50
51
boost::optional<PMDStrokeProperties> readRule(librevenge::RVNGInputStream *input, bool bigEndian)
52
0
{
53
0
  const uint16_t flags = readU16(input, bigEndian);
54
0
  if (!(flags & 0x1))
55
0
  {
56
0
    skip(input, 18);
57
0
    return boost::none;
58
0
  }
59
60
0
  PMDStrokeProperties stroke;
61
62
0
  stroke.m_strokeType = readU8(input, bigEndian);
63
0
  skip(input, 1);
64
  // FIXME: needs fixing of reading of stroke width elsewhere
65
0
  stroke.m_strokeWidth = uint16_t(readU32(input, bigEndian) >> 8);
66
0
  stroke.m_strokeColor = readU16(input, bigEndian);
67
0
  stroke.m_strokeTint = readU16(input, bigEndian);
68
0
  skip(input, 6);
69
70
0
  return stroke;
71
0
}
72
73
}
74
75
struct PMDParser::ToCState
76
{
77
  ToCState();
78
79
  std::set<unsigned long> parsedBlocks;
80
  unsigned seqNum;
81
};
82
83
PMDParser::ToCState::ToCState()
84
3
  : parsedBlocks()
85
3
  , seqNum(0)
86
3
{
87
3
}
88
89
class PMDParser::RecordIterator :
90
  public boost::iterator_facade
91
  < PMDParser::RecordIterator
92
  , RecordContainerList_t::const_iterator::value_type
93
  , std::bidirectional_iterator_tag
94
  , RecordContainerList_t::const_iterator::reference
95
  , RecordContainerList_t::const_iterator::difference_type
96
  >
97
{
98
public:
99
  RecordIterator(const RecordContainerList_t &records);
100
  RecordIterator(const RecordContainerList_t &records, uint16_t seqNum);
101
  RecordIterator(const RecordContainerList_t &records, const RecordTypeMap_t &types, uint16_t recType);
102
103
private:
104
  friend class boost::iterator_core_access;
105
106
  reference dereference() const;
107
  bool equal(const RecordIterator &other) const;
108
  void increment();
109
  void decrement();
110
111
private:
112
  RecordContainerList_t::const_iterator m_it;
113
  RecordContainerList_t::const_iterator m_begin;
114
  RecordContainerList_t::const_iterator m_end;
115
  boost::optional<uint16_t> m_selector;
116
  boost::optional<RecordTypeMap_t::mapped_type::const_iterator> m_recIt;
117
  RecordTypeMap_t::mapped_type::const_iterator m_recBegin;
118
  RecordTypeMap_t::mapped_type::const_iterator m_recEnd;
119
};
120
121
PMDParser::RecordIterator::RecordIterator(const RecordContainerList_t &records)
122
21
  : m_it(records.end())
123
21
  , m_begin(records.begin())
124
21
  , m_end(records.end())
125
21
  , m_selector()
126
21
  , m_recIt()
127
21
  , m_recBegin()
128
21
  , m_recEnd()
129
21
{
130
21
}
131
132
PMDParser::RecordIterator::RecordIterator(const RecordContainerList_t &records, const uint16_t seqNum)
133
0
  : m_it(records.begin())
134
0
  , m_begin(records.begin())
135
0
  , m_end(records.end())
136
0
  , m_selector(seqNum)
137
0
  , m_recIt()
138
0
  , m_recBegin()
139
0
  , m_recEnd()
140
0
{
141
0
  increment();
142
0
}
143
144
PMDParser::RecordIterator::RecordIterator(const RecordContainerList_t &records, const RecordTypeMap_t &types, const uint16_t recType)
145
9
  : m_it(records.end())
146
9
  , m_begin(records.begin())
147
9
  , m_end(records.end())
148
9
  , m_selector()
149
9
  , m_recIt()
150
9
  , m_recBegin()
151
9
  , m_recEnd()
152
9
{
153
9
  const RecordTypeMap_t::const_iterator it = types.find(recType);
154
9
  if (it != types.end())
155
3
  {
156
3
    m_recBegin = it->second.begin();
157
3
    m_recIt = m_recBegin;
158
3
    m_recEnd = it->second.end();
159
3
    if (get(m_recIt) != m_recEnd)
160
3
      m_it = m_begin + *get(m_recIt);
161
3
  }
162
9
}
163
164
PMDParser::RecordIterator::reference PMDParser::RecordIterator::dereference() const
165
9
{
166
9
  return *m_it;
167
9
}
168
169
bool PMDParser::RecordIterator::equal(const RecordIterator &other) const
170
21
{
171
21
  return (m_it == m_end && other.m_it == other.m_end) || m_it == other.m_it;
172
21
}
173
174
void PMDParser::RecordIterator::increment()
175
6
{
176
6
  if (m_selector)
177
0
  {
178
0
    if (m_it != m_end)
179
0
    {
180
0
      ++m_it;
181
0
      while (m_it != m_end && m_it->m_seqNum != get(m_selector))
182
0
        ++m_it;
183
0
    }
184
0
  }
185
6
  else if (m_recIt && get(m_recIt) != m_recEnd)
186
6
  {
187
6
    ++get(m_recIt);
188
6
    if (get(m_recIt) == m_recEnd)
189
0
      m_it = m_end;
190
6
    else
191
6
      m_it = m_begin + *get(m_recIt);
192
6
  }
193
6
}
194
195
void PMDParser::RecordIterator::decrement()
196
0
{
197
0
  if (m_selector)
198
0
  {
199
0
    if (m_it != m_begin)
200
0
    {
201
0
      --m_it;
202
0
      while (m_it != m_begin && m_it->m_seqNum != get(m_selector))
203
0
        --m_it;
204
0
    }
205
0
  }
206
0
  else if (m_recIt && get(m_recIt) != m_recBegin)
207
0
  {
208
0
    --get(m_recIt);
209
0
    m_it = m_begin + *get(m_recIt);
210
0
  }
211
0
}
212
213
PMDParser::PMDParser(librevenge::RVNGInputStream *input, PMDCollector *collector)
214
4
  : m_input(input), m_length(getLength(input)), m_collector(collector),
215
4
    m_records(), m_bigEndian(false), m_recordsInOrder(), m_xFormMap()
216
4
{
217
4
}
218
219
const PMDXForm &PMDParser::getXForm(const uint32_t xFormId) const
220
0
{
221
0
  if (xFormId != (std::numeric_limits<uint32_t>::max)() && xFormId != 0)
222
0
  {
223
0
    auto it = m_xFormMap.find(xFormId);
224
225
0
    if (it != m_xFormMap.end())
226
0
      return it->second;
227
0
  }
228
229
0
  return m_xFormMap.find(0)->second;
230
0
}
231
232
void seekToRecord(librevenge::RVNGInputStream *const input, const PMDRecordContainer &container, const unsigned recordIndex)
233
172k
{
234
172k
  uint32_t recordOffset = container.m_offset;
235
172k
  if (recordIndex > 0)
236
172k
  {
237
172k
    boost::optional<unsigned> sizePerRecord = getRecordSize(container.m_recordType);
238
172k
    if (!sizePerRecord.is_initialized())
239
0
    {
240
0
      throw UnknownRecordSizeException(container.m_recordType);
241
0
    }
242
172k
    recordOffset += sizePerRecord.get() * recordIndex;
243
172k
  }
244
172k
  seek(input, recordOffset);
245
172k
}
246
247
PMDShapePoint readPoint(librevenge::RVNGInputStream *const input, const bool bigEndian)
248
516k
{
249
516k
  const PMDShapeUnit x(readS16(input, bigEndian));
250
516k
  const PMDShapeUnit y(readS16(input, bigEndian));
251
516k
  return bigEndian ? PMDShapePoint(y, x) : PMDShapePoint(x, y);
252
516k
}
253
254
void PMDParser::parseGlobalInfo(const PMDRecordContainer &container)
255
0
{
256
0
  seekToRecord(m_input, container, 0);
257
258
0
  const unsigned opts = readU8(m_input, m_bigEndian);
259
260
0
  skip(m_input, 0x35);
261
262
0
  int16_t left = 0;
263
0
  int16_t right = 0;
264
0
  int16_t top = 0;
265
0
  int16_t bottom = 0;
266
  // FIXME: pass both pages' boundaries to collector instead of computed width/height
267
0
  readDims(m_input, m_bigEndian, left, top);
268
0
  readDims(m_input, m_bigEndian, right, bottom);
269
270
0
  m_collector->setDoubleSided(m_bigEndian ? opts & 0x40 : opts & 0x2);
271
0
  m_collector->setPageWidth(right - left);
272
0
  m_collector->setPageHeight(bottom - top);
273
0
}
274
275
void PMDParser::parseLine(const PMDRecordContainer &container, unsigned recordIndex,
276
                          unsigned pageID)
277
0
{
278
0
  seekToRecord(m_input, container, recordIndex);
279
280
0
  PMDStrokeProperties strokeProps;
281
282
0
  skip(m_input, 4);
283
0
  strokeProps.m_strokeColor = readU8(m_input);
284
0
  skip(m_input, 1);
285
0
  PMDShapePoint bboxTopLeft = readPoint(m_input, m_bigEndian);
286
0
  PMDShapePoint bboxBotRight = readPoint(m_input, m_bigEndian);
287
0
  bool mirrored = false;
288
0
  skip(m_input, 0x18);
289
0
  uint16_t temp = readU16(m_input, m_bigEndian);
290
291
0
  if (temp != 257 && temp != 0)
292
0
    mirrored = true;
293
294
0
  skip(m_input, 6);
295
0
  strokeProps.m_strokeType = readU8(m_input);
296
0
  skip(m_input, 1);
297
0
  strokeProps.m_strokeWidth =readU16(m_input, m_bigEndian);
298
0
  skip(m_input, 1);
299
0
  strokeProps.m_strokeTint = readU8(m_input);
300
0
  skip(m_input, 6);
301
0
  strokeProps.m_strokeOverprint = readU8(m_input);
302
303
0
  std::shared_ptr<PMDLineSet> newShape(new PMDLine(bboxTopLeft, bboxBotRight, mirrored, strokeProps));
304
0
  m_collector->addShapeToPage(pageID, newShape);
305
0
}
306
307
void PMDParser::parseTextBox(const PMDRecordContainer &container, unsigned recordIndex,
308
                             unsigned pageID)
309
0
{
310
0
  seekToRecord(m_input, container, recordIndex);
311
312
0
  skip(m_input, 6);
313
0
  PMDShapePoint bboxTopLeft = readPoint(m_input, m_bigEndian);
314
0
  PMDShapePoint bboxBotRight = readPoint(m_input, m_bigEndian);
315
316
0
  uint16_t textBoxTextPropsOne = 0;
317
0
  uint16_t textBoxTextPropsTwo = 0;
318
0
  uint16_t textBoxTextStyle = 0;
319
0
  uint16_t textBoxText = 0;
320
0
  uint16_t textBoxChars = 0;
321
0
  uint16_t textBoxPara = 0;
322
323
0
  skip(m_input, 0xe);
324
0
  uint32_t textBoxXformId = readU32(m_input, m_bigEndian);
325
0
  uint32_t textBoxTextBlockId = readU32(m_input, m_bigEndian);
326
327
0
  const PMDXForm xFormContainer = getXForm(textBoxXformId);
328
329
0
  RecordIterator textBlockIt = beginRecordsOfType(TEXT_BLOCK);
330
0
  if (textBlockIt == endRecords())
331
0
  {
332
0
    PMD_ERR_MSG("No Text Block Record Found.\n");
333
0
  }
334
335
0
  for (; textBlockIt != endRecords(); ++textBlockIt)
336
0
  {
337
0
    const PMDRecordContainer &textBlockContainer = *textBlockIt;
338
339
0
    for (unsigned i = 0; i < textBlockContainer.m_numRecords; ++i)
340
0
    {
341
0
      seekToRecord(m_input, textBlockContainer, i);
342
343
0
      skip(m_input, 0x20);
344
0
      uint32_t textBlockId = readU32(m_input, m_bigEndian);
345
346
0
      if (textBlockId == textBoxTextBlockId)
347
0
      {
348
0
        seekToRecord(m_input, textBlockContainer, i); // return to the beginning of the record
349
0
        textBoxTextPropsOne = readU16(m_input, m_bigEndian);
350
0
        textBoxTextPropsTwo = readU16(m_input, m_bigEndian);
351
0
        textBoxText = readU16(m_input, m_bigEndian);
352
0
        textBoxChars = readU16(m_input, m_bigEndian);
353
0
        textBoxPara = readU16(m_input, m_bigEndian);
354
0
        textBoxTextStyle = readU16(m_input, m_bigEndian);
355
356
0
        (void) textBoxTextPropsOne;
357
0
        (void) textBoxTextPropsTwo;
358
0
        (void) textBoxTextStyle;
359
0
        PMD_DEBUG_MSG(("Text Box Props One is %x \n",textBoxTextPropsOne));
360
0
        PMD_DEBUG_MSG(("Text Box Props Two is %x \n",textBoxTextPropsTwo));
361
0
        PMD_DEBUG_MSG(("Text Box Style is %x \n",textBoxTextStyle));
362
0
        break;
363
0
      }
364
365
0
    }
366
0
  }
367
0
  std::string text = "";
368
369
0
  RecordIterator textIt = beginRecordsWithSeqNumber(textBoxText);
370
0
  if (textIt == endRecords())
371
0
  {
372
0
    PMD_ERR_MSG("No Text Found.\n");
373
0
  }
374
375
0
  for (; textIt != endRecords(); ++textIt)
376
0
  {
377
0
    const PMDRecordContainer &textContainer = *textIt;
378
0
    seekToRecord(m_input, textContainer, 0);
379
0
    for (unsigned i = 0; i < textContainer.m_numRecords; ++i)
380
0
    {
381
0
      text.push_back(readU8(m_input));
382
0
    }
383
0
  }
384
385
0
  std::vector<PMDCharProperties> charProps;
386
0
  for (RecordIterator it = beginRecordsWithSeqNumber(textBoxChars); it != endRecords(); ++it)
387
0
  {
388
0
    const PMDRecordContainer &charsContainer = *it;
389
0
    for (unsigned i = 0; i < charsContainer.m_numRecords; ++i)
390
0
    {
391
0
      seekToRecord(m_input, charsContainer, i);
392
393
0
      charProps.push_back(PMDCharProperties());
394
0
      auto &props = charProps.back();
395
396
0
      props.m_length = readU16(m_input, m_bigEndian);
397
0
      props.m_fontFace = readU16(m_input, m_bigEndian);
398
0
      props.m_fontSize = readU16(m_input, m_bigEndian);
399
0
      skip(m_input, 2);
400
0
      props.m_fontColor = readU16(m_input, m_bigEndian);
401
0
      const unsigned flags = readU16(m_input, m_bigEndian);
402
0
      props.m_bold = flags & 0x1;
403
0
      props.m_italic = flags & 0x2;
404
0
      props.m_underline = flags & 0x4;
405
0
      props.m_outline = flags & 0x8;
406
0
      props.m_shadow = flags & 0x10;
407
0
      props.m_strike = flags & 0x100;
408
0
      props.m_super = flags & 0x200;
409
0
      props.m_sub = flags & 0x400;
410
0
      props.m_allCaps = flags & 0x800;
411
0
      props.m_smallCaps = flags & 0x1000;
412
0
      skip(m_input, 4);
413
0
      props.m_kerning = readS16(m_input, m_bigEndian);
414
0
      skip(m_input, 2);
415
0
      props.m_superSubSize = readU16(m_input, m_bigEndian);
416
0
      props.m_subPos = readU16(m_input, m_bigEndian);
417
0
      props.m_superPos = readU16(m_input, m_bigEndian);
418
0
      skip(m_input, 2);
419
0
      props.m_tint = readU16(m_input, m_bigEndian);
420
0
    }
421
0
  }
422
423
0
  std::vector<PMDParaProperties> paraProps;
424
0
  for (RecordIterator it = beginRecordsWithSeqNumber(textBoxPara); it != endRecords(); ++it)
425
0
  {
426
0
    const PMDRecordContainer &paraContainer = *it;
427
0
    for (unsigned i = 0; i < paraContainer.m_numRecords; ++i)
428
0
    {
429
0
      seekToRecord(m_input, paraContainer, i);
430
431
0
      paraProps.push_back(PMDParaProperties());
432
0
      auto &props = paraProps.back();
433
434
0
      props.m_length = readU16(m_input, m_bigEndian);
435
0
      const unsigned flags = readU8(m_input, m_bigEndian);
436
0
      props.m_hyphenate = flags & 0x8;
437
0
      props.m_align = readU8(m_input);
438
0
      skip(m_input, 6);
439
0
      props.m_leftIndent = readU16(m_input, m_bigEndian);
440
0
      props.m_firstIndent = readU16(m_input, m_bigEndian);
441
0
      props.m_rightIndent = readU16(m_input, m_bigEndian);
442
0
      props.m_beforeIndent = readU16(m_input, m_bigEndian); // Above Para Spacing
443
0
      props.m_afterIndent = readU16(m_input, m_bigEndian); // Below Para Spacing
444
0
      skip(m_input, 18);
445
0
      props.m_hyphensCount = readU8(m_input, m_bigEndian);
446
0
      skip(m_input, 1);
447
0
      const unsigned keepOpts = readU16(m_input, m_bigEndian);
448
0
      props.m_keepTogether = keepOpts & 0x1;
449
0
      props.m_keepWithNext = (keepOpts >> 1) & 0x3;
450
0
      props.m_widows = (keepOpts >> 4) & 0x3;
451
0
      props.m_orphans = (keepOpts >> 7) & 0x3;
452
0
      skip(m_input, 2);
453
0
      props.m_ruleAbove = readRule(m_input, m_bigEndian);
454
0
      props.m_ruleBelow = readRule(m_input, m_bigEndian);
455
0
    }
456
0
  }
457
458
0
  std::shared_ptr<PMDLineSet> newShape(new PMDTextBox(bboxTopLeft, bboxBotRight, xFormContainer, text, charProps, paraProps));
459
0
  m_collector->addShapeToPage(pageID, newShape);
460
461
0
}
462
463
void PMDParser::parseRectangle(const PMDRecordContainer &container, unsigned recordIndex,
464
                               unsigned pageID)
465
0
{
466
0
  seekToRecord(m_input, container, recordIndex);
467
468
0
  PMDFillProperties fillProps;
469
0
  PMDStrokeProperties strokeProps;
470
471
0
  skip(m_input, 2);
472
0
  fillProps.m_fillOverprint = readU8(m_input);
473
0
  skip(m_input, 1);
474
0
  fillProps.m_fillColor = readU8(m_input);
475
0
  skip(m_input, 1);
476
0
  PMDShapePoint bboxTopLeft = readPoint(m_input, m_bigEndian);
477
0
  PMDShapePoint bboxBotRight = readPoint(m_input, m_bigEndian);
478
0
  skip(m_input, 14);
479
0
  uint32_t rectXformId = readU32(m_input, m_bigEndian);
480
481
0
  strokeProps.m_strokeType = readU8(m_input);
482
0
  skip(m_input, 2);
483
0
  strokeProps.m_strokeWidth = readU16(m_input, m_bigEndian);
484
0
  skip(m_input, 1);
485
0
  fillProps.m_fillType = readU8(m_input);
486
0
  skip(m_input, 1);
487
0
  strokeProps.m_strokeColor = readU8(m_input);
488
0
  skip(m_input, 1);
489
0
  strokeProps.m_strokeOverprint = readU8(m_input);
490
0
  skip(m_input, 1);
491
0
  strokeProps.m_strokeTint = readU8(m_input);
492
493
0
  skip(m_input, 0xb3);
494
0
  fillProps.m_fillTint = readU8(m_input);
495
496
0
  const PMDXForm &xFormContainer = getXForm(rectXformId);
497
0
  std::shared_ptr<PMDLineSet> newShape(new PMDRectangle(bboxTopLeft, bboxBotRight, xFormContainer, fillProps, strokeProps));
498
0
  m_collector->addShapeToPage(pageID, newShape);
499
0
}
500
501
void PMDParser::parsePolygon(const PMDRecordContainer &container, unsigned recordIndex,
502
                             unsigned pageID)
503
0
{
504
0
  seekToRecord(m_input, container, recordIndex);
505
506
0
  PMDFillProperties fillProps;
507
0
  PMDStrokeProperties strokeProps;
508
509
0
  skip(m_input, 2);
510
0
  fillProps.m_fillOverprint = readU8(m_input);
511
0
  skip(m_input, 1);
512
0
  fillProps.m_fillColor = readU8(m_input);
513
514
0
  skip(m_input, 1);
515
0
  PMDShapePoint bboxTopLeft = readPoint(m_input, m_bigEndian);
516
0
  PMDShapePoint bboxBotRight = readPoint(m_input, m_bigEndian);
517
518
0
  skip(m_input, 14);
519
0
  uint32_t polyXformId = readU32(m_input, m_bigEndian);
520
521
0
  strokeProps.m_strokeType = readU8(m_input);
522
0
  skip(m_input, 2);
523
0
  strokeProps.m_strokeWidth = readU16(m_input, m_bigEndian);
524
0
  skip(m_input, 1);
525
0
  fillProps.m_fillType = readU8(m_input);
526
0
  skip(m_input, 1);
527
0
  strokeProps.m_strokeColor = readU8(m_input);
528
0
  skip(m_input, 1);
529
0
  strokeProps.m_strokeOverprint = readU8(m_input);
530
0
  skip(m_input, 1);
531
0
  strokeProps.m_strokeTint = readU8(m_input);
532
533
0
  skip(m_input, 1);
534
0
  uint16_t lineSetSeqNum = readU16(m_input, m_bigEndian);
535
0
  skip(m_input, 8);
536
0
  uint8_t closedMarker = readU8(m_input);
537
0
  skip(m_input, 0xa7);
538
0
  fillProps.m_fillTint = readU8(m_input);
539
540
0
  bool closed;
541
0
  switch (closedMarker)
542
0
  {
543
0
  default:
544
0
    PMD_ERR_MSG("Unknown value for polygon closed/open marker. Defaulting to closed.\n");
545
  // Intentional fall-through.
546
0
  case POLYGON_CLOSED:
547
0
    closed = true;
548
0
    break;
549
0
  case POLYGON_OPEN:
550
0
    closed = false;
551
0
    break;
552
0
  case REGULAR_POLYGON:
553
0
    closed = true;
554
0
    break;
555
0
  }
556
557
0
  std::vector<PMDShapePoint> points;
558
0
  for (RecordIterator it = beginRecordsWithSeqNumber(lineSetSeqNum); it != endRecords(); ++it)
559
0
  {
560
0
    const PMDRecordContainer &lineSetContainer = *it;
561
0
    for (unsigned i = 0; i < lineSetContainer.m_numRecords; ++i)
562
0
    {
563
0
      seekToRecord(m_input, lineSetContainer, i);
564
0
      points.push_back(readPoint(m_input, m_bigEndian));
565
0
    }
566
0
  }
567
568
0
  const PMDXForm &xFormContainer = getXForm(polyXformId);
569
0
  std::shared_ptr<PMDLineSet> newShape(new PMDPolygon(points, closed, bboxTopLeft, bboxBotRight, xFormContainer, fillProps, strokeProps));
570
0
  m_collector->addShapeToPage(pageID, newShape);
571
0
}
572
573
void PMDParser::parseEllipse(const PMDRecordContainer &container, unsigned recordIndex, unsigned pageID)
574
0
{
575
0
  seekToRecord(m_input, container, recordIndex);
576
577
0
  PMDFillProperties fillProps;
578
0
  PMDStrokeProperties strokeProps;
579
580
0
  skip(m_input, 2);
581
0
  fillProps.m_fillOverprint = readU8(m_input);
582
0
  skip(m_input, 1);
583
0
  fillProps.m_fillColor = readU8(m_input);
584
585
0
  skip(m_input, 1);
586
0
  PMDShapePoint bboxTopLeft = readPoint(m_input, m_bigEndian);
587
0
  PMDShapePoint bboxBotRight = readPoint(m_input, m_bigEndian);
588
589
0
  skip(m_input, 14);
590
0
  uint32_t ellipseXformId = readU32(m_input, m_bigEndian);
591
592
0
  strokeProps.m_strokeType = readU8(m_input);
593
0
  skip(m_input, 2);
594
0
  strokeProps.m_strokeWidth = readU16(m_input, m_bigEndian);
595
0
  skip(m_input, 1);
596
0
  fillProps.m_fillType = readU8(m_input);
597
0
  skip(m_input, 1);
598
0
  strokeProps.m_strokeColor = readU8(m_input);
599
0
  skip(m_input, 1);
600
0
  strokeProps.m_strokeOverprint = readU8(m_input);
601
0
  skip(m_input, 1);
602
0
  strokeProps.m_strokeTint = readU8(m_input);
603
604
0
  skip(m_input, 0xb3);
605
0
  fillProps.m_fillTint = readU8(m_input);
606
607
0
  const PMDXForm &xFormContainer = getXForm(ellipseXformId);
608
0
  std::shared_ptr<PMDLineSet> newShape(new PMDEllipse(bboxTopLeft, bboxBotRight, xFormContainer, fillProps, strokeProps));
609
0
  m_collector->addShapeToPage(pageID, newShape);
610
0
}
611
612
void PMDParser::parseBitmap(const PMDRecordContainer &container, unsigned recordIndex, unsigned pageID)
613
0
{
614
0
  librevenge::RVNGBinaryData bitmap;
615
0
  seekToRecord(m_input, container, recordIndex);
616
617
0
  skip(m_input, 6);
618
0
  PMDShapePoint bboxTopLeft = readPoint(m_input, m_bigEndian);
619
0
  PMDShapePoint bboxBotRight = readPoint(m_input, m_bigEndian);
620
0
  skip(m_input, 14);
621
0
  uint32_t bboxXformId = readU32(m_input, m_bigEndian);
622
623
0
  skip(m_input, 16);
624
0
  uint16_t bitmapRecordSeqNum = readU16(m_input, m_bigEndian);
625
626
0
  const PMDXForm &xFormContainer = getXForm(bboxXformId);
627
628
0
  RecordIterator tiffIt = beginRecordsWithSeqNumber(bitmapRecordSeqNum);
629
0
  if (tiffIt == endRecords())
630
0
  {
631
0
    throw RecordNotFoundException(TIFF, bitmapRecordSeqNum);
632
0
  }
633
634
0
  for (; tiffIt != endRecords(); ++tiffIt)
635
0
  {
636
0
    const PMDRecordContainer &tiffContainer = *tiffIt;
637
0
    seekToRecord(m_input, tiffContainer, 0);
638
0
    const unsigned char *const tempBytes = readNBytes(m_input,tiffContainer.m_numRecords);
639
0
    bitmap.append(tempBytes,tiffContainer.m_numRecords);
640
0
  }
641
642
0
  tiffIt = beginRecordsWithSeqNumber(bitmapRecordSeqNum + 1);
643
0
  if (tiffIt == endRecords())
644
0
  {
645
0
    throw RecordNotFoundException(TIFF, bitmapRecordSeqNum);
646
0
  }
647
0
  for (; tiffIt != endRecords(); ++tiffIt)
648
0
  {
649
0
    const PMDRecordContainer &tiffSecondContainer = *tiffIt;
650
0
    seekToRecord(m_input, tiffSecondContainer, 0);
651
0
    const unsigned char *const tempBytes = readNBytes(m_input,tiffSecondContainer.m_numRecords);
652
0
    bitmap.append(tempBytes,tiffSecondContainer.m_numRecords);
653
0
  }
654
655
656
0
  std::shared_ptr<PMDLineSet> newShape(new PMDBitmap(bboxTopLeft, bboxBotRight, xFormContainer, bitmap));
657
0
  m_collector->addShapeToPage(pageID, newShape);
658
659
0
}
660
661
void PMDParser::parseShapes(uint16_t seqNum, unsigned pageID)
662
0
{
663
0
  for (RecordIterator it = beginRecordsWithSeqNumber(seqNum); it != endRecords(); ++it)
664
0
  {
665
0
    const PMDRecordContainer &container = *it;
666
667
0
    for (unsigned i = 0; i < container.m_numRecords; ++i)
668
0
    {
669
0
      seekToRecord(m_input, container, i);
670
671
0
      uint8_t shapeType = readU8(m_input);
672
0
      switch (shapeType)
673
0
      {
674
0
      case LINE_RECORD:
675
0
        parseLine(container, i, pageID);
676
0
        break;
677
0
      case RECTANGLE_RECORD:
678
0
        parseRectangle(container, i, pageID);
679
0
        break;
680
0
      case POLYGON_RECORD:
681
0
        parsePolygon(container, i, pageID);
682
0
        break;
683
0
      case ELLIPSE_RECORD:
684
0
        parseEllipse(container, i, pageID);
685
0
        break;
686
0
      case TEXT_RECORD:
687
0
        parseTextBox(container, i, pageID);
688
0
        break;
689
0
      case BITMAP_RECORD:
690
0
      case METAFILE_RECORD:
691
0
        parseBitmap(container, i, pageID);
692
0
        break;
693
0
      default:
694
0
        PMD_ERR_MSG("Encountered shape of unknown type.\n");
695
0
        continue;
696
0
      }
697
0
    }
698
0
  }
699
0
}
700
701
void PMDParser::parseFonts()
702
3
{
703
3
  RecordIterator it = beginRecordsOfType(FONTS);
704
705
3
  if (it != endRecords())
706
0
  {
707
0
    PMD_ERR_MSG("No Font Record Found.\n");
708
0
  }
709
710
3
  uint16_t fontIndex = 0;
711
3
  for (; it != endRecords(); ++it)
712
0
  {
713
0
    const PMDRecordContainer &container = *it;
714
715
0
    for (unsigned i = 0; i < container.m_numRecords; ++i)
716
0
    {
717
0
      seekToRecord(m_input, container, i);
718
719
0
      std::string fontName;
720
721
0
      uint8_t temp = readU8(m_input);
722
723
0
      while (temp)
724
0
      {
725
0
        fontName.push_back(temp);
726
0
        temp = readU8(m_input);
727
0
      }
728
0
      m_collector->addFont(PMDFont(fontIndex, fontName));
729
0
      fontIndex++;
730
0
    }
731
0
  }
732
3
}
733
734
void PMDParser::parseColors()
735
3
{
736
3
  RecordIterator it = beginRecordsOfType(COLORS);
737
738
3
  if (it != endRecords())
739
0
  {
740
0
    PMD_ERR_MSG("No Color Record Found.\n");
741
0
  }
742
743
3
  for (; it != endRecords(); ++it)
744
0
  {
745
0
    const PMDRecordContainer &container = *it;
746
747
0
    for (unsigned i = 0; i < container.m_numRecords; ++i)
748
0
    {
749
0
      seekToRecord(m_input, container, i);
750
0
      skip(m_input, 0x22);
751
752
0
      uint8_t colorModel = readU8(m_input);
753
0
      uint8_t red = 0;
754
0
      uint8_t blue = 0;
755
0
      uint8_t green = 0;
756
757
0
      skip(m_input, 3);
758
0
      if (colorModel == RGB)
759
0
      {
760
0
        red = readU8(m_input);
761
0
        green = readU8(m_input);
762
0
        blue = readU8(m_input);
763
0
      }
764
0
      else if (colorModel == CMYK || colorModel == HLS) // HLS is also stroed in CMYK format
765
0
      {
766
0
        uint16_t cyan = readU16(m_input, m_bigEndian);
767
0
        uint16_t magenta = readU16(m_input, m_bigEndian);
768
0
        uint16_t yellow = readU16(m_input, m_bigEndian);
769
0
        uint16_t black = readU16(m_input, m_bigEndian);
770
771
0
        uint16_t max = (std::numeric_limits<uint16_t>::max)();
772
773
0
        red = 255*(1 - std::min(1.0, (double)cyan/max + (double)black/max));
774
0
        green = 255*(1 - std::min(1.0, (double)magenta/max + (double)black/max));
775
0
        blue = 255*(1 - std::min(1.0, (double)yellow/max + (double)black/max));
776
0
      }
777
778
0
      m_collector->addColor(PMDColor(i, red, green, blue));
779
0
    }
780
0
  }
781
3
}
782
783
void PMDParser::parseXforms()
784
3
{
785
3
  RecordIterator it = beginRecordsOfType(XFORM);
786
787
12
  for (; it != endRecords(); ++it)
788
9
  {
789
9
    const PMDRecordContainer &xformContainer = *it;
790
791
172k
    for (unsigned i = 0; i < xformContainer.m_numRecords; ++i)
792
172k
    {
793
794
172k
      seekToRecord(m_input, xformContainer, i);
795
796
172k
      uint32_t rotationDegree = readU32(m_input, m_bigEndian);
797
172k
      uint32_t skewDegree = readU32(m_input, m_bigEndian);
798
172k
      skip(m_input, 2);
799
172k
      PMDShapePoint xformTopLeft = readPoint(m_input, m_bigEndian);
800
172k
      PMDShapePoint xformBotRight = readPoint(m_input, m_bigEndian);
801
172k
      PMDShapePoint rotatingPoint = readPoint(m_input, m_bigEndian);
802
172k
      uint32_t xformId = readU32(m_input, m_bigEndian);
803
804
172k
      m_xFormMap.insert(std::pair<uint32_t, PMDXForm>(xformId,PMDXForm(rotationDegree,skewDegree,xformTopLeft,xformBotRight,rotatingPoint,xformId)));
805
172k
    }
806
9
  }
807
3
  m_xFormMap.insert(std::pair<uint32_t,PMDXForm>(0,PMDXForm(0,0,PMDShapePoint(0,0),PMDShapePoint(0,0),PMDShapePoint(0,0),0))); //Default XForm
808
3
}
809
810
811
void PMDParser::parsePages(const PMDRecordContainer &container)
812
0
{
813
0
  seekToRecord(m_input, container, 0);
814
815
0
  skip(m_input, 8);
816
0
  uint16_t pageWidth = readU16(m_input, m_bigEndian);
817
0
  (void) pageWidth;
818
819
  // if (pageWidth)
820
  // m_collector->setPageWidth(pageWidth);
821
822
0
  for (unsigned i = 0; i < container.m_numRecords; ++i)
823
0
  {
824
0
    seekToRecord(m_input, container, i);
825
826
0
    skip(m_input, 2);
827
0
    uint16_t shapesSeqNum = readU16(m_input, m_bigEndian);
828
0
    unsigned pageID = m_collector->addPage();
829
0
    parseShapes(shapesSeqNum, pageID);
830
0
  }
831
0
}
832
833
void PMDParser::parseHeader(uint32_t *tocOffset, uint16_t *tocLength)
834
3
{
835
3
  PMD_DEBUG_MSG(("[Header] Parsing header...\n"));
836
3
  seek(m_input, ENDIANNESS_MARKER_OFFSET);
837
3
  uint16_t endiannessMarker = readU16(m_input, false);
838
3
  if (endiannessMarker == ENDIANNESS_MARKER)
839
0
  {
840
0
    PMD_DEBUG_MSG(("[Header] File is little-endian.\n"));
841
0
    m_bigEndian = false;
842
0
  }
843
3
  else if (endiannessMarker == WARPED_ENDIANNESS_MARKER)
844
3
  {
845
3
    PMD_DEBUG_MSG(("[Header] File is big-endian.\n"));
846
3
    m_bigEndian = true;
847
3
  }
848
0
  else
849
0
  {
850
0
    throw PMDParseException("Endianness marker is corrupt in PMD header.");
851
0
  }
852
3
  try
853
3
  {
854
3
    seek(m_input, TABLE_OF_CONTENTS_LENGTH_OFFSET);
855
3
    *tocLength = readU16(m_input, m_bigEndian);
856
3
    PMD_DEBUG_MSG(("[Header] TOC length is %d\n", *tocLength));
857
3
  }
858
3
  catch (const PMDStreamException &)
859
3
  {
860
0
    throw PMDParseException("Can't find the table of contents length in the header.");
861
0
  }
862
3
  try
863
3
  {
864
3
    seek(m_input, TABLE_OF_CONTENTS_OFFSET_OFFSET);
865
3
    *tocOffset = readU32(m_input, m_bigEndian);
866
3
    PMD_DEBUG_MSG(("[Header] TOC offset is 0x%x\n", *tocOffset));
867
3
  }
868
3
  catch (const PMDStreamException &)
869
3
  {
870
0
    throw PMDParseException("Can't find the table of contents offset in the header.");
871
0
  }
872
3
}
873
874
void PMDParser::readNextRecordFromTableOfContents(ToCState &state, const bool subRecord, const uint16_t subRecordType)
875
39
{
876
39
  skip(m_input, 1);
877
39
  uint16_t recType = readU8(m_input);
878
39
  uint16_t numRecs = readU16(m_input, m_bigEndian);
879
39
  uint32_t offset = readU32(m_input, m_bigEndian);
880
39
  skip(m_input, 2);
881
882
39
  uint16_t subType = 0;
883
884
39
  if (!subRecord && (recType != 0 || numRecs == 0))
885
30
  {
886
30
    skip(m_input, 1);
887
30
    subType = readU8(m_input);
888
30
    if (subType == 0)
889
6
    {
890
6
      PMD_DEBUG_MSG(("[TOC] invalid subrecord type\n"));
891
6
    }
892
30
    skip(m_input, 4);
893
30
  }
894
895
39
  if (recType == 0 && numRecs == 0)
896
6
  {
897
    // empty record
898
6
    ++state.seqNum;
899
6
  }
900
33
  else if (!subRecord && recType == 1)
901
0
  {
902
0
    readTableOfContents(state, offset, numRecs, true, subType);
903
0
    ++state.seqNum;
904
0
  }
905
33
  else if (!subRecord && recType == 0)
906
9
  {
907
9
    readTableOfContents(state, offset, numRecs, false);
908
9
  }
909
24
  else
910
24
  {
911
24
    if (numRecs != 0 && offset != 0)
912
24
    {
913
24
      if (subRecord && recType != subRecordType)
914
0
      {
915
0
        PMD_DEBUG_MSG(("[TOC] subrecord type mismatch: expected %hu, got %hu.\n", subRecordType, recType));
916
0
        if (subRecordType != 0) // can only happen in a broken file -- better ignore
917
0
          recType = subRecordType;
918
0
      }
919
24
      m_recordsInOrder.push_back(PMDRecordContainer(recType, offset, state.seqNum, numRecs));
920
24
      m_records[recType].push_back((unsigned)(m_recordsInOrder.size() - 1));
921
24
    }
922
24
    if (!subRecord)
923
24
      ++state.seqNum;
924
24
  }
925
39
}
926
927
void PMDParser::readTableOfContents(ToCState &state, const uint32_t offset, unsigned records, const bool subRecords, const uint16_t subRecordType)
928
12
{
929
12
  if (state.parsedBlocks.end() != state.parsedBlocks.find(m_input->tell()))
930
3
  {
931
3
    PMD_DEBUG_MSG(("[TOC] ToC block at offset %ld has already been read. The file is probably broken. Skipping...\n", m_input->tell()));
932
3
    return;
933
3
  }
934
935
9
  state.parsedBlocks.insert(m_input->tell());
936
937
9
  if (records == 0 || offset == 0)
938
0
  {
939
0
    PMD_DEBUG_MSG(("[TOC] no records to read\n"));
940
0
    return;
941
0
  }
942
943
9
  const long orig = m_input->tell();
944
945
9
  PMD_DEBUG_MSG(("[TOC] reading %sblock at offset 0x%x\n", subRecords ? "subrecord " : "", offset));
946
9
  seek(m_input, offset);
947
9
  PMD_DEBUG_MSG(("[TOC] records to read: %d\n", records));
948
9
  const size_t minRecordSize = subRecords ? 10 : 16;
949
9
  const size_t maxPossibleRecords = (m_length-offset)/minRecordSize;
950
48
  for (unsigned i = 0; i < std::min<size_t>(records, maxPossibleRecords); ++i)
951
39
    readNextRecordFromTableOfContents(state, subRecords, subRecordType);
952
953
9
  seek(m_input, orig);
954
9
}
955
956
6
void PMDParser::parseTableOfContents(uint32_t offset, uint16_t length) try
957
6
{
958
6
  ToCState state;
959
6
  readTableOfContents(state, offset, length, false);
960
6
}
961
6
catch (...)
962
6
{
963
3
  PMD_ERR_MSG("Error reading the table of contents! Some or all records will be missing.\n");
964
3
}
965
966
void PMDParser::parse()
967
3
{
968
3
  uint32_t tocOffset;
969
3
  uint16_t tocLength;
970
3
  parseHeader(&tocOffset, &tocLength);
971
3
  parseTableOfContents(tocOffset, tocLength);
972
3
  parseFonts();
973
3
  parseColors();
974
3
  parseXforms();
975
976
3
  auto i = m_records.find(GLOBAL_INFO);
977
3
  if (i != m_records.end()
978
0
      && !(i->second.empty()))
979
0
  {
980
0
    parseGlobalInfo(m_recordsInOrder[i->second[0]]);
981
0
  }
982
3
  else
983
3
  {
984
3
    throw RecordNotFoundException(GLOBAL_INFO);
985
3
  }
986
987
0
  i = m_records.find(PAGE);
988
0
  if (i != m_records.end()
989
0
      && !(i->second.empty()))
990
0
  {
991
0
    parsePages(m_recordsInOrder[i->second[0]]);
992
0
  }
993
0
  else
994
0
  {
995
0
    throw RecordNotFoundException(PAGE);
996
0
  }
997
0
}
998
999
PMDParser::RecordIterator PMDParser::beginRecordsWithSeqNumber(const uint16_t seqNum) const
1000
0
{
1001
0
  return RecordIterator(m_recordsInOrder, seqNum);
1002
0
}
1003
1004
PMDParser::RecordIterator PMDParser::beginRecordsOfType(const uint16_t recType) const
1005
9
{
1006
9
  return RecordIterator(m_recordsInOrder, m_records, recType);
1007
9
}
1008
1009
PMDParser::RecordIterator PMDParser::endRecords() const
1010
21
{
1011
21
  return RecordIterator(m_recordsInOrder);
1012
21
}
1013
1014
}
1015
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */