Coverage Report

Created: 2026-07-20 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libe-book/src/lib/SoftBookParser.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 libe-book 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 <cassert>
11
#include <deque>
12
13
#include <librevenge-stream/librevenge-stream.h>
14
15
#include "libebook_utils.h"
16
#include "EBOOKStreamView.h"
17
#include "SoftBookLZSSStream.h"
18
#include "SoftBookParser.h"
19
#include "SoftBookResourceDir.h"
20
#include "SoftBookText.h"
21
22
using std::shared_ptr;
23
24
namespace libebook
25
{
26
27
namespace
28
{
29
30
static const char *const SOFTBOOK_TEXT_FILE_NAME = "    ";
31
32
}
33
34
namespace
35
{
36
37
struct SoftBookParserException {};
38
39
#ifdef UNUSED
40
void skipStandardHeader(std::shared_ptr<librevenge::RVNGInputStream> stream)
41
{
42
  assert(bool(stream));
43
  skip(stream.get(), 32);
44
}
45
#endif
46
47
SoftBookLZSSStream::Configuration makeLZSSConfiguration(const unsigned offsetBits, const unsigned lengthBits, const unsigned uncompressedLength)
48
0
{
49
0
  SoftBookLZSSStream::Configuration configuration;
50
51
0
  configuration.offsetBits = offsetBits;
52
0
  configuration.lengthBits = lengthBits;
53
0
  configuration.uncompressedLength = uncompressedLength;
54
0
  configuration.fillPos = 1;
55
0
  configuration.allowOverflow = false;
56
0
  configuration.bigEndian = true;
57
58
0
  return configuration;
59
0
}
60
61
}
62
63
namespace
64
{
65
66
class Resource
67
{
68
  struct ResourceIndexEntry
69
  {
70
    ResourceIndexEntry();
71
72
    unsigned id;
73
    unsigned length;
74
    unsigned offset;
75
  };
76
77
  typedef std::deque<ResourceIndexEntry> ResourceIndex_t;
78
79
public:
80
  enum IndexMode
81
  {
82
    INDEX_MODE_NORMAL,
83
    INDEX_MODE_WORKAROUND
84
  };
85
86
public:
87
  Resource(const char *name, shared_ptr<librevenge::RVNGInputStream> stream, const SoftBookHeader &header);
88
89
  unsigned getRecordCount() const;
90
91
  unsigned getRecordId(unsigned n) const;
92
93
  shared_ptr<librevenge::RVNGInputStream> getRecord(unsigned n) const;
94
95
private:
96
  void readResourceIndex(IndexMode mode);
97
98
private:
99
  const shared_ptr<librevenge::RVNGInputStream> m_stream;
100
  long m_start;
101
  ResourceIndex_t m_resourceIndex;
102
};
103
104
Resource::ResourceIndexEntry::ResourceIndexEntry()
105
0
  : id(0)
106
0
  , length(0)
107
0
  , offset(0)
108
0
{
109
0
}
110
111
Resource::Resource(const char *const name, const shared_ptr<librevenge::RVNGInputStream> stream, const SoftBookHeader &header)
112
0
  : m_stream(stream)
113
0
  , m_start(stream->tell())
114
0
  , m_resourceIndex()
115
0
{
116
0
  const unsigned version = readU16(stream, true);
117
0
  if (1 != version)
118
0
  {
119
0
    EBOOK_DEBUG_MSG(("version of resource %s is not 1\n", name));
120
0
  }
121
122
0
  const char *const type = reinterpret_cast<const char *>(readNBytes(stream, 4));
123
0
  if (!std::equal(name, name + 4, type))
124
0
  {
125
0
    EBOOK_DEBUG_MSG(("type of resource does not match: %s expected, %s got\n", name, type));
126
0
  }
127
128
0
  readResourceIndex((SOFTBOOK_COLOR_MODE_GRAYSCALE == header.getColorMode()) ? INDEX_MODE_WORKAROUND : INDEX_MODE_NORMAL);
129
0
}
130
131
unsigned Resource::getRecordCount() const
132
0
{
133
0
  return (unsigned) m_resourceIndex.size();
134
0
}
135
136
unsigned Resource::getRecordId(const unsigned n) const
137
0
{
138
0
  return (n < m_resourceIndex.size()) ? m_resourceIndex[n].id : 0;
139
0
}
140
141
shared_ptr<librevenge::RVNGInputStream> Resource::getRecord(const unsigned n) const
142
0
{
143
0
  shared_ptr<librevenge::RVNGInputStream> record;
144
145
0
  if (n < m_resourceIndex.size())
146
0
  {
147
0
    const ResourceIndexEntry &entry = m_resourceIndex[n];
148
0
    const long start = m_start + (long) entry.offset;
149
0
    record.reset(new EBOOKStreamView(m_stream.get(), start, start + (long) entry.length));
150
0
  }
151
152
0
  return record;
153
0
}
154
155
void Resource::readResourceIndex(const IndexMode mode)
156
0
{
157
0
  m_stream->seek(m_start + 0xa, librevenge::RVNG_SEEK_SET);
158
0
  const unsigned offset = readU32(m_stream, true);
159
0
  m_stream->seek(m_start + (long) offset, librevenge::RVNG_SEEK_SET);
160
161
0
  while (!m_stream->isEnd())
162
0
  {
163
0
    ResourceIndexEntry entry;
164
0
    if (INDEX_MODE_NORMAL == mode)
165
0
    {
166
0
      entry.id = readU16(m_stream, true);
167
0
      entry.length = readU32(m_stream, true);
168
0
      entry.offset = readU32(m_stream, true);
169
0
    }
170
0
    else
171
0
    {
172
      // this is really LITTLE endian
173
0
      entry.id = readU32(m_stream);
174
0
      entry.length = readU32(m_stream);
175
0
      entry.offset = readU32(m_stream);
176
0
    }
177
0
    skip(m_stream, 2);
178
0
    m_resourceIndex.push_front(entry);
179
0
  }
180
0
}
181
182
}
183
184
SoftBookParser::SoftBookParser(librevenge::RVNGInputStream *const input, librevenge::RVNGTextInterface *const document)
185
975
  : m_header(input)
186
975
  , m_input(input)
187
975
  , m_collector(document)
188
975
  , m_resources()
189
975
  , m_text()
190
975
{
191
975
}
192
193
bool SoftBookParser::parse()
194
975
{
195
975
  SoftBookResourceDir resourceDir(m_input, m_header);
196
975
  m_resources = resourceDir.getTypeStream();
197
198
975
  createTextStream();
199
200
975
  SoftBookText text(m_text.get(), &m_collector);
201
975
  return text.parse();
202
975
}
203
204
void SoftBookParser::createTextStream()
205
245
{
206
245
  assert(bool(m_resources));
207
208
245
  const shared_ptr<librevenge::RVNGInputStream> textStream = getFileStream(SOFTBOOK_TEXT_FILE_NAME);
209
210
245
  if (m_header.getCompressed())
211
1
  {
212
1
    const shared_ptr<librevenge::RVNGInputStream> compressionInfo = getFileStream("!!cm");
213
1
    const Resource cm("!!cm", compressionInfo, m_header);
214
1
    unsigned windowSize = 14;
215
1
    unsigned lookAheadSize = 3;
216
1
    unsigned textLength = 0;
217
218
1
    for (unsigned i = 0; i != cm.getRecordCount(); ++i)
219
0
    {
220
0
      const unsigned id = cm.getRecordId(i);
221
0
      if (0x64 == id)
222
0
      {
223
0
        const shared_ptr<librevenge::RVNGInputStream> record = cm.getRecord(i);
224
0
        skip(record, 6);
225
0
        windowSize = readU16(record, true);
226
0
        lookAheadSize = readU16(record, true);
227
0
        break;
228
0
      }
229
0
      else if (0x65 == id)
230
0
      {
231
0
        const shared_ptr<librevenge::RVNGInputStream> record = cm.getRecord(i);
232
0
        seek(record, getRemainingLength(record) - 10);
233
0
        textLength = readU32(record, true);
234
0
      }
235
0
      else
236
0
      {
237
0
        EBOOK_DEBUG_MSG(("unknown resource ID %x\n", id));
238
0
      }
239
0
    }
240
241
1
    m_text.reset(new SoftBookLZSSStream(textStream.get(), makeLZSSConfiguration(windowSize, lookAheadSize, textLength)));
242
1
  }
243
244
  else
244
244
    m_text = textStream;
245
245
}
246
247
shared_ptr<librevenge::RVNGInputStream> SoftBookParser::getFileStream(const char *const name) const
248
246
{
249
246
  const shared_ptr<librevenge::RVNGInputStream> stream(m_resources->getSubStreamByName(name));
250
246
  if (!stream)
251
72
  {
252
72
    EBOOK_DEBUG_MSG(("resource %s not found\n", name));
253
72
    throw SoftBookParserException();
254
72
  }
255
256
  // skip file header
257
  // TODO: check that the resource type matches name
258
174
  skip(stream, 20);
259
260
174
  return stream;
261
246
}
262
263
}
264
265
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */