Coverage Report

Created: 2026-07-30 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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