Coverage Report

Created: 2026-10-14 08:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/cpp/poppler-page.cpp
Line
Count
Source
1
/*
2
 * Copyright (C) 2009-2010, Pino Toscano <pino@kde.org>
3
 * Copyright (C) 2017-2020, 2025, 2026, Albert Astals Cid <aacid@kde.org>
4
 * Copyright (C) 2017, Jason Alan Palmer <jalanpalmer@gmail.com>
5
 * Copyright (C) 2018, 2020, Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
6
 * Copyright (C) 2018, 2020, Adam Reichold <adam.reichold@t-online.de>
7
 * Copyright (C) 2018, Zsombor Hollay-Horvath <hollay.horvath@gmail.com>
8
 * Copyright (C) 2018, Aleksey Nikolaev <nae202@gmail.com>
9
 * Copyright (C) 2020, Jiri Jakes <freedesktop@jirijakes.eu>
10
 * Copyright (C) 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 2, or (at your option)
15
 * any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program; if not, write to the Free Software
24
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
25
 */
26
27
/**
28
 \file poppler-page.h
29
 */
30
#include "poppler-page.h"
31
#include "poppler-page-transition.h"
32
33
#include "poppler-document-private.h"
34
#include "poppler-page-private.h"
35
#include "poppler-private.h"
36
#include "poppler-font-private.h"
37
#include "poppler-font.h"
38
39
#include "TextOutputDev.h"
40
41
#include <memory>
42
#include <utility>
43
44
using namespace poppler;
45
46
243k
page_private::page_private(document_private *_doc, int _index) : doc(_doc), page(doc->doc->getCatalog()->getPage(_index + 1)), index(_index) { }
47
48
page_private::~page_private()
49
243k
{
50
243k
    delete transition;
51
243k
}
52
53
void page_private::init_font_info_cache()
54
759k
{
55
759k
    if (font_info_cache_initialized) {
56
752k
        return;
57
752k
    }
58
59
6.91k
    poppler::font_iterator it(index, doc);
60
61
6.91k
    if (it.has_next()) {
62
6.91k
        font_info_cache = it.next();
63
6.91k
    }
64
65
6.91k
    font_info_cache_initialized = true;
66
6.91k
}
67
68
/**
69
 \class poppler::page poppler-page.h "poppler/cpp/poppler-page.h"
70
71
 A page in a PDF %document.
72
 */
73
74
/**
75
 \enum poppler::page::orientation_enum
76
77
 The possible orientation of a page.
78
*/
79
80
/**
81
 \enum poppler::page::search_direction_enum
82
83
 The direction/action to follow when performing a text search.
84
*/
85
86
/**
87
 \enum poppler::page::text_layout_enum
88
89
 A layout of the text of a page.
90
*/
91
92
243k
page::page(document_private *doc, int index) : d(new page_private(doc, index)) { }
93
94
/**
95
 Destructor.
96
 */
97
page::~page()
98
243k
{
99
243k
    delete d;
100
243k
}
101
102
/**
103
 \returns the orientation of the page
104
 */
105
page::orientation_enum page::orientation() const
106
0
{
107
0
    const int rotation = d->page->getRotate();
108
0
    switch (rotation) {
109
0
    case 90:
110
0
        return landscape;
111
0
        break;
112
0
    case 180:
113
0
        return upside_down;
114
0
        break;
115
0
    case 270:
116
0
        return seascape;
117
0
        break;
118
0
    default:
119
0
        return portrait;
120
0
    }
121
0
}
122
123
/**
124
 The eventual duration the page can be hinted to be shown in a presentation.
125
126
 If this value is positive (usually different than -1) then a PDF viewer, when
127
 showing the page in a presentation, should show the page for at most for this
128
 number of seconds, and then switch to the next page (if any). Note this is
129
 purely a presentation attribute, it has no influence on the behaviour.
130
131
 \returns the duration time (in seconds) of the page
132
 */
133
double page::duration() const
134
0
{
135
0
    return d->page->getDuration();
136
0
}
137
138
/**
139
 Returns the size of one rect of the page.
140
141
 \returns the size of the specified page rect
142
 */
143
rectf page::page_rect(page_box_enum box) const
144
11.9k
{
145
11.9k
    std::optional<PDFRectangle> r;
146
11.9k
    switch (box) {
147
0
    case media_box:
148
0
        r = d->page->getMediaBox();
149
0
        break;
150
11.9k
    case crop_box:
151
11.9k
        r = d->page->getCropBox();
152
11.9k
        break;
153
0
    case bleed_box:
154
0
        r = d->page->getBleedBox();
155
0
        break;
156
0
    case trim_box:
157
0
        r = d->page->getTrimBox();
158
0
        break;
159
0
    case art_box:
160
0
        r = d->page->getArtBox();
161
0
        break;
162
11.9k
    }
163
11.9k
    if (r) {
164
11.9k
        return detail::pdfrectangle_to_rectf(*r);
165
11.9k
    }
166
0
    return rectf();
167
11.9k
}
168
169
/**
170
 \returns the label of the page, if any
171
 */
172
ustring page::label() const
173
33.4k
{
174
33.4k
    std::string label;
175
33.4k
    if (!d->doc->doc->getCatalog()->indexToLabel(d->index, &label)) {
176
400
        return ustring();
177
400
    }
178
179
33.0k
    return detail::unicode_string_to_ustring(label);
180
33.4k
}
181
182
/**
183
 The transition from this page to the next one.
184
185
 If it is set, then a PDF viewer in a presentation should perform the
186
 specified transition effect when switching from this page to the next one.
187
188
 \returns the transition effect for the switch to the next page, if any
189
 */
190
page_transition *page::transition() const
191
0
{
192
0
    if (!d->transition) {
193
0
        Object o = d->page->getTrans();
194
0
        if (o.isDict()) {
195
0
            d->transition = new page_transition(&o);
196
0
        }
197
0
    }
198
0
    return d->transition;
199
0
}
200
201
/**
202
 Search the page for some text.
203
204
 \param text the text to search
205
 \param[in,out] r the area where to start search, which will be set to the area
206
                  of the match (if any)
207
 \param direction in which direction search for text
208
 \param case_sensitivity whether search in a case sensitive way
209
 \param rotation the rotation assumed for the page
210
 */
211
bool page::search(const ustring &text, rectf &r, search_direction_enum direction, case_sensitivity_enum case_sensitivity, rotation_enum rotation) const
212
11.9k
{
213
11.9k
    const size_t len = text.length();
214
215
11.9k
    if (len == 0) {
216
942
        return false;
217
942
    }
218
219
10.9k
    std::vector<Unicode> u(len);
220
169k
    for (size_t i = 0; i < len; ++i) {
221
158k
        u[i] = text[i];
222
158k
    }
223
224
10.9k
    const bool sCase = case_sensitivity == case_sensitive;
225
10.9k
    const int rotation_value = static_cast<int>(rotation) * 90;
226
227
10.9k
    bool found = false;
228
10.9k
    double rect_left = r.left();
229
10.9k
    double rect_top = r.top();
230
10.9k
    double rect_right = r.right();
231
10.9k
    double rect_bottom = r.bottom();
232
233
10.9k
    TextOutputDev td(nullptr, true, 0, false, false);
234
10.9k
    d->doc->doc->displayPage(&td, d->index + 1, 72, 72, rotation_value, false, true, false);
235
10.9k
    std::unique_ptr<TextPage> text_page = td.takeText();
236
237
10.9k
    switch (direction) {
238
10.9k
    case search_from_top:
239
10.9k
        found = text_page->findText(u.data(), len, true, true, false, false, sCase, false, false, &rect_left, &rect_top, &rect_right, &rect_bottom);
240
10.9k
        break;
241
0
    case search_next_result:
242
0
        found = text_page->findText(u.data(), len, false, true, true, false, sCase, false, false, &rect_left, &rect_top, &rect_right, &rect_bottom);
243
0
        break;
244
0
    case search_previous_result:
245
0
        found = text_page->findText(u.data(), len, false, true, true, false, sCase, true, false, &rect_left, &rect_top, &rect_right, &rect_bottom);
246
0
        break;
247
10.9k
    }
248
249
10.9k
    r.set_left(rect_left);
250
10.9k
    r.set_top(rect_top);
251
10.9k
    r.set_right(rect_right);
252
10.9k
    r.set_bottom(rect_bottom);
253
254
10.9k
    return found;
255
10.9k
}
256
257
/**
258
 Returns the text in the page, in its physical layout.
259
260
 \param r if not empty, it will be extracted the text in it; otherwise, the
261
          text of the whole page
262
263
 \returns the text of the page in the specified rect or in the whole page
264
 */
265
ustring page::text(const rectf &r) const
266
0
{
267
0
    return text(r, physical_layout);
268
0
}
269
270
static void appendToGooString(void *stream, const char *text, int len)
271
0
{
272
0
    (static_cast<GooString *>(stream))->append(text, len);
273
0
}
274
275
/**
276
 Returns the text in the page.
277
278
 \param rect if not empty, it will be extracted the text in it; otherwise, the
279
             text of the whole page
280
 \param layout_mode the layout of the text. non_raw_non_physical_layout is not
281
                    supported together with a non empty rect
282
283
 \returns the text of the page in the specified rect or in the whole page
284
285
 \since 0.16
286
 */
287
ustring page::text(const rectf &r, text_layout_enum layout_mode) const
288
0
{
289
0
    GooString out;
290
0
    const bool use_raw_order = (layout_mode == raw_order_layout);
291
0
    const bool use_physical_layout = (layout_mode == physical_layout);
292
0
    TextOutputDev td(&appendToGooString, &out, use_physical_layout, 0, use_raw_order, false);
293
0
    if (r.is_empty()) {
294
0
        d->doc->doc->displayPage(&td, d->index + 1, 72, 72, 0, false, true, false);
295
0
    } else {
296
0
        if (layout_mode == non_raw_non_physical_layout) {
297
0
            detail::user_debug_function("non_raw_non_physical_layout is not supported together with a non empty rect", detail::debug_closure);
298
0
            return {};
299
0
        }
300
0
        d->doc->doc->displayPageSlice(&td, d->index + 1, 72, 72, 0, false, true, false, r.left(), r.top(), r.width(), r.height());
301
0
    }
302
0
    return ustring::from_utf8(out.c_str());
303
0
}
304
305
/*
306
 * text_box_font_info object for text_box
307
 */
308
1.51M
text_box_font_info_data::~text_box_font_info_data() = default;
309
310
/*
311
 * text_box object for page::text_list()
312
 */
313
759k
text_box_data::~text_box_data() = default;
314
315
1.51M
text_box::~text_box() = default;
316
317
0
text_box &text_box::operator=(text_box &&a) noexcept = default;
318
759k
text_box::text_box(text_box &&a) noexcept = default;
319
320
759k
text_box::text_box(text_box_data *data) : m_data { data } { }
321
322
ustring text_box::text() const
323
0
{
324
0
    return m_data->text;
325
0
}
326
327
rectf text_box::bbox() const
328
0
{
329
0
    return m_data->bbox;
330
0
}
331
332
int text_box::rotation() const
333
0
{
334
0
    return m_data->rotation;
335
0
}
336
337
rectf text_box::char_bbox(size_t i) const
338
0
{
339
0
    if (i < m_data->char_bboxes.size()) {
340
0
        return m_data->char_bboxes[i];
341
0
    }
342
0
    return rectf(0, 0, 0, 0);
343
0
}
344
345
bool text_box::has_space_after() const
346
0
{
347
0
    return m_data->has_space_after;
348
0
}
349
350
bool text_box::has_font_info() const
351
0
{
352
0
    return (m_data->text_box_font != nullptr);
353
0
}
354
355
text_box::writing_mode_enum text_box::get_wmode(int i) const
356
0
{
357
0
    if (this->has_font_info()) {
358
0
        return m_data->text_box_font->wmodes[i];
359
0
    }
360
0
    return text_box::invalid_wmode;
361
0
}
362
363
double text_box::get_font_size() const
364
0
{
365
0
    if (this->has_font_info()) {
366
0
        return m_data->text_box_font->font_size;
367
0
    }
368
0
    return -1;
369
0
}
370
371
std::string text_box::get_font_name(int i) const
372
0
{
373
0
    if (!this->has_font_info()) {
374
0
        return std::string("*ignored*");
375
0
    }
376
377
0
    int j = m_data->text_box_font->glyph_to_cache_index[i];
378
0
    if (j < 0) {
379
0
        return std::string("");
380
0
    }
381
0
    return m_data->text_box_font->font_info_cache[j].name();
382
0
}
383
384
std::vector<text_box> page::text_list(int opt_flag) const
385
16.4k
{
386
16.4k
    std::vector<text_box> output_list;
387
388
    /* config values are same with Qt5 Page::TextList() */
389
16.4k
    auto output_dev = std::make_unique<TextOutputDev>(nullptr, /* char* fileName */
390
16.4k
                                                      false, /* bool physLayoutA */
391
16.4k
                                                      0, /* double fixedPitchA */
392
16.4k
                                                      false, /* bool rawOrderA */
393
16.4k
                                                      false /* bool append */
394
16.4k
    );
395
396
    /*
397
     * config values are same with Qt5 Page::TextList(),
398
     * but rotation is fixed to zero.
399
     * Few people use non-zero values.
400
     */
401
16.4k
    d->doc->doc->displayPageSlice(output_dev.get(), d->index + 1, /* page */
402
16.4k
                                  72, 72, 0, /* hDPI, vDPI, rot */
403
16.4k
                                  false, false, false, /* useMediaBox, crop, printing */
404
16.4k
                                  -1, -1, -1, -1, /* sliceX, sliceY, sliceW, sliceH */
405
16.4k
                                  nullptr, nullptr, /* abortCheckCbk(), abortCheckCbkData */
406
16.4k
                                  nullptr, nullptr, /* annotDisplayDecideCbk(), annotDisplayDecideCbkData */
407
16.4k
                                  true); /* copyXRef */
408
409
16.4k
    const std::unique_ptr<TextWordList> word_list = output_dev->makeWordList();
410
411
16.4k
    const std::vector<TextWord *> &words = word_list->getWords();
412
16.4k
    output_list.reserve(words.size());
413
759k
    for (const TextWord *word : words) {
414
759k
        const std::unique_ptr<std::string> wordText = word->getText();
415
759k
        const ustring ustr = ustring::from_utf8(wordText->c_str());
416
417
759k
        double xMin, yMin, xMax, yMax;
418
759k
        word->getBBox(&xMin, &yMin, &xMax, &yMax);
419
420
759k
        text_box tb { new text_box_data { .text = ustr, .bbox = { xMin, yMin, xMax - xMin, yMax - yMin }, .rotation = word->getRotation(), .char_bboxes = {}, .has_space_after = word->hasSpaceAfter(), .text_box_font = nullptr } };
421
422
759k
        std::unique_ptr<text_box_font_info_data> tb_font_info;
423
759k
        if (opt_flag & page::text_list_include_font) {
424
759k
            d->init_font_info_cache();
425
426
759k
            tb_font_info = std::make_unique<text_box_font_info_data>(text_box_font_info_data {
427
759k
                    .font_size = word->getFontSize(), // double font_size
428
759k
                    .wmodes = {}, // std::vector<text_box::writing_mode> wmodes;
429
759k
                    .font_info_cache = d->font_info_cache, // std::vector<font_info> font_info_cache;
430
759k
                    .glyph_to_cache_index = {} // std::vector<int> glyph_to_cache_index;
431
759k
            });
432
759k
        };
433
434
759k
        tb.m_data->char_bboxes.reserve(word->getLength());
435
4.21M
        for (int j = 0; j < word->getLength(); j++) {
436
3.45M
            word->getCharBBox(j, &xMin, &yMin, &xMax, &yMax);
437
3.45M
            tb.m_data->char_bboxes.emplace_back(xMin, yMin, xMax - xMin, yMax - yMin);
438
3.45M
        }
439
440
759k
        if (tb_font_info && d->font_info_cache_initialized) {
441
759k
            tb_font_info->glyph_to_cache_index.reserve(word->getLength());
442
4.21M
            for (int j = 0; j < word->getLength(); j++) {
443
3.45M
                const TextFontInfo *cur_text_font_info = word->getFontInfo(j);
444
445
3.45M
                switch (cur_text_font_info->getWMode()) {
446
3.45M
                case GfxFont::WritingMode::Horizontal:
447
3.45M
                    tb_font_info->wmodes.push_back(text_box::horizontal_wmode);
448
3.45M
                    break;
449
4.79k
                case GfxFont::WritingMode::Vertical:
450
4.79k
                    tb_font_info->wmodes.push_back(text_box::vertical_wmode);
451
4.79k
                    break;
452
3.45M
                };
453
454
3.45M
                tb_font_info->glyph_to_cache_index.push_back(-1);
455
8.94M
                for (size_t k = 0; k < tb_font_info->font_info_cache.size(); k++) {
456
8.94M
                    if (cur_text_font_info->matches(&(tb_font_info->font_info_cache[k].d->ref))) {
457
3.45M
                        tb_font_info->glyph_to_cache_index[j] = k;
458
3.45M
                        break;
459
3.45M
                    }
460
8.94M
                }
461
3.45M
            }
462
759k
            tb.m_data->text_box_font = std::move(tb_font_info);
463
759k
        }
464
465
759k
        output_list.push_back(std::move(tb));
466
759k
    }
467
468
16.4k
    return output_list;
469
16.4k
}
470
471
std::vector<text_box> page::text_list() const
472
0
{
473
0
    return text_list(0);
474
0
}