/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> §ions) |
66 | 0 | { |
67 | 0 | m_sections = sections; |
68 | 0 | } |
69 | | |
70 | | void IWAText::setParagraphs(const std::map<unsigned, IWORKStylePtr_t> ¶s) |
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: */ |