Coverage Report

Created: 2026-07-20 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libetonyek/src/lib/IWAText.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 libetonyek 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 "IWAText.h"
11
12
#include <memory>
13
14
#include "IWORKLanguageManager.h"
15
#include "IWORKProperties.h"
16
#include "IWORKText.h"
17
18
namespace libetonyek
19
{
20
21
using boost::none;
22
23
using std::make_shared;
24
using std::map;
25
using std::string;
26
27
namespace
28
{
29
30
void flushText(string &text, IWORKText &collector)
31
21.9k
{
32
21.9k
  if (!text.empty())
33
13.1k
  {
34
13.1k
    collector.insertText(text);
35
13.1k
    text.clear();
36
13.1k
  }
37
21.9k
}
38
39
}
40
41
IWAText::IWAText(const std::string &text, IWORKLanguageManager &langManager)
42
5.64k
  : m_text(text.c_str())
43
5.64k
  , m_langManager(langManager)
44
5.64k
  , m_pageMasters()
45
5.64k
  , m_sections()
46
5.64k
  , m_paras()
47
5.64k
  , m_spans()
48
49
5.64k
  , m_langs()
50
5.64k
  , m_links()
51
5.64k
  , m_lists()
52
5.64k
  , m_listLevels()
53
5.64k
  , m_dropCaps()
54
5.64k
  , m_rtls()
55
56
5.64k
  , m_attachments()
57
5.64k
{
58
5.64k
}
59
60
void IWAText::setPageMasters(const std::map<unsigned, IWORKStylePtr_t> &pageMasters)
61
0
{
62
0
  m_pageMasters = pageMasters;
63
0
}
64
65
void IWAText::setSections(const std::map<unsigned, IWORKStylePtr_t> &sections)
66
0
{
67
0
  m_sections = sections;
68
0
}
69
70
void IWAText::setParagraphs(const std::map<unsigned, IWORKStylePtr_t> &paras)
71
5.03k
{
72
5.03k
  m_paras = paras;
73
5.03k
}
74
75
void IWAText::setSpans(const std::map<unsigned, IWORKStylePtr_t> &spans)
76
199
{
77
199
  m_spans = spans;
78
199
}
79
80
void IWAText::setLanguages(const std::map<unsigned, std::string> &langs)
81
1.79k
{
82
1.79k
  m_langs = langs;
83
1.79k
}
84
85
void IWAText::setLinks(const std::map<unsigned, std::string> &links)
86
221
{
87
221
  m_links = links;
88
221
}
89
90
void IWAText::setListLevels(const std::map<unsigned, unsigned> &levels)
91
5.08k
{
92
5.08k
  m_listLevels = levels;
93
5.08k
}
94
95
void IWAText::setLists(const std::map<unsigned, IWORKStylePtr_t> &lists)
96
4.72k
{
97
4.72k
  m_lists = lists;
98
4.72k
}
99
100
void IWAText::setDropCaps(const std::map<unsigned, IWORKStylePtr_t> &dropCaps)
101
58
{
102
58
  m_dropCaps = dropCaps;
103
58
}
104
105
void IWAText::setRTLs(const std::map<unsigned, bool> &rtls)
106
5.05k
{
107
5.05k
  m_rtls = rtls;
108
5.05k
}
109
110
void IWAText::setAttachments(const std::multimap<unsigned, std::function<void(unsigned, bool &)> > &attachments)
111
5.49k
{
112
5.49k
  m_attachments = attachments;
113
5.49k
}
114
115
void IWAText::parse(IWORKText &collector, const std::function<void(unsigned, IWORKStylePtr_t)> &openPageSpan)
116
5.57k
{
117
5.57k
  auto pageMasterIt = m_pageMasters.begin();
118
5.57k
  auto sectionIt = m_sections.begin();
119
5.57k
  map<unsigned, IWORKStylePtr_t>::const_iterator paraIt = m_paras.begin();
120
5.57k
  map<unsigned, IWORKStylePtr_t>::const_iterator dropCapIt = m_dropCaps.begin();
121
5.57k
  map<unsigned, bool>::const_iterator rtlIt = m_rtls.begin();
122
5.57k
  map<unsigned, IWORKStylePtr_t>::const_iterator spanIt = m_spans.begin();
123
5.57k
  map<unsigned, string>::const_iterator langIt = m_langs.begin();
124
5.57k
  map<unsigned, string>::const_iterator linkIt = m_links.begin();
125
5.57k
  map<unsigned, IWORKStylePtr_t>::const_iterator listIt = m_lists.begin();
126
5.57k
  map<unsigned, unsigned>::const_iterator listLevelIt = m_listLevels.begin();
127
5.57k
  auto attachmentIt = m_attachments.begin();
128
5.57k
  bool wasSpace = false;
129
5.57k
  IWORKStylePtr_t currentListStyle;
130
5.57k
  bool isLink = false;
131
5.57k
  string curText;
132
133
  // to handle drop cap's style, we need to set the drop cap style at
134
  // the beginning of a pararagraph, and then reset the current style
135
  // at the end of the drop cap's letters
136
5.57k
  IWORKStylePtr_t lastSpanStyle;
137
5.57k
  boost::optional<std::size_t> endDropCapPos;
138
5.57k
  bool rtl=false;
139
140
5.57k
  std::size_t pos = 0;
141
5.57k
  librevenge::RVNGString::Iter iter(m_text);
142
5.57k
  iter.rewind();
143
179k
  for (; iter.next(); ++pos)
144
173k
  {
145
    // first the page master change
146
173k
    if ((pageMasterIt != m_pageMasters.end()) && (pageMasterIt->first == pos))
147
0
    {
148
0
      if (openPageSpan)
149
0
      {
150
0
        flushText(curText, collector);
151
0
        openPageSpan(unsigned(pos), pageMasterIt->second);
152
0
      }
153
0
      ++pageMasterIt;
154
0
    }
155
    // first handle section change
156
173k
    if ((sectionIt != m_sections.end()) && (sectionIt->first == pos))
157
0
    {
158
0
      collector.flushLayout();
159
0
      collector.setLayoutStyle(sectionIt->second);
160
0
      ++sectionIt;
161
0
    }
162
163
    // handle span style change
164
173k
    IWORKStylePtr_t spanStyle;
165
173k
    IWORKStylePtr_t langStyle;
166
173k
    IWORKStylePtr_t dropCapStyle;
167
173k
    bool spanChanged = false;
168
173k
    bool langChanged = false;
169
173k
    bool isEndDropCap = bool(endDropCapPos) && get(endDropCapPos)==pos;
170
173k
    if ((spanIt != m_spans.end()) && (spanIt->first == pos))
171
404
    {
172
404
      lastSpanStyle = spanStyle = spanIt->second;
173
404
      spanChanged = true;
174
404
      ++spanIt;
175
404
    }
176
173k
    if ((langIt != m_langs.end()) && (langIt->first == pos))
177
2.07k
    {
178
2.07k
      IWORKPropertyMap props;
179
2.07k
      if (!langIt->second.empty())
180
1.24k
      {
181
1.24k
        const string &tag = m_langManager.addTag(langIt->second);
182
1.24k
        if (tag.empty())
183
0
          props.clear<property::Language>();
184
1.24k
        else
185
1.24k
          props.put<property::Language>(tag);
186
1.24k
      }
187
831
      else
188
831
      {
189
831
        props.clear<property::Language>();
190
831
      }
191
2.07k
      langStyle = make_shared<IWORKStyle>(props, none, none);
192
2.07k
      langChanged = true;
193
2.07k
      ++langIt;
194
2.07k
    }
195
173k
    if (spanChanged || langChanged || isEndDropCap)
196
2.37k
    {
197
2.37k
      flushText(curText, collector);
198
2.37k
      if (pos != 0)
199
695
        collector.flushSpan();
200
2.37k
      if (spanChanged || isEndDropCap)
201
404
      {
202
404
        collector.setSpanStyle(spanChanged ? spanStyle : lastSpanStyle);
203
404
        endDropCapPos.reset();
204
404
      }
205
2.37k
      if (langChanged)
206
2.07k
        collector.setLanguage(langStyle);
207
2.37k
    }
208
    // handle start/end of a link
209
173k
    if ((linkIt != m_links.end()) && (linkIt->first == pos))
210
304
    {
211
304
      flushText(curText, collector);
212
304
      if (isLink)
213
0
      {
214
0
        collector.closeLink();
215
0
        isLink = false;
216
0
      }
217
304
      if (!linkIt->second.empty())
218
0
      {
219
0
        collector.openLink(linkIt->second);
220
0
        isLink = true;
221
0
      }
222
304
      ++linkIt;
223
304
    }
224
225
    // handle list style change
226
173k
    if ((dropCapIt != m_dropCaps.end()) && (dropCapIt->first == pos))
227
0
    {
228
0
      dropCapStyle=dropCapIt->second;
229
0
      ++dropCapIt;
230
0
    }
231
173k
    if ((rtlIt != m_rtls.end()) && (rtlIt->first==pos))
232
4.23k
    {
233
4.23k
      rtl=rtlIt->second;
234
4.23k
      ++rtlIt;
235
4.23k
    }
236
    // handle paragraph style change
237
173k
    if ((paraIt != m_paras.end()) && (paraIt->first == pos))
238
9.86k
    {
239
9.86k
      if (dropCapStyle && dropCapStyle->has<property::DropCap>())
240
0
      {
241
0
        auto const &dropCap=dropCapStyle->get<property::DropCap>();
242
0
        if (!dropCap.empty() && dropCap.m_style)
243
0
        {
244
0
          collector.setSpanStyle(dropCap.m_style);
245
0
          endDropCapPos=pos+dropCap.m_numCharacters;
246
0
        }
247
0
        IWORKPropertyMap props;
248
0
        props.put<property::DropCap>(dropCap);
249
0
        if (rtl)
250
0
          props.put<property::WritingMode>("rl-tb");
251
0
        collector.setParagraphStyle(std::make_shared<IWORKStyle>(props, boost::none, paraIt->second));
252
0
      }
253
9.86k
      else if (rtl)
254
1.46k
      {
255
1.46k
        IWORKPropertyMap props;
256
1.46k
        props.put<property::WritingMode>("rl-tb");
257
1.46k
        collector.setParagraphStyle(std::make_shared<IWORKStyle>(props, boost::none, paraIt->second));
258
1.46k
      }
259
8.39k
      else
260
8.39k
        collector.setParagraphStyle(paraIt->second);
261
9.86k
      ++paraIt;
262
9.86k
    }
263
264
    // handle list style change
265
173k
    if ((listIt != m_lists.end()) && (listIt->first == pos))
266
4.34k
    {
267
4.34k
      currentListStyle = listIt->second;
268
4.34k
      collector.setListStyle(currentListStyle);
269
4.34k
      ++listIt;
270
4.34k
    }
271
272
    // handle list level change
273
173k
    if ((listLevelIt != m_listLevels.end()) && (listLevelIt->first == pos))
274
8.66k
    {
275
8.66k
      collector.setListLevel(listLevelIt->second + 1);
276
8.66k
      ++listLevelIt;
277
8.66k
    }
278
279
173k
    bool ignoreCharacter=false;
280
173k
    while (attachmentIt != m_attachments.end() && attachmentIt->first == pos)
281
3
    {
282
3
      flushText(curText, collector);
283
3
      attachmentIt->second(unsigned(pos), ignoreCharacter);
284
3
      ++attachmentIt;
285
3
    }
286
173k
    if (ignoreCharacter)
287
3
      continue;
288
    // handle text
289
173k
    const char *const u8Char = iter();
290
291
173k
    if (unsigned(u8Char[0])<=0x1f && bool(endDropCapPos))
292
0
    {
293
      // a special character, better to force a span style reset
294
0
      flushText(curText, collector);
295
0
      collector.flushSpan();
296
0
      collector.setSpanStyle(lastSpanStyle);
297
0
      endDropCapPos.reset();
298
0
    }
299
300
173k
    switch (u8Char[0])
301
173k
    {
302
169
    case char(4): // new section(ok)
303
      // be sure to send the last section, even if it has no content
304
169
      if (openPageSpan && pos+1==size_t(m_text.len()) && (pageMasterIt != m_pageMasters.end()) && (pageMasterIt->first == pos+1))
305
0
      {
306
0
        flushText(curText, collector);
307
0
        openPageSpan(unsigned(pos), pageMasterIt->second);
308
0
      }
309
169
      break;
310
70
    case char(14): // footnote: normally already ignored
311
70
      break;
312
533
    case char(5):
313
533
    {
314
533
      flushText(curText, collector);
315
533
      collector.flushParagraph();
316
533
      collector.insertPageBreak();
317
533
      break;
318
0
    }
319
174
    case char(12): // column break
320
174
      if (m_sections.empty()) break;
321
0
      flushText(curText, collector);
322
0
      collector.flushParagraph();
323
0
      collector.insertColumnBreak();
324
0
      break;
325
278
    case '\t' :
326
278
      flushText(curText, collector);
327
278
      collector.insertTab();
328
278
      break;
329
377
    case '\r' :
330
377
      flushText(curText, collector);
331
377
      collector.insertLineBreak();
332
377
      break;
333
12.2k
    case '\n' :
334
12.2k
      flushText(curText, collector);
335
12.2k
      collector.flushParagraph();
336
12.2k
      break;
337
20.6k
    case ' ' :
338
20.6k
      if (wasSpace)
339
194
      {
340
194
        flushText(curText, collector);
341
194
        collector.insertSpace();
342
194
      }
343
20.4k
      else
344
20.4k
        curText.push_back(' ');
345
20.6k
      break;
346
139k
    default:
347
139k
      if (unsigned(u8Char[0])<=0x1f)
348
1.72k
      {
349
1.72k
        ETONYEK_DEBUG_MSG(("IWAText::parse: find bad character %d\n", (int) unsigned(u8Char[0])));
350
1.72k
        break;
351
1.72k
      }
352
137k
      curText.append(u8Char);
353
137k
      break;
354
173k
    }
355
173k
    wasSpace=u8Char[0]==' ';
356
173k
  }
357
5.57k
  flushText(curText, collector);
358
5.57k
  collector.flushParagraph();
359
5.57k
  collector.setListLevel(0);
360
5.57k
  collector.flushList();
361
5.57k
  collector.flushLayout();
362
5.57k
}
363
364
}
365
366
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */