/src/poppler/poppler/TextOutputDev.h
Line | Count | Source |
1 | | //======================================================================== |
2 | | // |
3 | | // TextOutputDev.h |
4 | | // |
5 | | // Copyright 1997-2003 Glyph & Cog, LLC |
6 | | // |
7 | | //======================================================================== |
8 | | |
9 | | //======================================================================== |
10 | | // |
11 | | // Modified under the Poppler project - http://poppler.freedesktop.org |
12 | | // |
13 | | // All changes made under the Poppler project to this file are licensed |
14 | | // under GPL version 2 or later |
15 | | // |
16 | | // Copyright (C) 2005-2007 Kristian Høgsberg <krh@redhat.com> |
17 | | // Copyright (C) 2006 Ed Catmur <ed@catmur.co.uk> |
18 | | // Copyright (C) 2007, 2008, 2011, 2013 Carlos Garcia Campos <carlosgc@gnome.org> |
19 | | // Copyright (C) 2007, 2017 Adrian Johnson <ajohnson@redneon.com> |
20 | | // Copyright (C) 2008, 2010, 2015, 2016, 2018, 2019, 2021, 2025, 2026 Albert Astals Cid <aacid@kde.org> |
21 | | // Copyright (C) 2010 Brian Ewins <brian.ewins@gmail.com> |
22 | | // Copyright (C) 2012, 2013, 2015, 2016 Jason Crain <jason@aquaticape.us> |
23 | | // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
24 | | // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
25 | | // Copyright (C) 2018 Sanchit Anand <sanxchit@gmail.com> |
26 | | // Copyright (C) 2018, 2020, 2021, 2025 Nelson Benítez León <nbenitezl@gmail.com> |
27 | | // Copyright (C) 2019, 2022 Oliver Sander <oliver.sander@tu-dresden.de> |
28 | | // Copyright (C) 2019 Dan Shea <dan.shea@logical-innovations.com> |
29 | | // Copyright (C) 2020 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp> |
30 | | // Copyright (C) 2024, 2025 Stefan Brüns <stefan.bruens@rwth-aachen.de> |
31 | | // Copyright (C) 2024-2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
32 | | // Copyright (C) 2025 Hagen Möbius <hagen.moebius@googlemail.com> |
33 | | // Copyright (C) 2026 Aditya Tiwari <suntiwari3495@gmail.com> |
34 | | // |
35 | | // To see a description of the changes please see the Changelog file that |
36 | | // came with your tarball or type make ChangeLog if you are building from git |
37 | | // |
38 | | //======================================================================== |
39 | | |
40 | | #ifndef TEXTOUTPUTDEV_H |
41 | | #define TEXTOUTPUTDEV_H |
42 | | |
43 | | #include "poppler_private_export.h" |
44 | | #include "GfxFont.h" |
45 | | #include "GfxState.h" |
46 | | #include "OutputDev.h" |
47 | | #include "PDFRectangle.h" |
48 | | |
49 | | class GooString; |
50 | | class Gfx; |
51 | | class GfxFont; |
52 | | class GfxState; |
53 | | class UnicodeMap; |
54 | | class AnnotLink; |
55 | | |
56 | | class TextWord; |
57 | | class TextPool; |
58 | | class TextLine; |
59 | | class TextLineFrag; |
60 | | class TextBlock; |
61 | | class TextFlow; |
62 | | class TextLink; |
63 | | class TextUnderline; |
64 | | class TextWordList; |
65 | | class TextPage; |
66 | | class TextSelectionVisitor; |
67 | | |
68 | | //------------------------------------------------------------------------ |
69 | | |
70 | | using TextOutputFunc = void (*)(void *stream, const char *text, int len); |
71 | | |
72 | | enum SelectionStyle |
73 | | { |
74 | | selectionStyleGlyph, |
75 | | selectionStyleWord, |
76 | | selectionStyleLine |
77 | | }; |
78 | | |
79 | | enum EndOfLineKind |
80 | | { |
81 | | eolUnix, // LF |
82 | | eolDOS, // CR+LF |
83 | | eolMac // CR |
84 | | }; |
85 | | |
86 | | enum class EndOfLineHyphenMode |
87 | | { |
88 | | RemoveAll, |
89 | | RemoveSoft, |
90 | | Keep |
91 | | }; |
92 | | |
93 | | //------------------------------------------------------------------------ |
94 | | // TextFontInfo |
95 | | //------------------------------------------------------------------------ |
96 | | |
97 | | class POPPLER_PRIVATE_EXPORT TextFontInfo |
98 | | { |
99 | | public: |
100 | | explicit TextFontInfo(const GfxState *state); |
101 | | ~TextFontInfo(); |
102 | | |
103 | | TextFontInfo(const TextFontInfo &) = delete; |
104 | | TextFontInfo &operator=(const TextFontInfo &) = delete; |
105 | | |
106 | | bool matches(const GfxState *state) const; |
107 | | bool matches(const TextFontInfo *fontInfo) const; |
108 | | bool matches(const Ref *ref) const; |
109 | | |
110 | | // Get the font ascent, or a default value if the font is not set |
111 | | double getAscent() const; |
112 | | |
113 | | // Get the font descent, or a default value if the font is not set |
114 | | double getDescent() const; |
115 | | |
116 | | // Get the writing mode, or Horizontal if the font is not set |
117 | | GfxFont::WritingMode getWMode() const; |
118 | | |
119 | | // Get the font name (which may be NULL). |
120 | 0 | const GooString *getFontName() const { return fontName; } |
121 | | |
122 | | // Get font descriptor flags. |
123 | 0 | bool isFixedWidth() const { return flags & fontFixedWidth; } |
124 | 0 | bool isSerif() const { return flags & fontSerif; } |
125 | 0 | bool isSymbolic() const { return flags & fontSymbolic; } |
126 | 0 | bool isItalic() const { return flags & fontItalic; } |
127 | 0 | bool isBold() const { return flags & fontBold; } |
128 | | |
129 | | private: |
130 | | std::shared_ptr<GfxFont> gfxFont; |
131 | | GooString *fontName; |
132 | | int flags; |
133 | | |
134 | | friend class TextWord; |
135 | | friend class TextPage; |
136 | | friend class TextSelectionPainter; |
137 | | }; |
138 | | |
139 | | //------------------------------------------------------------------------ |
140 | | // TextWord |
141 | | //------------------------------------------------------------------------ |
142 | | |
143 | | class POPPLER_PRIVATE_EXPORT TextWord |
144 | | { |
145 | | public: |
146 | | // Constructor. |
147 | | TextWord(const GfxState *state, int rotA, double fontSize); |
148 | | |
149 | | // Destructor. |
150 | | ~TextWord(); |
151 | | |
152 | | TextWord(const TextWord &) = delete; |
153 | | TextWord &operator=(const TextWord &) = delete; |
154 | | |
155 | | // Add a character to the word. |
156 | | void addChar(TextFontInfo *fontA, double x, double y, double dx, double dy, int charPosA, int charLen, CharCode c, Unicode u, const Matrix &textMatA); |
157 | | |
158 | | // Attempt to add a character to the word as a combining character. |
159 | | // Either character u or the last character in the word must be an |
160 | | // acute, dieresis, or other combining character. Returns true if |
161 | | // the character was added. |
162 | | bool addCombining(TextFontInfo *fontA, double fontSizeA, double x, double y, double dx, double dy, int charPosA, int charLen, CharCode c, Unicode u, const Matrix &textMatA); |
163 | | |
164 | | // Merge <word> onto the end of <this>. |
165 | | void merge(TextWord *word); |
166 | | |
167 | | // Compares <this> to <word>, returning -1 (<), 0 (=), or +1 (>), |
168 | | // based on a primary-axis comparison, e.g., x ordering if rot=0. |
169 | | int primaryCmp(const TextWord *word) const; |
170 | | |
171 | | // Return the distance along the primary axis between <this> and |
172 | | // <word>. |
173 | | double primaryDelta(const TextWord *word) const; |
174 | | |
175 | | static bool cmpYX(const TextWord *word1, const TextWord *word2); |
176 | | |
177 | | void visitSelection(TextSelectionVisitor *visitor, const PDFRectangle &selection); |
178 | | |
179 | | // Get the TextFontInfo object associated with a character. |
180 | 0 | const TextFontInfo *getFontInfo(int idx) const { return chars[idx].font; } |
181 | | |
182 | | // Get the next TextWord on the linked list. |
183 | 0 | const TextWord *getNext() const { return next; } |
184 | | |
185 | 0 | int getLength() const { return chars.size(); } |
186 | 0 | const Unicode *getChar(int idx) const { return &chars[idx].text; } |
187 | | std::unique_ptr<std::string> getText() const; |
188 | 0 | const GooString *getFontName(int idx) const { return chars[idx].font->fontName; } |
189 | | void getColor(double *r, double *g, double *b) const |
190 | 0 | { |
191 | 0 | *r = colorR; |
192 | 0 | *g = colorG; |
193 | 0 | *b = colorB; |
194 | 0 | } |
195 | | void getBBox(double *xMinA, double *yMinA, double *xMaxA, double *yMaxA) const |
196 | 0 | { |
197 | 0 | *xMinA = xMin; |
198 | 0 | *yMinA = yMin; |
199 | 0 | *xMaxA = xMax; |
200 | 0 | *yMaxA = yMax; |
201 | 0 | } |
202 | 0 | PDFRectangle getBBox() const { return { xMin, yMin, xMax, yMax }; } |
203 | | |
204 | | void getCharBBox(int charIdx, double *xMinA, double *yMinA, double *xMaxA, double *yMaxA) const; |
205 | 13.9k | double getFontSize() const { return fontSize; } |
206 | 0 | int getRotation() const { return rot; } |
207 | 0 | int getCharPos() const { return chars.empty() ? 0 : chars.front().charPos; } |
208 | 0 | int getCharLen() const { return chars.empty() ? 0 : chars.back().charPos - chars.front().charPos; } |
209 | 0 | bool getSpaceAfter() const { return spaceAfter; } |
210 | 0 | bool isUnderlined() const { return underlined; } |
211 | 0 | const AnnotLink *getLink() const { return link; } |
212 | 0 | double getEdge(int i) const { return chars[i].edge; } |
213 | 0 | double getBaseline() const { return base; } |
214 | 0 | bool hasSpaceAfter() const { return spaceAfter; } |
215 | 0 | const TextWord *nextWord() const { return next; }; |
216 | 104M | auto len() const { return chars.size(); } |
217 | | |
218 | | private: |
219 | | void setInitialBounds(TextFontInfo *fontA, double x, double y); |
220 | | |
221 | | int rot; // rotation, multiple of 90 degrees |
222 | | // (0, 1, 2, or 3) |
223 | | GfxFont::WritingMode wMode; |
224 | | double xMin, xMax; // bounding box x coordinates |
225 | | double yMin, yMax; // bounding box y coordinates |
226 | | double base; // baseline x or y coordinate |
227 | | |
228 | | double fontSize; // font size |
229 | | |
230 | | struct CharInfo |
231 | | { |
232 | | Unicode text; |
233 | | CharCode charcode; |
234 | | int charPos; |
235 | | double edge; |
236 | | TextFontInfo *font; |
237 | | Matrix textMat; |
238 | | }; |
239 | | std::vector<CharInfo> chars; |
240 | | int charPosEnd = 0; |
241 | | double edgeEnd = 0; |
242 | | |
243 | | bool spaceAfter; // set if there is a space between this |
244 | | // word and the next word on the line |
245 | | bool underlined; |
246 | | bool invisible; // whether we are invisible (glyphless) |
247 | | TextWord *next; // next word in line |
248 | | |
249 | | double colorR, // word color |
250 | | colorG, colorB; |
251 | | |
252 | | AnnotLink *link; |
253 | | |
254 | | friend class TextPool; |
255 | | friend class TextLine; |
256 | | friend class TextBlock; |
257 | | friend class TextFlow; |
258 | | friend class TextWordList; |
259 | | friend class TextPage; |
260 | | |
261 | | friend class TextSelectionPainter; |
262 | | friend class TextSelectionDumper; |
263 | | }; |
264 | | |
265 | | //------------------------------------------------------------------------ |
266 | | // TextPool |
267 | | //------------------------------------------------------------------------ |
268 | | |
269 | | class TextPool |
270 | | { |
271 | | public: |
272 | | TextPool(); |
273 | | ~TextPool(); |
274 | | |
275 | | TextPool(const TextPool &) = delete; |
276 | | TextPool &operator=(const TextPool &) = delete; |
277 | | |
278 | 26.0M | TextWord *getPool(int baseIdx) { return pool[baseIdx - minBaseIdx].head; } |
279 | 955k | void setPool(int baseIdx, TextWord *p) { pool[baseIdx - minBaseIdx].head = p; } |
280 | | |
281 | | int getBaseIdx(double base) const; |
282 | | |
283 | | void addWord(TextWord *word); |
284 | | void sort(); |
285 | | |
286 | | private: |
287 | | int minBaseIdx; // min baseline bucket index |
288 | | int maxBaseIdx; // max baseline bucket index |
289 | | struct WordList |
290 | | { |
291 | | TextWord *head = nullptr; |
292 | | TextWord *tail = nullptr; |
293 | | }; |
294 | | std::vector<WordList> pool; |
295 | | |
296 | | friend class TextBlock; |
297 | | friend class TextPage; |
298 | | }; |
299 | | |
300 | | struct TextFlowData; |
301 | | |
302 | | //------------------------------------------------------------------------ |
303 | | // TextLine |
304 | | //------------------------------------------------------------------------ |
305 | | |
306 | | class TextLine |
307 | | { |
308 | | public: |
309 | | TextLine(TextBlock *blkA, int rotA, double baseA); |
310 | | ~TextLine(); |
311 | | |
312 | | TextLine(const TextLine &) = delete; |
313 | | TextLine &operator=(const TextLine &) = delete; |
314 | | |
315 | | void addWord(TextWord *word); |
316 | | |
317 | | // Return the distance along the primary axis between <this> and |
318 | | // <line>. |
319 | | double primaryDelta(const TextLine *line) const; |
320 | | |
321 | | // Compares <this> to <line>, returning -1 (<), 0 (=), or +1 (>), |
322 | | // based on a primary-axis comparison, e.g., x ordering if rot=0. |
323 | | int primaryCmp(const TextLine *line) const; |
324 | | |
325 | | // Compares <this> to <line>, returning -1 (<), 0 (=), or +1 (>), |
326 | | // based on a secondary-axis comparison of the baselines, e.g., y |
327 | | // ordering if rot=0. |
328 | | int secondaryCmp(const TextLine *line) const; |
329 | | |
330 | | int cmpYX(const TextLine *line) const; |
331 | | |
332 | | static bool cmpXY(const TextLine *line1, const TextLine *line2); |
333 | | |
334 | | void coalesce(const UnicodeMap *uMap); |
335 | | |
336 | | void visitSelection(TextSelectionVisitor *visitor, const PDFRectangle &selection, SelectionStyle style); |
337 | 0 | PDFRectangle getBBox() const { return { xMin, yMin, xMax, yMax }; } |
338 | | |
339 | | // Get the head of the linked list of TextWords. |
340 | 0 | const TextWord *getWords() const { return words; } |
341 | | |
342 | | // Get the next TextLine on the linked list. |
343 | 0 | const TextLine *getNext() const { return next; } |
344 | | |
345 | | private: |
346 | | std::pair<int, int> getLineBounds(const PDFRectangle &area) const; |
347 | | |
348 | | TextBlock *blk; // parent block |
349 | | int rot; // text rotation |
350 | | double xMin, xMax; // bounding box x coordinates |
351 | | double yMin, yMax; // bounding box y coordinates |
352 | | double base; // baseline x or y coordinate |
353 | | TextWord *words; // words in this line |
354 | | TextWord *lastWord; // last word in this line |
355 | | Unicode *text; // Unicode text of the line, including |
356 | | // spaces between words |
357 | | double *edge; // "near" edge x or y coord of each char |
358 | | // (plus one extra entry for the last char) |
359 | | int *col; // starting column number of each Unicode char |
360 | | int len; // number of Unicode chars |
361 | | int convertedLen; // total number of converted characters |
362 | | TextLine *next; // next line in block |
363 | | Unicode *normalized; // normalized form of Unicode text |
364 | | int normalized_len; // number of normalized Unicode chars |
365 | | int *normalized_idx; // indices of normalized chars into Unicode text |
366 | | Unicode *ascii_translation; // ascii translation from the normalized text |
367 | | int ascii_len; // length of ascii translation text |
368 | | int *ascii_idx; // indices of ascii chars into Unicode text of line |
369 | | |
370 | | friend class TextLineFrag; |
371 | | friend class TextBlock; |
372 | | friend class TextFlow; |
373 | | friend class TextWordList; |
374 | | friend class TextPage; |
375 | | |
376 | | friend class TextSelectionPainter; |
377 | | friend class TextSelectionSizer; |
378 | | friend class TextSelectionDumper; |
379 | | }; |
380 | | |
381 | | //------------------------------------------------------------------------ |
382 | | // TextBlock |
383 | | //------------------------------------------------------------------------ |
384 | | |
385 | | class TextBlock |
386 | | { |
387 | | public: |
388 | | TextBlock(TextPage *pageA, int rotA); |
389 | | ~TextBlock(); |
390 | | |
391 | | TextBlock(const TextBlock &) = delete; |
392 | | TextBlock &operator=(const TextBlock &) = delete; |
393 | | |
394 | | void addWord(TextWord *word); |
395 | | |
396 | | void coalesce(const UnicodeMap *uMap, double fixedPitch); |
397 | | |
398 | | // Update this block's priMin and priMax values, looking at <blk>. |
399 | | void updatePriMinMax(const TextBlock *blk); |
400 | | |
401 | | static bool cmpXYPrimaryRot(const TextBlock *blk1, const TextBlock *blk2); |
402 | | |
403 | | int primaryCmp(const TextBlock *blk) const; |
404 | | |
405 | | double secondaryDelta(const TextBlock *blk) const; |
406 | | |
407 | | // Returns true if <this> is below <blk>, relative to the page's |
408 | | // primary rotation. |
409 | | bool isBelow(const TextBlock *blk) const; |
410 | | |
411 | | void visitSelection(TextSelectionVisitor *visitor, const PDFRectangle &selection, SelectionStyle style); |
412 | | |
413 | | // Get the head of the linked list of TextLines. |
414 | 0 | const TextLine *getLines() const { return lines; } |
415 | | |
416 | | // Get the next TextBlock on the linked list. |
417 | 0 | const TextBlock *getNext() const { return next; } |
418 | | |
419 | | void getBBox(double *xMinA, double *yMinA, double *xMaxA, double *yMaxA) const |
420 | 0 | { |
421 | 0 | *xMinA = xMin; |
422 | 0 | *yMinA = yMin; |
423 | 0 | *xMaxA = xMax; |
424 | 0 | *yMaxA = yMax; |
425 | 0 | } |
426 | 0 | PDFRectangle getBBox() const { return { xMin, yMin, xMax, yMax }; } |
427 | | |
428 | 0 | int getLineCount() const { return nLines; } |
429 | | |
430 | | private: |
431 | | bool isBeforeByRule1(const TextBlock *blk1); |
432 | | bool isBeforeByRepeatedRule1(const TextBlock *blkList, const TextBlock *blk1); |
433 | | bool isBeforeByRule2(const TextBlock *blk1); |
434 | | |
435 | | int visitDepthFirst(TextBlock *blkList, int pos1, TextBlock **sorted, int sortPos, bool *visited); |
436 | | int visitDepthFirst(TextBlock *blkList, int pos1, TextBlock **sorted, int sortPos, bool *visited, TextBlock **cache, int cacheSize); |
437 | | |
438 | | TextPage *page; // the parent page |
439 | | int rot; // text rotation |
440 | | double xMin, xMax; // bounding box x coordinates |
441 | | double yMin, yMax; // bounding box y coordinates |
442 | | double priMin, priMax; // whitespace bounding box along primary axis |
443 | | double ExMin, ExMax; // extended bounding box x coordinates |
444 | | double EyMin, EyMax; // extended bounding box y coordinates |
445 | | int tableId; // id of table to which this block belongs |
446 | | bool tableEnd; // is this block at end of line of actual table |
447 | | |
448 | | TextPool *pool; // pool of words (used only until lines |
449 | | // are built) |
450 | | TextLine *lines; // linked list of lines |
451 | | TextLine *curLine; // most recently added line |
452 | | int nLines; // number of lines |
453 | | int charCount; // number of characters in the block |
454 | | int col; // starting column |
455 | | int nColumns; // number of columns in the block |
456 | | |
457 | | TextBlock *next; |
458 | | TextBlock *stackNext; |
459 | | |
460 | | friend class TextLine; |
461 | | friend class TextLineFrag; |
462 | | friend class TextFlow; |
463 | | friend class TextWordList; |
464 | | friend class TextPage; |
465 | | friend class TextSelectionPainter; |
466 | | friend class TextSelectionDumper; |
467 | | }; |
468 | | |
469 | | //------------------------------------------------------------------------ |
470 | | // TextFlow |
471 | | //------------------------------------------------------------------------ |
472 | | |
473 | | class TextFlow |
474 | | { |
475 | | public: |
476 | | TextFlow(TextPage *pageA, TextBlock *blk); |
477 | | ~TextFlow(); |
478 | | |
479 | | TextFlow(const TextFlow &) = delete; |
480 | | TextFlow &operator=(const TextFlow &) = delete; |
481 | | |
482 | | // Add a block to the end of this flow. |
483 | | void addBlock(TextBlock *blk); |
484 | | |
485 | | // Returns true if <blk> fits in the flow, i.e., (1) |
486 | | // it uses a font no larger than the last block added to the flow, |
487 | | // and (2) it fits within the flow's [priMin, priMax] along the |
488 | | // primary axis. |
489 | | bool blockFits(const TextBlock *blk) const; |
490 | | |
491 | | // Get the head of the linked list of TextBlocks. |
492 | 0 | const TextBlock *getBlocks() const { return blocks; } |
493 | | |
494 | | // Get the next TextFlow on the linked list. |
495 | 0 | const TextFlow *getNext() const { return next; } |
496 | | |
497 | | private: |
498 | | TextPage *page; // the parent page |
499 | | double xMin, xMax; // bounding box x coordinates |
500 | | double yMin, yMax; // bounding box y coordinates |
501 | | double priMin, priMax; // whitespace bounding box along primary axis |
502 | | TextBlock *blocks; // blocks in flow |
503 | | TextBlock *lastBlk; // last block in this flow |
504 | | TextFlow *next; |
505 | | |
506 | | friend class TextWordList; |
507 | | friend class TextPage; |
508 | | }; |
509 | | |
510 | | //------------------------------------------------------------------------ |
511 | | // TextWordList |
512 | | //------------------------------------------------------------------------ |
513 | | |
514 | | class POPPLER_PRIVATE_EXPORT TextWordList |
515 | | { |
516 | | public: |
517 | | // Build a flat word list, in content stream order (if |
518 | | // text->rawOrder is true), physical layout order (if <physLayout> |
519 | | // is true and text->rawOrder is false), or reading order (if both |
520 | | // flags are false). |
521 | | TextWordList(const TextPage *text, bool physLayout); |
522 | | |
523 | | ~TextWordList(); |
524 | | |
525 | | TextWordList(const TextWordList &) = delete; |
526 | | TextWordList &operator=(const TextWordList &) = delete; |
527 | | |
528 | 0 | const std::vector<TextWord *> &getWords() const { return words; } |
529 | | |
530 | | private: |
531 | | std::vector<TextWord *> words; |
532 | | }; |
533 | | |
534 | | class TextWordSelection |
535 | | { |
536 | | public: |
537 | 0 | TextWordSelection(const TextWord *wordA, int beginA, int endA) : word(wordA), begin(beginA), end(endA) { } |
538 | | |
539 | 0 | const TextWord *getWord() const { return word; } |
540 | 0 | int getBegin() const { return begin; } |
541 | 0 | int getEnd() const { return end; } |
542 | | |
543 | | private: |
544 | | const TextWord *word; |
545 | | int begin; |
546 | | int end; |
547 | | |
548 | | friend class TextSelectionPainter; |
549 | | friend class TextSelectionDumper; |
550 | | }; |
551 | | |
552 | | //------------------------------------------------------------------------ |
553 | | // TextPage |
554 | | //------------------------------------------------------------------------ |
555 | | |
556 | | class POPPLER_PRIVATE_EXPORT TextPage |
557 | | { |
558 | | public: |
559 | | // Constructor. |
560 | | explicit TextPage(bool rawOrderA, bool discardDiagA = false); |
561 | | |
562 | | TextPage(const TextPage &) = delete; |
563 | | TextPage &operator=(const TextPage &) = delete; |
564 | | // Destructor. |
565 | | ~TextPage(); |
566 | | |
567 | | // Start a new page. |
568 | | void startPage(const GfxState *state); |
569 | | |
570 | | // End the current page. |
571 | | void endPage(); |
572 | | |
573 | | // Update the current font. |
574 | | void updateFont(const GfxState *state); |
575 | | |
576 | | // Begin a new word. |
577 | | void beginWord(const GfxState *state); |
578 | | |
579 | | // Add a character to the current word. |
580 | | void addChar(const GfxState *state, double x, double y, double dx, double dy, CharCode c, int nBytes, const Unicode *u, int uLen); |
581 | | |
582 | | // Add <nChars> invisible characters. |
583 | | void incCharCount(int nChars); |
584 | | |
585 | | // End the current word, sorting it into the list of words. |
586 | | void endWord(); |
587 | | |
588 | | // Add a word, sorting it into the list of words. |
589 | | void addWord(TextWord *word); |
590 | | |
591 | | // Add a (potential) underline. |
592 | | void addUnderline(double x0, double y0, double x1, double y1); |
593 | | |
594 | | // Add a hyperlink. |
595 | | void addLink(int xMin, int yMin, int xMax, int yMax, AnnotLink *link); |
596 | | |
597 | | // Coalesce strings that look like parts of the same line. |
598 | | void coalesce(bool physLayout, double fixedPitch, bool doHTML); |
599 | | void coalesce(bool physLayout, double fixedPitch, bool doHTML, double minColSpacing1); |
600 | | |
601 | | // Find a string. If <startAtTop> is true, starts looking at the |
602 | | // top of the page; else if <startAtLast> is true, starts looking |
603 | | // immediately after the last find result; else starts looking at |
604 | | // <xMin>,<yMin>. If <stopAtBottom> is true, stops looking at the |
605 | | // bottom of the page; else if <stopAtLast> is true, stops looking |
606 | | // just before the last find result; else stops looking at |
607 | | // <xMax>,<yMax>. |
608 | | bool findText(const Unicode *s, int len, bool startAtTop, bool stopAtBottom, bool startAtLast, bool stopAtLast, bool caseSensitive, bool backward, bool wholeWord, double *xMin, double *yMin, double *xMax, double *yMax); |
609 | | |
610 | | // Adds new parameter ignoreDiacritics, which will do diacritics |
611 | | // insensitive search, i.e. ignore accents, umlauts, diaeresis,etc. |
612 | | // while matching. This option will be ignored if <s> contains characters |
613 | | // which are not pure ascii. |
614 | | bool findText(const Unicode *s, int len, bool startAtTop, bool stopAtBottom, bool startAtLast, bool stopAtLast, bool caseSensitive, bool ignoreDiacritics, bool backward, bool wholeWord, double *xMin, double *yMin, double *xMax, |
615 | | double *yMax); |
616 | | |
617 | | // Adds new parameter <matchAcrossLines>, which allows <s> to match on text |
618 | | // spanning from end of a line to the next line. In that case, the rect for |
619 | | // the part of match that falls on the next line will be stored in |
620 | | // <continueMatch>, and if hyphenation (i.e. ignoring hyphen at end of line) |
621 | | // was used while matching at the end of the line prior to <continueMatch>, |
622 | | // then <ignoredHyphen> will be true, otherwise will be false. |
623 | | // Only finding across two lines is supported, i.e. it won't match where <s> |
624 | | // spans more than two lines. |
625 | | // |
626 | | // <matchAcrossLines> will be ignored if <backward> is true (as that |
627 | | // combination has not been implemented yet). |
628 | | bool findText(const Unicode *s, int len, bool startAtTop, bool stopAtBottom, bool startAtLast, bool stopAtLast, bool caseSensitive, bool ignoreDiacritics, bool matchAcrossLines, bool backward, bool wholeWord, double *xMin, double *yMin, |
629 | | double *xMax, double *yMax, PDFRectangle *continueMatch, bool *ignoredHyphen); |
630 | | |
631 | | // Get the text which is inside the specified rectangle. |
632 | | // physical layout false and raw order false does not go well with a rectangle |
633 | | GooString getText(const std::optional<PDFRectangle> &area, EndOfLineKind textEOL, bool physLayout, EndOfLineHyphenMode hyphenMode) const; |
634 | | |
635 | | void visitSelection(TextSelectionVisitor *visitor, const PDFRectangle &selection, SelectionStyle style); |
636 | | |
637 | | void drawSelection(OutputDev *out, double scale, int rotation, const PDFRectangle &selection, SelectionStyle style, const GfxColor &glyph_color, const GfxColor &box_color, double box_opacity, bool draw_glyphs); |
638 | | |
639 | | std::vector<PDFRectangle *> *getSelectionRegion(const PDFRectangle &selection, SelectionStyle style, double scale); |
640 | | |
641 | | GooString getSelectionText(const PDFRectangle &selection, SelectionStyle style); |
642 | | |
643 | | [[nodiscard]] std::vector<std::vector<std::unique_ptr<TextWordSelection>>> getSelectionWords(const PDFRectangle &selection, SelectionStyle style); |
644 | | |
645 | | // Find a string by character position and length. If found, sets |
646 | | // the text bounding rectangle and returns true; otherwise returns |
647 | | // false. |
648 | | bool findCharRange(int pos, int length, double *xMin, double *yMin, double *xMax, double *yMax) const; |
649 | | |
650 | | // Dump contents of page to a file. |
651 | | void dump(void *outputStream, TextOutputFunc outputFunc, bool physLayout, EndOfLineKind textEOL, bool pageBreaks, bool suppressLastEol, std::optional<PDFRectangle> area, EndOfLineHyphenMode hyphenMode) const; |
652 | | |
653 | | // Get the head of the linked list of TextFlows. |
654 | 0 | const TextFlow *getFlows() const { return flows; } |
655 | | |
656 | | // If true, will combine characters when a base and combining |
657 | | // character are drawn on eachother. |
658 | | void setMergeCombining(bool merge); |
659 | | |
660 | | // Build a flat word list, in content stream order (if |
661 | | // this->rawOrder is true), physical layout order (if <physLayout> |
662 | | // is true and this->rawOrder is false), or reading order (if both |
663 | | // flags are false). |
664 | | std::unique_ptr<TextWordList> makeWordList(bool physLayout); |
665 | | |
666 | | private: |
667 | | void clear(); |
668 | | static void assignColumns(TextLineFrag *frags, int nFrags, bool rot); |
669 | | int dumpFragment(const Unicode *text, int len, const UnicodeMap *uMap, GooString *s) const; |
670 | | static void adjustRotation(TextLine *line, int start, int end, double *xMin, double *xMax, double *yMin, double *yMax); |
671 | | |
672 | | bool rawOrder; // keep text in content stream order |
673 | | bool discardDiag; // discard diagonal text |
674 | | bool mergeCombining; // merge when combining and base characters |
675 | | // are drawn on top of each other |
676 | | |
677 | | double pageWidth, pageHeight; // width and height of current page |
678 | | TextWord *curWord; // currently active string |
679 | | int charPos; // next character position (within content |
680 | | // stream) |
681 | | TextFontInfo *curFont; // current font |
682 | | double curFontSize; // current font size |
683 | | int nest; // current nesting level (for Type 3 fonts) |
684 | | int nTinyChars; // number of "tiny" chars seen so far |
685 | | bool lastCharOverlap; // set if the last added char overlapped the |
686 | | // previous char |
687 | | bool diagonal; // whether the current text is diagonal |
688 | | |
689 | | std::unique_ptr<TextPool> pools[4]; // a "pool" of TextWords for each rotation |
690 | | TextFlow *flows; // linked list of flows |
691 | | TextBlock **blocks; // array of blocks, in yx order |
692 | | int nBlocks; // number of blocks |
693 | | int primaryRot; // primary rotation |
694 | | bool primaryLR; // primary direction (true means L-to-R, |
695 | | // false means R-to-L) |
696 | | TextWord *rawWords; // list of words, in raw order (only if |
697 | | // rawOrder is set) |
698 | | TextWord *rawLastWord; // last word on rawWords list |
699 | | |
700 | | std::vector<std::unique_ptr<TextFontInfo>> fonts; // all font info objects used on this page |
701 | | |
702 | | double lastFindXMin, // coordinates of the last "find" result |
703 | | lastFindYMin; |
704 | | bool haveLastFind; |
705 | | |
706 | | std::vector<std::unique_ptr<TextUnderline>> underlines; |
707 | | std::vector<std::unique_ptr<TextLink>> links; |
708 | | |
709 | | friend class TextLine; |
710 | | friend class TextLineFrag; |
711 | | friend class TextBlock; |
712 | | friend class TextFlow; |
713 | | friend class TextWordList; |
714 | | friend class TextSelectionPainter; |
715 | | friend class TextSelectionDumper; |
716 | | }; |
717 | | |
718 | | //------------------------------------------------------------------------ |
719 | | // ActualText |
720 | | //------------------------------------------------------------------------ |
721 | | |
722 | | class POPPLER_PRIVATE_EXPORT ActualText |
723 | | { |
724 | | public: |
725 | | /// Create an ActualText |
726 | | /// \note: \param out must be kept alive during the lifetime of this object |
727 | | explicit ActualText(TextPage *out); |
728 | | ~ActualText(); |
729 | | |
730 | | ActualText(const ActualText &) = delete; |
731 | | ActualText &operator=(const ActualText &) = delete; |
732 | | |
733 | | void addChar(const GfxState *state, double x, double y, double dx, double dy, CharCode c, int nBytes, const Unicode *u, int uLen); |
734 | | void begin(const GfxState *state, const std::string &text); |
735 | | void end(const GfxState *state); |
736 | | |
737 | | private: |
738 | | TextPage *text; |
739 | | |
740 | | std::optional<std::string> actualText; // replacement text for the span |
741 | | double actualTextX0; |
742 | | double actualTextY0; |
743 | | double actualTextX1; |
744 | | double actualTextY1; |
745 | | int actualTextNBytes; |
746 | | }; |
747 | | |
748 | | //------------------------------------------------------------------------ |
749 | | // TextOutputDev |
750 | | //------------------------------------------------------------------------ |
751 | | |
752 | | class POPPLER_PRIVATE_EXPORT TextOutputDev : public OutputDev |
753 | | { |
754 | | public: |
755 | | static double minColSpacing1_default; |
756 | | |
757 | | // Open a text output file. If <fileName> is NULL, no file is |
758 | | // written (this is useful, e.g., for searching text). If |
759 | | // <physLayoutA> is true, the original physical layout of the text |
760 | | // is maintained. If <rawOrder> is true, the text is kept in |
761 | | // content stream order. If <discardDiag> is true, diagonal text |
762 | | // is removed from output. |
763 | | TextOutputDev(const char *fileName, bool physLayoutA, double fixedPitchA, bool rawOrderA, bool append, bool discardDiagA = false); |
764 | | |
765 | | // Create a TextOutputDev which will write to a generic stream. If |
766 | | // <physLayoutA> is true, the original physical layout of the text |
767 | | // is maintained. If <rawOrder> is true, the text is kept in |
768 | | // content stream order. If <discardDiag> is true, diagonal text |
769 | | // is removed from output. |
770 | | TextOutputDev(TextOutputFunc func, void *stream, bool physLayoutA, double fixedPitchA, bool rawOrderA, bool discardDiagA = false); |
771 | | |
772 | | // Destructor. |
773 | | ~TextOutputDev() override; |
774 | | |
775 | | // Check if file was successfully created. |
776 | 0 | virtual bool isOk() { return ok; } |
777 | | |
778 | | //---- get info about output device |
779 | | |
780 | | // Does this device use upside-down coordinates? |
781 | | // (Upside-down means (0,0) is the top left corner of the page.) |
782 | 7.56k | bool upsideDown() override { return true; } |
783 | | |
784 | | // Does this device use drawChar() or drawString()? |
785 | 555k | bool useDrawChar() override { return true; } |
786 | | |
787 | | // Does this device use beginType3Char/endType3Char? Otherwise, |
788 | | // text in Type 3 fonts will be drawn with drawChar/drawString. |
789 | 0 | bool interpretType3Chars() override { return false; } |
790 | | |
791 | | // Does this device need non-text content? |
792 | 0 | bool needNonText() override { return false; } |
793 | | |
794 | | // Does this device require incCharCount to be called for text on |
795 | | // non-shown layers? |
796 | 0 | bool needCharCount() override { return true; } |
797 | | |
798 | | //----- initialization and control |
799 | | |
800 | | // Start a page. |
801 | | void startPage(int pageNum, GfxState *state, XRef *xref) override; |
802 | | |
803 | | // End a page. |
804 | | void endPage() override; |
805 | | |
806 | | //----- save/restore graphics state |
807 | | void restoreState(GfxState *state) override; |
808 | | |
809 | | //----- update text state |
810 | | void updateFont(GfxState *state) override; |
811 | | |
812 | | //----- text drawing |
813 | | void beginString(GfxState *state, const std::string &s) override; |
814 | | void endString(GfxState *state) override; |
815 | | void drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode c, int nBytes, const Unicode *u, int uLen) override; |
816 | | void incCharCount(int nChars) override; |
817 | | void beginActualText(GfxState *state, const std::string &text) override; |
818 | | void endActualText(GfxState *state) override; |
819 | | |
820 | | //----- path painting |
821 | | void stroke(GfxState *state) override; |
822 | | void fill(GfxState *state) override; |
823 | | void eoFill(GfxState *state) override; |
824 | | |
825 | | //----- link borders |
826 | | void processLink(AnnotLink *link) override; |
827 | | |
828 | | //----- special access |
829 | | |
830 | | // Find a string. If <startAtTop> is true, starts looking at the |
831 | | // top of the page; else if <startAtLast> is true, starts looking |
832 | | // immediately after the last find result; else starts looking at |
833 | | // <xMin>,<yMin>. If <stopAtBottom> is true, stops looking at the |
834 | | // bottom of the page; else if <stopAtLast> is true, stops looking |
835 | | // just before the last find result; else stops looking at |
836 | | // <xMax>,<yMax>. |
837 | | bool findText(const Unicode *s, int len, bool startAtTop, bool stopAtBottom, bool startAtLast, bool stopAtLast, bool caseSensitive, bool backward, bool wholeWord, double *xMin, double *yMin, double *xMax, double *yMax) const; |
838 | | |
839 | | // Get the text which is inside the specified rectangle. |
840 | | // You can only give an area if either physLayout or rawOrder are true |
841 | | GooString getText(const std::optional<PDFRectangle> &area) const; |
842 | | |
843 | | // Find a string by character position and length. If found, sets |
844 | | // the text bounding rectangle and returns true; otherwise returns |
845 | | // false. |
846 | | bool findCharRange(int pos, int length, double *xMin, double *yMin, double *xMax, double *yMax) const; |
847 | | |
848 | | void drawSelection(OutputDev *out, double scale, int rotation, const PDFRectangle &selection, SelectionStyle style, const GfxColor &glyph_color, const GfxColor &box_color, double box_opacity, bool draw_glyphs); |
849 | | |
850 | | std::vector<PDFRectangle *> *getSelectionRegion(const PDFRectangle &selection, SelectionStyle style, double scale); |
851 | | |
852 | | GooString getSelectionText(const PDFRectangle &selection, SelectionStyle style); |
853 | | |
854 | | // If true, will combine characters when a base and combining |
855 | | // character are drawn on eachother. |
856 | | void setMergeCombining(bool merge); |
857 | | |
858 | | // Build a flat word list, in content stream order (if |
859 | | // this->rawOrder is true), physical layout order (if |
860 | | // this->physLayout is true and this->rawOrder is false), or reading |
861 | | // order (if both flags are false). |
862 | | std::unique_ptr<TextWordList> makeWordList(); |
863 | | |
864 | | // Returns the TextPage object for the last rasterized page, |
865 | | // transferring ownership to the caller. |
866 | | std::unique_ptr<TextPage> takeText(); |
867 | | |
868 | | // Turn extra processing for HTML conversion on or off. |
869 | 0 | void enableHTMLExtras(bool doHTMLA) { doHTML = doHTMLA; } |
870 | | |
871 | | // Get the head of the linked list of TextFlows for the |
872 | | // last rasterized page. |
873 | | const TextFlow *getFlows() const; |
874 | | |
875 | 0 | void setTextEOL(EndOfLineKind textEOLA) { textEOL = textEOLA; } |
876 | 0 | void setTextPageBreaks(bool textPageBreaksA) { textPageBreaks = textPageBreaksA; } |
877 | 0 | double getMinColSpacing1() const { return minColSpacing1; } |
878 | 0 | void setMinColSpacing1(double val) { minColSpacing1 = val; } |
879 | 0 | void setEndOfLineHyphenMode(EndOfLineHyphenMode mode) { hyphenMode = mode; } |
880 | | |
881 | | private: |
882 | | TextOutputFunc outputFunc; // output function |
883 | | void *outputStream; // output stream |
884 | | bool needClose; // need to close the output file? |
885 | | // (only if outputStream is a FILE*) |
886 | | std::unique_ptr<TextPage> text; // text for the current page |
887 | | bool physLayout; // maintain original physical layout when |
888 | | // dumping text |
889 | | double fixedPitch; // if physLayout is true and this is non-zero, |
890 | | // assume fixed-pitch characters with this |
891 | | // width |
892 | | double minColSpacing1; // see default value defined with same name at TextOutputDev.cc |
893 | | bool rawOrder; // keep text in content stream order |
894 | | bool discardDiag; // Diagonal text, i.e., text that is not close to one of the |
895 | | // 0, 90, 180, or 270 degree axes, is discarded. This is useful |
896 | | // to skip watermarks drawn on top of body text, etc. |
897 | | bool doHTML; // extra processing for HTML conversion |
898 | | bool ok; // set up ok? |
899 | | bool textPageBreaks; // insert end-of-page markers? |
900 | | EndOfLineKind textEOL; // type of EOL marker to use |
901 | | EndOfLineHyphenMode hyphenMode = EndOfLineHyphenMode::RemoveAll; |
902 | | |
903 | | std::unique_ptr<ActualText> actualText; |
904 | | }; |
905 | | |
906 | | #endif |