Coverage Report

Created: 2026-10-28 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libabw/src/lib/ABWStylesCollector.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 libabw 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 <limits>
11
#include <vector>
12
13
#include <boost/algorithm/string.hpp>
14
15
#include <librevenge/librevenge.h>
16
#include "ABWStylesCollector.h"
17
18
#define ABW_EPSILON 1.0E-06
19
20
namespace libabw
21
{
22
23
namespace
24
{
25
26
enum ABWListType
27
{
28
  NUMBERED_LIST = 0,
29
  LOWERCASE_LIST = 1,
30
  UPPERCASE_LIST = 2,
31
  LOWERROMAN_LIST = 3,
32
  UPPERROMAN_LIST = 4,
33
34
  BULLETED_LIST = 5,
35
  DASHED_LIST = 6,
36
  SQUARE_LIST = 7,
37
  TRIANGLE_LIST = 8,
38
  DIAMOND_LIST = 9,
39
  STAR_LIST = 10,
40
  IMPLIES_LIST = 11,
41
  TICK_LIST = 12,
42
  BOX_LIST = 13,
43
  HAND_LIST = 14,
44
  HEART_LIST = 15,
45
  ARROWHEAD_LIST = 16,
46
47
  LAST_BULLETED_LIST = 17,
48
  OTHER_NUMBERED_LISTS = 0x7f,
49
  ARABICNUMBERED_LIST = 0x80,
50
  HEBREW_LIST = 0x81,
51
  NOT_A_LIST = 0xff
52
};
53
54
static int abw_unichar_to_utf8(unsigned c, char *outbuf)
55
10.7k
{
56
10.7k
  unsigned char len = 1;
57
10.7k
  unsigned char first = 0;
58
59
10.7k
  if (c < 0x80)
60
574
  {
61
574
    first = 0;
62
574
    len = 1;
63
574
  }
64
10.1k
  else if (c < 0x800)
65
0
  {
66
0
    first = 0xc0;
67
0
    len = 2;
68
0
  }
69
10.1k
  else if (c < 0x10000)
70
10.1k
  {
71
10.1k
    first = 0xe0;
72
10.1k
    len = 3;
73
10.1k
  }
74
0
  else if (c < 0x200000)
75
0
  {
76
0
    first = 0xf0;
77
0
    len = 4;
78
0
  }
79
0
  else if (c < 0x4000000)
80
0
  {
81
0
    first = 0xf8;
82
0
    len = 5;
83
0
  }
84
0
  else
85
0
  {
86
0
    first = 0xfc;
87
0
    len = 6;
88
0
  }
89
90
10.7k
  if (outbuf)
91
5.38k
  {
92
15.5k
    for (auto i = (unsigned char)(len - 1); i > 0; --i)
93
10.1k
    {
94
10.1k
      outbuf[i] = (char)((c & 0x3f) | 0x80);
95
10.1k
      c >>= 6;
96
10.1k
    }
97
5.38k
    outbuf[0] = (char)(c | first);
98
5.38k
  }
99
100
10.7k
  return len;
101
10.7k
}
102
103
static void appendUCS4(librevenge::RVNGString &str, unsigned ucs4)
104
5.38k
{
105
5.38k
  int charLength = abw_unichar_to_utf8(ucs4, nullptr);
106
5.38k
  std::vector<char> utf8(charLength+1);
107
5.38k
  utf8[charLength] = '\0';
108
5.38k
  abw_unichar_to_utf8(ucs4, utf8.data());
109
5.38k
  str.append(utf8.data());
110
5.38k
}
111
112
} // anonymous namespace
113
114
} // namespace libabw
115
116
libabw::ABWStylesTableState::ABWStylesTableState() :
117
26.4k
  m_currentCellProperties(),
118
26.4k
  m_currentTableWidth(0),
119
26.4k
  m_currentTableRow(-1),
120
26.4k
  m_currentTableId(-1) {}
121
122
libabw::ABWStylesTableState::ABWStylesTableState(const ABWStylesTableState &ts) :
123
26.4k
  m_currentCellProperties(ts.m_currentCellProperties),
124
26.4k
  m_currentTableWidth(ts.m_currentTableWidth),
125
26.4k
  m_currentTableRow(ts.m_currentTableRow),
126
26.4k
  m_currentTableId(ts.m_currentTableId) {}
127
128
52.9k
libabw::ABWStylesTableState::~ABWStylesTableState() {}
129
130
libabw::ABWStylesParsingState::ABWStylesParsingState() :
131
33.2k
  m_tableStates() {}
132
133
libabw::ABWStylesParsingState::ABWStylesParsingState(const ABWStylesParsingState &ps) :
134
0
  m_tableStates(ps.m_tableStates) {}
135
136
33.2k
libabw::ABWStylesParsingState::~ABWStylesParsingState() {}
137
138
libabw::ABWStylesCollector::ABWStylesCollector(std::map<int, int> &tableSizes,
139
                                               std::map<std::string, ABWData> &data,
140
                                               std::map<int, std::shared_ptr<ABWListElement>> &listElements) :
141
33.2k
  m_ps(new ABWStylesParsingState),
142
33.2k
  m_tableSizes(tableSizes),
143
33.2k
  m_data(data),
144
33.2k
  m_tableCounter(0),
145
33.2k
  m_listElements(listElements) {}
146
147
libabw::ABWStylesCollector::~ABWStylesCollector()
148
33.2k
{
149
33.2k
}
150
151
void libabw::ABWStylesCollector::openTable(const char *)
152
26.4k
{
153
26.4k
  m_ps->m_tableStates.push(ABWStylesTableState());
154
26.4k
  m_ps->m_tableStates.top().m_currentTableId = m_tableCounter++;
155
26.4k
  m_ps->m_tableStates.top().m_currentTableRow = -1;
156
26.4k
  m_ps->m_tableStates.top().m_currentTableWidth = 0;
157
26.4k
}
158
159
void libabw::ABWStylesCollector::closeTable()
160
22.9k
{
161
22.9k
  if (!m_ps->m_tableStates.empty())
162
22.9k
  {
163
22.9k
    auto curWidth = m_ps->m_tableStates.top().m_currentTableWidth;
164
22.9k
    m_tableSizes[m_ps->m_tableStates.top().m_currentTableId] = curWidth > 0 ? curWidth : 0;
165
22.9k
    m_ps->m_tableStates.pop();
166
22.9k
  }
167
22.9k
}
168
169
void libabw::ABWStylesCollector::openCell(const char *props)
170
24.3k
{
171
24.3k
  if (!m_ps->m_tableStates.empty())
172
23.5k
  {
173
23.5k
    if (props)
174
12.7k
      parsePropString(props, m_ps->m_tableStates.top().m_currentCellProperties);
175
23.5k
    int currentRow(0);
176
23.5k
    if (!findInt(_findCellProperty("top-attach"), currentRow))
177
13.5k
    {
178
13.5k
      currentRow = m_ps->m_tableStates.top().m_currentTableRow;
179
13.5k
      if (currentRow < std::numeric_limits<int>::max())
180
13.4k
        ++currentRow;
181
13.5k
    }
182
23.5k
    if (m_ps->m_tableStates.top().m_currentTableRow < currentRow)
183
16.6k
      m_ps->m_tableStates.top().m_currentTableRow = currentRow;
184
185
23.5k
    if (0 == m_ps->m_tableStates.top().m_currentTableRow)
186
6.17k
    {
187
6.17k
      int leftAttach(0);
188
6.17k
      int rightAttach(0);
189
6.17k
      if (findInt(_findCellProperty("left-attach"), leftAttach)
190
2.82k
          && findInt(_findCellProperty("right-attach"), rightAttach)
191
1.82k
          && leftAttach >= 0
192
1.60k
          && rightAttach > leftAttach
193
1.34k
          && rightAttach - leftAttach < std::numeric_limits<int>::max() - m_ps->m_tableStates.top().m_currentTableWidth
194
6.17k
         )
195
1.25k
        m_ps->m_tableStates.top().m_currentTableWidth += rightAttach - leftAttach;
196
4.91k
      else
197
4.91k
        m_ps->m_tableStates.top().m_currentTableWidth++;
198
6.17k
    }
199
23.5k
  }
200
24.3k
}
201
202
void libabw::ABWStylesCollector::closeCell()
203
17.5k
{
204
17.5k
  if (!m_ps->m_tableStates.empty())
205
16.9k
    m_ps->m_tableStates.top().m_currentCellProperties.clear();
206
17.5k
}
207
208
std::string libabw::ABWStylesCollector::_findCellProperty(const char *name)
209
32.5k
{
210
32.5k
  ABWPropertyMap::const_iterator iter = m_ps->m_tableStates.top().m_currentCellProperties.find(name);
211
32.5k
  if (iter != m_ps->m_tableStates.top().m_currentCellProperties.end())
212
20.1k
    return iter->second;
213
12.4k
  return std::string();
214
32.5k
}
215
216
void libabw::ABWStylesCollector::collectData(const char *name, const char *mimeType, const librevenge::RVNGBinaryData &data)
217
19.6k
{
218
19.6k
  if (!name)
219
15.7k
    return;
220
3.93k
  m_data[name] = ABWData(mimeType ? mimeType : "", data);
221
3.93k
}
222
223
void libabw::ABWStylesCollector::_processList(int id, const char *listDelim, int parentid, int startValue, int type)
224
23.3k
{
225
23.3k
  using namespace boost;
226
23.3k
  using namespace boost::algorithm;
227
228
23.3k
  if ((type >= BULLETED_LIST && type < LAST_BULLETED_LIST) || type == NOT_A_LIST)
229
19.0k
  {
230
19.0k
    auto tmpElement = std::make_shared<ABWUnorderedListElement>();
231
19.0k
    switch (type)
232
19.0k
    {
233
4.26k
    case BULLETED_LIST:
234
4.26k
      appendUCS4(tmpElement->m_bulletChar, 0x2022);
235
4.26k
      break;
236
287
    case DASHED_LIST:
237
287
      appendUCS4(tmpElement->m_bulletChar, 0x002D);
238
287
      break;
239
132
    case SQUARE_LIST:
240
132
      appendUCS4(tmpElement->m_bulletChar, 0x25A0);
241
132
      break;
242
73
    case TRIANGLE_LIST:
243
73
      appendUCS4(tmpElement->m_bulletChar, 0x25B2);
244
73
      break;
245
70
    case DIAMOND_LIST:
246
70
      appendUCS4(tmpElement->m_bulletChar, 0x2666);
247
70
      break;
248
25
    case STAR_LIST:
249
25
      appendUCS4(tmpElement->m_bulletChar, 0x2733);
250
25
      break;
251
92
    case IMPLIES_LIST:
252
92
      appendUCS4(tmpElement->m_bulletChar, 0x21D2);
253
92
      break;
254
41
    case TICK_LIST:
255
41
      appendUCS4(tmpElement->m_bulletChar, 0x2713);
256
41
      break;
257
35
    case BOX_LIST:
258
35
      appendUCS4(tmpElement->m_bulletChar, 0x2752);
259
35
      break;
260
78
    case HAND_LIST:
261
78
      appendUCS4(tmpElement->m_bulletChar, 0x261E);
262
78
      break;
263
68
    case HEART_LIST:
264
68
      appendUCS4(tmpElement->m_bulletChar, 0x2665);
265
68
      break;
266
213
    case ARROWHEAD_LIST:
267
213
      appendUCS4(tmpElement->m_bulletChar, 0x27A3);
268
213
      break;
269
13.6k
    default:
270
13.6k
      tmpElement->m_bulletChar = ""; // for the while
271
13.6k
      break;
272
19.0k
    }
273
19.0k
    m_listElements[id] = tmpElement;
274
19.0k
  }
275
4.23k
  else
276
4.23k
  {
277
4.23k
    auto tmpElement = std::make_shared<ABWOrderedListElement>();
278
4.23k
    switch (type)
279
4.23k
    {
280
473
    case NUMBERED_LIST:
281
473
      tmpElement->m_numFormat = "1";
282
473
      break;
283
1.42k
    case LOWERCASE_LIST:
284
1.42k
      tmpElement->m_numFormat = "a";
285
1.42k
      break;
286
118
    case UPPERCASE_LIST:
287
118
      tmpElement->m_numFormat = "A";
288
118
      break;
289
261
    case LOWERROMAN_LIST:
290
261
      tmpElement->m_numFormat = "i";
291
261
      break;
292
79
    case UPPERROMAN_LIST:
293
79
      tmpElement->m_numFormat = "I";
294
79
      break;
295
1.88k
    default:
296
1.88k
      tmpElement->m_numFormat = "1";
297
1.88k
      break;
298
4.23k
    }
299
4.23k
    tmpElement->m_startValue = startValue;
300
301
    // get prefix and suffix by splitting the listDelim
302
4.23k
    if (listDelim)
303
2.96k
    {
304
2.96k
      std::string delim(listDelim);
305
2.96k
      std::vector<librevenge::RVNGString> strVec;
306
307
2.96k
      for (split_iterator<std::string::iterator> It =
308
2.96k
             make_split_iterator(delim, first_finder("%L", is_iequal()));
309
17.5k
           It != split_iterator<std::string::iterator>(); ++It)
310
14.6k
      {
311
14.6k
        strVec.push_back(copy_range<std::string>(*It).c_str());
312
14.6k
      }
313
2.96k
      if (2 <= strVec.size())
314
2.59k
      {
315
2.59k
        tmpElement->m_numPrefix = strVec[0];
316
2.59k
        tmpElement->m_numSuffix = strVec[1];
317
2.59k
      }
318
2.96k
    }
319
4.23k
    m_listElements[id] = tmpElement;
320
4.23k
  }
321
23.3k
  if (parentid)
322
1.54k
    m_listElements[id]->m_parentId = parentid;
323
23.3k
}
324
325
void libabw::ABWStylesCollector::collectList(const char *id, const char *, const char *listDelim,
326
                                             const char *parentid, const char *startValue, const char *type)
327
13.1k
{
328
13.1k
  int intId(0);
329
13.1k
  if (!id || !findInt(id, intId) || intId < 0)
330
3.64k
    intId = 0;
331
13.1k
  if (!intId)
332
3.78k
    return;
333
9.38k
  if (m_listElements[intId])
334
7.24k
    m_listElements[intId].reset();
335
9.38k
  int intType(0);
336
9.38k
  if (!type || !findInt(type, intType) || intType < 0)
337
4.18k
    intType = 5;
338
9.38k
  int intParentId(0);
339
9.38k
  if (!parentid || !findInt(parentid, intParentId) || intParentId < 0)
340
7.82k
    intParentId = 0;
341
9.38k
  int intStartValue(0);
342
9.38k
  if (!startValue || !findInt(startValue, intStartValue) || intStartValue < 0)
343
8.78k
    intStartValue = 0;
344
9.38k
  _processList(intId, listDelim, intParentId, intStartValue, intType);
345
9.38k
}
346
347
void libabw::ABWStylesCollector::collectParagraphProperties(const char *level, const char *listid, const char *parentid, const char * /* style */, const char *props)
348
110k
{
349
110k
  ABWPropertyMap properties;
350
110k
  if (props)
351
21.7k
    parsePropString(props, properties);
352
353
110k
  int intParentId(0);
354
110k
  if (!parentid || !findInt(parentid, intParentId) || intParentId < 0)
355
109k
    intParentId = 0;
356
110k
  int intListId(0);
357
110k
  if (!listid || !findInt(listid, intListId) || intListId < 0)
358
107k
    intListId = 0;
359
360
110k
  auto iter = m_listElements.find(intListId);
361
110k
  if (iter == m_listElements.end() || !iter->second)
362
13.9k
  {
363
13.9k
    ABWPropertyMap::const_iterator i = properties.find("list-style");
364
13.9k
    int listStyle(NOT_A_LIST);
365
13.9k
    if (i != properties.end())
366
326
    {
367
326
      if (i->second == "Numbered List")
368
44
        listStyle = NUMBERED_LIST;
369
282
      else if (i->second == "Lower Case List")
370
40
        listStyle = LOWERCASE_LIST;
371
242
      else if (i->second == "Upper Case List")
372
6
        listStyle = UPPERCASE_LIST;
373
236
      else if (i->second == "Lower Roman List")
374
60
        listStyle = LOWERROMAN_LIST;
375
176
      else if (i->second == "Upper Roman List")
376
17
        listStyle = UPPERROMAN_LIST;
377
159
      else if (i->second == "Hebrew List")
378
1
        listStyle = HEBREW_LIST;
379
158
      else if (i->second == "Arabic List")
380
11
        listStyle = ARABICNUMBERED_LIST;
381
147
      else if (i->second == "Bullet List")
382
36
        listStyle = BULLETED_LIST;
383
111
      else if (i->second == "Dashed List")
384
1
        listStyle = DASHED_LIST;
385
110
      else if (i->second == "Square List")
386
1
        listStyle = SQUARE_LIST;
387
109
      else if (i->second == "Triangle List")
388
2
        listStyle = TRIANGLE_LIST;
389
107
      else if (i->second == "Diamond List")
390
1
        listStyle = DIAMOND_LIST;
391
106
      else if (i->second == "Star List")
392
3
        listStyle = STAR_LIST;
393
103
      else if (i->second == "Implies List")
394
1
        listStyle = IMPLIES_LIST;
395
102
      else if (i->second == "Tick List")
396
3
        listStyle = TICK_LIST;
397
99
      else if (i->second == "Box List")
398
1
        listStyle = BOX_LIST;
399
98
      else if (i->second == "Hand List")
400
1
        listStyle = HAND_LIST;
401
97
      else if (i->second == "Heart List")
402
1
        listStyle = HEART_LIST;
403
96
      else if (i->second == "Arrowhead List")
404
3
        listStyle = ARROWHEAD_LIST;
405
93
      else
406
93
        listStyle = NOT_A_LIST;
407
326
    }
408
13.9k
    i = properties.find("start-value");
409
13.9k
    std::string startValue;
410
13.9k
    if (i != properties.end())
411
125
      startValue = i->second;
412
13.9k
    int intStartValue(0);
413
13.9k
    if (startValue.empty() || findInt(startValue, intStartValue) || intStartValue < 0)
414
13.9k
      intStartValue = 0;
415
13.9k
    _processList(intListId, "%L", intParentId, intStartValue, listStyle);
416
13.9k
    iter = m_listElements.find(intListId);
417
13.9k
  }
418
110k
  if (iter != m_listElements.end() && iter->second)
419
110k
  {
420
110k
    const auto &listElement = iter->second;
421
422
110k
    if (!level || !findInt(level, listElement->m_listLevel) || listElement->m_listLevel < 0)
423
97.6k
      listElement->m_listLevel = 0;
424
425
110k
    ABWPropertyMap::const_iterator i = properties.find("margin-left");
426
110k
    ABWUnit unit(ABW_NONE);
427
110k
    double marginLeft(0.0);
428
110k
    if (i == properties.end() || !findDouble(i->second, marginLeft, unit) || unit != ABW_IN)
429
108k
      marginLeft = 0.0;
430
110k
    i = properties.find("text-indent");
431
110k
    double textIndent(0.0);
432
110k
    if (i == properties.end() || !findDouble(i->second, textIndent, unit) || unit != ABW_IN)
433
108k
      textIndent = 0.0;
434
110k
    listElement->m_minLabelWidth = -textIndent;
435
110k
    listElement->m_spaceBefore = marginLeft + textIndent;
436
110k
  }
437
110k
}
438
439
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */