Coverage Report

Created: 2026-09-06 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libmspub/src/lib/MSPUBParser97.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 libmspub 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 "MSPUBParser97.h"
11
12
#include <algorithm>
13
#include <limits>
14
#include <map>
15
#include <memory>
16
17
#include "MSPUBCollector.h"
18
#include "MSPUBTypes.h"
19
#include "libmspub_utils.h"
20
21
namespace libmspub
22
{
23
24
MSPUBParser97::MSPUBParser97(librevenge::RVNGInputStream *input, MSPUBCollector *collector)
25
4.04k
  : MSPUBParser2k(input, collector), m_isBanner(false)
26
4.04k
{
27
4.04k
  m_collector->useEncodingHeuristic();
28
4.04k
}
29
30
unsigned short MSPUBParser97::getTextMarker() const
31
22.5k
{
32
22.5k
  return 0x0000;
33
22.5k
}
34
35
unsigned MSPUBParser97::getTextIdOffset() const
36
17.5k
{
37
17.5k
  return 0x46;
38
17.5k
}
39
40
bool MSPUBParser97::parse()
41
4.04k
{
42
4.04k
  std::unique_ptr<librevenge::RVNGInputStream> contents(m_input->getSubStreamByName("Contents"));
43
4.04k
  if (!contents)
44
0
  {
45
0
    MSPUB_DEBUG_MSG(("Couldn't get contents stream.\n"));
46
0
    return false;
47
0
  }
48
4.04k
  if (!parseContents(contents.get()))
49
49
  {
50
49
    MSPUB_DEBUG_MSG(("Couldn't parse contents stream.\n"));
51
49
    return false;
52
49
  }
53
3.99k
  return m_collector->go();
54
4.04k
}
55
56
bool MSPUBParser97::parseDocument(librevenge::RVNGInputStream *input)
57
3.09k
{
58
3.09k
  if (bool(m_documentChunkIndex))
59
3.04k
  {
60
3.04k
    input->seek(m_contentChunks[m_documentChunkIndex.get()].offset + 0x12, librevenge::RVNG_SEEK_SET);
61
3.04k
    unsigned short coordinateSystemMark = readU16(input);
62
3.04k
    m_isBanner = coordinateSystemMark == 0x0007;
63
3.04k
    unsigned width = readU32(input);
64
3.04k
    unsigned height = readU32(input);
65
3.04k
    m_collector->setWidthInEmu(width);
66
3.04k
    m_collector->setHeightInEmu(height);
67
3.04k
    return true;
68
3.04k
  }
69
49
  return false;
70
3.09k
}
71
72
void MSPUBParser97::parseContentsTextIfNecessary(librevenge::RVNGInputStream *input)
73
4.04k
{
74
4.04k
  input->seek(0x12, librevenge::RVNG_SEEK_SET);
75
4.04k
  input->seek(readU32(input), librevenge::RVNG_SEEK_SET);
76
4.04k
  input->seek(14, librevenge::RVNG_SEEK_CUR);
77
4.04k
  unsigned textStart = readU32(input);
78
4.04k
  unsigned textEnd = readU32(input);
79
4.04k
  unsigned prop1Index = readU16(input);
80
4.04k
  unsigned prop2Index = readU16(input);
81
4.04k
  unsigned prop3Index = readU16(input);
82
4.04k
  unsigned prop3End = readU16(input);
83
4.04k
  std::vector<SpanInfo97> spanInfos = getSpansInfo(input, prop1Index,
84
4.04k
                                                   prop2Index, prop3Index, prop3End);
85
4.04k
  input->seek(textStart, librevenge::RVNG_SEEK_SET);
86
4.04k
  TextInfo97 textInfo = getTextInfo(input, textEnd - textStart);
87
4.04k
  unsigned iParaEnd = 0, iSpanEnd = 0;
88
4.04k
  unsigned currentParaIndex = 0;
89
4.04k
  unsigned currentSpanIndex = 0;
90
540k
  for (size_t iShapeEnd = 0; iShapeEnd < textInfo.m_shapeEnds.size(); ++iShapeEnd)
91
536k
  {
92
536k
    unsigned shapeEnd = std::min<unsigned>(textInfo.m_shapeEnds[iShapeEnd], textInfo.m_chars.size());
93
536k
    std::vector<TextParagraph> shapeParas;
94
1.11M
    while (currentParaIndex < shapeEnd)
95
574k
    {
96
574k
      unsigned paraEnd = iParaEnd < textInfo.m_paragraphEnds.size() ?
97
522k
                         textInfo.m_paragraphEnds[iParaEnd++] : shapeEnd;
98
574k
      if (paraEnd > shapeEnd)
99
484k
      {
100
484k
        --iParaEnd;
101
484k
        paraEnd = shapeEnd;
102
484k
      }
103
574k
      std::vector<TextSpan> paraSpans;
104
5.19M
      while (currentSpanIndex < paraEnd)
105
4.62M
      {
106
4.62M
        const SpanInfo97 &spanInfo = iSpanEnd < spanInfos.size() ?
107
4.13M
                                     spanInfos[iSpanEnd++] :
108
4.62M
                                     SpanInfo97(paraEnd, CharacterStyle());
109
4.62M
        unsigned spanEnd = spanInfo.m_spanEnd;
110
4.62M
        if (spanEnd > paraEnd)
111
84.8k
        {
112
84.8k
          --iSpanEnd;
113
84.8k
          spanEnd = paraEnd;
114
84.8k
        }
115
4.62M
        const CharacterStyle &spanStyle = spanInfo.m_style;
116
4.62M
        std::vector<unsigned char> spanChars;
117
4.62M
        spanChars.reserve(std::min(spanEnd - currentSpanIndex, m_length));
118
268M
        for (unsigned i = currentSpanIndex; i < spanEnd; ++i)
119
263M
        {
120
263M
          unsigned char ch = textInfo.m_chars[i];
121
263M
          if (ch == 0xB) // Pub97 interprets vertical tab as nonbreaking space.
122
295k
          {
123
295k
            spanChars.push_back('\n');
124
295k
          }
125
263M
          else if (ch == 0x0D)
126
1.07M
          {
127
1.07M
            if (i + 1 < spanEnd && textInfo.m_chars[i + 1] == 0x0A)
128
204k
            {
129
204k
              ++i; // ignore the 0x0D and advance past the 0x0A
130
204k
            }
131
1.07M
          }
132
262M
          else if (ch == 0x0C)
133
1.20M
          {
134
            // ignore the 0x0C
135
1.20M
          }
136
261M
          else
137
261M
          {
138
261M
            spanChars.push_back(ch);
139
261M
          }
140
263M
        }
141
4.62M
        paraSpans.push_back(TextSpan(spanChars, spanStyle));
142
4.62M
        currentSpanIndex = spanEnd;
143
4.62M
      }
144
574k
      shapeParas.push_back(TextParagraph(paraSpans, ParagraphStyle()));
145
574k
      currentParaIndex = paraEnd;
146
574k
    }
147
536k
    m_collector->addTextString(shapeParas, iShapeEnd);
148
536k
  }
149
4.04k
}
150
151
std::vector<MSPUBParser97::SpanInfo97> MSPUBParser97::getSpansInfo(
152
  librevenge::RVNGInputStream *input,
153
  unsigned prop1Index, unsigned prop2Index, unsigned /* prop3Index */,
154
  unsigned /* prop3End */)
155
3.95k
{
156
3.95k
  std::vector<unsigned> spanEnds;
157
3.95k
  std::vector<SpanInfo97> ret;
158
9.68k
  for (unsigned i = prop1Index; i < prop2Index; ++i)
159
5.72k
  {
160
5.72k
    unsigned offset = i * 0x200;
161
5.72k
    input->seek(offset + 0x1FF, librevenge::RVNG_SEEK_SET);
162
5.72k
    unsigned numEntries = readU8(input);
163
5.72k
    input->seek(offset, librevenge::RVNG_SEEK_SET);
164
    // Skip the first thing; it is not an end
165
5.72k
    unsigned start = readU32(input);
166
347k
    for (unsigned j = 0; j < numEntries; ++j)
167
342k
    {
168
342k
      spanEnds.push_back(readU32(input) - start);
169
342k
    }
170
5.72k
    std::vector<unsigned char> spanStyleIndices;
171
18.8M
    for (size_t j = 0; j < spanEnds.size(); ++j)
172
18.8M
    {
173
18.8M
      spanStyleIndices.push_back(readU8(input));
174
18.8M
    }
175
28.2k
    while (stillReading(input, offset + 0x200) && readU8(input) == 0)
176
22.5k
    {
177
22.5k
      ;
178
22.5k
    }
179
5.72k
    input->seek(-1, librevenge::RVNG_SEEK_CUR);
180
5.72k
    std::map<unsigned char, CharacterStyle> stylesByIndex;
181
121k
    while (stillReading(input, offset + 0x1FF))
182
115k
    {
183
115k
      unsigned length = readU8(input);
184
115k
      unsigned nextOffset = input->tell() + length;
185
115k
      auto index = static_cast<unsigned char>((input->tell() - 1 - offset) / 2);
186
115k
      stylesByIndex[index] = readCharacterStyle(input, length);
187
115k
      input->seek(nextOffset, librevenge::RVNG_SEEK_SET);
188
115k
    }
189
18.7M
    for (size_t j = 0; j < spanEnds.size(); ++j)
190
18.7M
    {
191
18.7M
      ret.push_back(SpanInfo97(spanEnds[j], j < spanStyleIndices.size() ?
192
18.7M
                               stylesByIndex[spanStyleIndices[j]] : CharacterStyle()));
193
18.7M
    }
194
5.72k
  }
195
3.95k
  return ret;
196
3.95k
}
197
198
CharacterStyle MSPUBParser97::readCharacterStyle(
199
  librevenge::RVNGInputStream *input, unsigned length)
200
115k
{
201
115k
  CharacterStyle style;
202
203
115k
  unsigned begin = input->tell();
204
115k
  int textSizeVariationFromDefault = 0;
205
206
115k
  if (length >= 1)
207
14.5k
  {
208
14.5k
    unsigned char biFlags = readU8(input);
209
14.5k
    style.bold = biFlags & 0x1;
210
14.5k
    style.italic = biFlags & 0x2;
211
14.5k
  }
212
115k
  if (length >= 3)
213
13.4k
  {
214
13.4k
    input->seek(begin + 0x2, librevenge::RVNG_SEEK_SET);
215
13.4k
    style.fontIndex = readU8(input);
216
13.4k
  }
217
115k
  if (length >= 9)
218
10.0k
  {
219
10.0k
    input->seek(begin + 0x8, librevenge::RVNG_SEEK_SET);
220
10.0k
    if (readU8(input) & 0x1)
221
2.45k
      style.underline = Underline::Single;
222
10.0k
  }
223
115k
  if (length >= 5)
224
13.2k
  {
225
13.2k
    input->seek(begin + 0x4, librevenge::RVNG_SEEK_SET);
226
13.2k
    textSizeVariationFromDefault =
227
13.2k
      length >= 6 ? readS16(input) : readS8(input);
228
13.2k
  }
229
115k
  if (length >= 16)
230
8.54k
  {
231
8.54k
    input->seek(begin + 0xC, librevenge::RVNG_SEEK_SET);
232
8.54k
    style.colorIndex = getColorIndexByQuillEntry(readU32(input));
233
8.54k
  }
234
115k
  style.textSizeInPt = 10 +
235
115k
                       static_cast<double>(textSizeVariationFromDefault) / 2;
236
237
115k
  return style;
238
115k
}
239
240
MSPUBParser97::TextInfo97 MSPUBParser97::getTextInfo(librevenge::RVNGInputStream *input, unsigned length)
241
3.85k
{
242
3.85k
  length = std::min(length, m_length); // sanity check
243
3.85k
  std::vector<unsigned char> chars;
244
3.85k
  chars.reserve(length);
245
3.85k
  std::vector<unsigned> paragraphEnds;
246
3.85k
  std::vector<unsigned> shapeEnds;
247
3.85k
  unsigned start = input->tell();
248
3.85k
  unsigned char last = '\0';
249
13.6M
  while (stillReading(input, start + length))
250
13.6M
  {
251
13.6M
    chars.push_back(readU8(input));
252
13.6M
    if (last == 0xD && chars.back() == 0xA)
253
41.5k
    {
254
41.5k
      paragraphEnds.push_back(chars.size());
255
41.5k
    }
256
13.5M
    else if (chars.back() == 0xC)
257
536k
    {
258
536k
      shapeEnds.push_back(chars.size());
259
536k
    }
260
13.6M
    last = chars.back();
261
13.6M
  }
262
3.85k
  return TextInfo97(chars, paragraphEnds, shapeEnds);
263
3.85k
}
264
265
int MSPUBParser97::translateCoordinateIfNecessary(int coordinate) const
266
225k
{
267
225k
  const int offset = (m_isBanner ? 120 : 25) * EMUS_IN_INCH;
268
225k
  if (std::numeric_limits<int>::min() + offset > coordinate)
269
3.13k
    return std::numeric_limits<int>::min();
270
222k
  else
271
222k
    return coordinate - offset;
272
225k
}
273
274
unsigned MSPUBParser97::getFirstLineOffset() const
275
36.6k
{
276
36.6k
  return 0x22;
277
36.6k
}
278
279
unsigned MSPUBParser97::getSecondLineOffset() const
280
19.4k
{
281
19.4k
  return 0x2D;
282
19.4k
}
283
284
unsigned MSPUBParser97::getShapeFillTypeOffset() const
285
35.8k
{
286
35.8k
  return 0x20;
287
35.8k
}
288
289
unsigned MSPUBParser97::getShapeFillColorOffset() const
290
5.47k
{
291
5.47k
  return 0x18;
292
5.47k
}
293
294
}
295
296
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */