Coverage Report

Created: 2026-06-07 08:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/glib/poppler-page.cc
Line
Count
Source
1
/* poppler-page.cc: glib wrapper for poppler
2
 * Copyright (C) 2005, Red Hat, Inc.
3
 * Copyright (C) 2025, 2026 Lucas Baudin <lucas.baudin@ensae.fr>
4
 * Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
5
 * Copyright (C) 2025 Nelson Benítez León <nbenitezl@gmail.com>
6
 * Copyright (C) 2025, 2026 Albert Astals Cid <aacid@kde.org>
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2, or (at your option)
11
 * any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
21
 */
22
23
#include "config.h"
24
#include <cmath>
25
26
#ifndef __GI_SCANNER__
27
#    include <GlobalParams.h>
28
#    include <PDFDoc.h>
29
#    include <Outline.h>
30
#    include <ErrorCodes.h>
31
#    include <UnicodeMap.h>
32
#    include <GfxState.h>
33
#    include <PageTransition.h>
34
#    include <BBoxOutputDev.h>
35
#    include <goo/gmem.h>
36
#endif
37
38
#include "poppler.h"
39
#include "poppler-private.h"
40
41
/**
42
 * SECTION:poppler-page
43
 * @short_description: Information about a page in a document
44
 * @title: PopplerPage
45
 */
46
47
namespace {
48
enum
49
{
50
    PROP_0,
51
    PROP_LABEL
52
};
53
}
54
55
static PopplerRectangleExtended *poppler_rectangle_extended_new();
56
57
struct _PopplerPageClass
58
{
59
    GObjectClass parent_class;
60
};
61
using PopplerPageClass = _PopplerPageClass;
62
63
0
G_DEFINE_TYPE(PopplerPage, poppler_page, G_TYPE_OBJECT)
64
0
65
0
PopplerPage *_poppler_page_new(PopplerDocument *document, Page *page, int index)
66
13.1k
{
67
13.1k
    PopplerPage *poppler_page;
68
69
13.1k
    g_return_val_if_fail(POPPLER_IS_DOCUMENT(document), NULL);
70
71
13.1k
    poppler_page = static_cast<PopplerPage *>(g_object_new(POPPLER_TYPE_PAGE, nullptr, NULL));
72
13.1k
    poppler_page->document = static_cast<PopplerDocument *> g_object_ref(document);
73
13.1k
    poppler_page->page = page;
74
13.1k
    poppler_page->index = index;
75
76
13.1k
    return poppler_page;
77
13.1k
}
78
79
static void poppler_page_finalize(GObject *object)
80
13.1k
{
81
13.1k
    PopplerPage *page = POPPLER_PAGE(object);
82
83
13.1k
    g_object_unref(page->document);
84
13.1k
    page->document = nullptr;
85
13.1k
    page->text.reset();
86
87
    /* page->page is owned by the document */
88
89
13.1k
    G_OBJECT_CLASS(poppler_page_parent_class)->finalize(object);
90
13.1k
}
91
92
/**
93
 * poppler_page_get_size:
94
 * @page: A #PopplerPage
95
 * @width: (out) (allow-none): return location for the width of @page
96
 * @height: (out) (allow-none): return location for the height of @page
97
 *
98
 * Gets the size of @page at the current scale and rotation.
99
 **/
100
void poppler_page_get_size(PopplerPage *page, double *width, double *height)
101
11.2k
{
102
11.2k
    double page_width, page_height;
103
11.2k
    int rotate;
104
105
11.2k
    g_return_if_fail(POPPLER_IS_PAGE(page));
106
107
11.2k
    rotate = page->page->getRotate();
108
11.2k
    if (rotate == 90 || rotate == 270) {
109
527
        page_height = page->page->getCropWidth();
110
527
        page_width = page->page->getCropHeight();
111
10.7k
    } else {
112
10.7k
        page_width = page->page->getCropWidth();
113
10.7k
        page_height = page->page->getCropHeight();
114
10.7k
    }
115
116
11.2k
    if (width != nullptr) {
117
1.49k
        *width = page_width;
118
1.49k
    }
119
11.2k
    if (height != nullptr) {
120
11.2k
        *height = page_height;
121
11.2k
    }
122
11.2k
}
123
124
/**
125
 * poppler_page_get_index:
126
 * @page: a #PopplerPage
127
 *
128
 * Returns the index of @page
129
 *
130
 * Return value: index value of @page
131
 **/
132
int poppler_page_get_index(PopplerPage *page)
133
0
{
134
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), 0);
135
136
0
    return page->index;
137
0
}
138
139
/**
140
 * poppler_page_get_label:
141
 * @page: a #PopplerPage
142
 *
143
 * Returns the label of @page. Note that page labels
144
 * and page indices might not coincide.
145
 *
146
 * Return value: a new allocated string containing the label of @page,
147
 *               or %NULL if @page doesn't have a label
148
 *
149
 * Since: 0.16
150
 **/
151
gchar *poppler_page_get_label(PopplerPage *page)
152
0
{
153
0
    GooString label;
154
155
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
156
157
0
    page->document->doc->getCatalog()->indexToLabel(page->index, &label);
158
0
    return _poppler_goo_string_to_utf8(label.toStr());
159
0
}
160
161
/**
162
 * poppler_page_get_duration:
163
 * @page: a #PopplerPage
164
 *
165
 * Returns the duration of @page
166
 *
167
 * Return value: duration in seconds of @page or -1.
168
 **/
169
double poppler_page_get_duration(PopplerPage *page)
170
0
{
171
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), -1);
172
173
0
    return page->page->getDuration();
174
0
}
175
176
/**
177
 * poppler_page_get_transition:
178
 * @page: a #PopplerPage
179
 *
180
 * Returns the transition effect of @page
181
 *
182
 * Return value: a #PopplerPageTransition or %NULL.
183
 **/
184
PopplerPageTransition *poppler_page_get_transition(PopplerPage *page)
185
0
{
186
0
    PageTransition *trans;
187
0
    PopplerPageTransition *transition;
188
189
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
190
191
0
    Object obj = page->page->getTrans();
192
0
    trans = new PageTransition(&obj);
193
194
0
    if (!trans->isOk()) {
195
0
        delete trans;
196
0
        return nullptr;
197
0
    }
198
199
0
    transition = poppler_page_transition_new();
200
201
0
    switch (trans->getType()) {
202
0
    case transitionReplace:
203
0
        transition->type = POPPLER_PAGE_TRANSITION_REPLACE;
204
0
        break;
205
0
    case transitionSplit:
206
0
        transition->type = POPPLER_PAGE_TRANSITION_SPLIT;
207
0
        break;
208
0
    case transitionBlinds:
209
0
        transition->type = POPPLER_PAGE_TRANSITION_BLINDS;
210
0
        break;
211
0
    case transitionBox:
212
0
        transition->type = POPPLER_PAGE_TRANSITION_BOX;
213
0
        break;
214
0
    case transitionWipe:
215
0
        transition->type = POPPLER_PAGE_TRANSITION_WIPE;
216
0
        break;
217
0
    case transitionDissolve:
218
0
        transition->type = POPPLER_PAGE_TRANSITION_DISSOLVE;
219
0
        break;
220
0
    case transitionGlitter:
221
0
        transition->type = POPPLER_PAGE_TRANSITION_GLITTER;
222
0
        break;
223
0
    case transitionFly:
224
0
        transition->type = POPPLER_PAGE_TRANSITION_FLY;
225
0
        break;
226
0
    case transitionPush:
227
0
        transition->type = POPPLER_PAGE_TRANSITION_PUSH;
228
0
        break;
229
0
    case transitionCover:
230
0
        transition->type = POPPLER_PAGE_TRANSITION_COVER;
231
0
        break;
232
0
    case transitionUncover:
233
0
        transition->type = POPPLER_PAGE_TRANSITION_UNCOVER;
234
0
        break;
235
0
    case transitionFade:
236
0
        transition->type = POPPLER_PAGE_TRANSITION_FADE;
237
0
        break;
238
0
    default:
239
0
        g_assert_not_reached();
240
0
    }
241
242
0
    transition->alignment = (trans->getAlignment() == transitionHorizontal) ? POPPLER_PAGE_TRANSITION_HORIZONTAL : POPPLER_PAGE_TRANSITION_VERTICAL;
243
244
0
    transition->direction = (trans->getDirection() == transitionInward) ? POPPLER_PAGE_TRANSITION_INWARD : POPPLER_PAGE_TRANSITION_OUTWARD;
245
246
0
    transition->duration = trans->getDuration();
247
0
    transition->duration_real = trans->getDuration();
248
0
    transition->angle = trans->getAngle();
249
0
    transition->scale = trans->getScale();
250
0
    transition->rectangular = trans->isRectangular();
251
252
0
    delete trans;
253
254
0
    return transition;
255
0
}
256
257
static TextPage *poppler_page_get_text_page(PopplerPage *page)
258
9.77k
{
259
9.77k
    std::scoped_lock lock(page->mutex);
260
9.77k
    if (page->text == nullptr) {
261
262
9.77k
        auto text_dev = std::make_unique<TextOutputDev>(nullptr, true, 0, false, false);
263
9.77k
        std::unique_ptr<Gfx> gfx = page->page->createGfx(text_dev.get(), 72.0, 72.0, 0, false, /* useMediaBox */
264
9.77k
                                                         true, /* Crop */
265
9.77k
                                                         -1, -1, -1, -1, nullptr, nullptr);
266
9.77k
        page->page->display(gfx.get());
267
9.77k
        text_dev->endPage();
268
269
9.77k
        page->text = text_dev->takeText();
270
9.77k
    }
271
272
9.77k
    return page->text.get();
273
9.77k
}
274
275
static bool annots_display_decide_cb(Annot *annot, void *user_data)
276
5.97k
{
277
5.97k
    auto flags = static_cast<PopplerRenderAnnotsFlags> GPOINTER_TO_UINT(user_data);
278
5.97k
    Annot::AnnotSubtype type = annot->getType();
279
5.97k
    int typeMask = 1 << MAX(0, (((int)type) - 1));
280
281
5.97k
    return (flags & typeMask) != 0;
282
5.97k
}
283
284
/**
285
 * poppler_page_render_full:
286
 * @page: the page to render from
287
 * @cairo: cairo context to render to
288
 * @printing: cairo context to render to
289
 * @flags: flags which allow to select which annotations to render
290
 *
291
 * Render the page to the given cairo context, manually selecting which
292
 * annotations should be displayed.
293
 *
294
 * The @printing parameter determines whether a page is rendered for printing
295
 * or for displaying it on a screen. See the documentation for
296
 * poppler_page_render_for_printing() for the differences between rendering to
297
 * the screen and rendering to a printer.
298
 *
299
 * Since: 25.02
300
 **/
301
void poppler_page_render_full(PopplerPage *page, cairo_t *cairo, gboolean printing, PopplerRenderAnnotsFlags flags)
302
1.43k
{
303
1.43k
    CairoOutputDev *output_dev;
304
305
1.43k
    g_return_if_fail(POPPLER_IS_PAGE(page));
306
1.43k
    g_return_if_fail(cairo != nullptr);
307
308
1.43k
    std::unique_lock lock(page->mutex);
309
310
1.43k
    output_dev = page->document->output_dev;
311
1.43k
    output_dev->setCairo(cairo);
312
1.43k
    output_dev->setPrinting(printing);
313
314
1.43k
    if (!printing && page->text == nullptr) {
315
0
        page->text = std::make_shared<TextPage>(false);
316
0
        output_dev->setTextPage(page->text);
317
1.43k
    } else {
318
1.43k
        lock.unlock();
319
1.43k
    }
320
321
1.43k
    cairo_save(cairo);
322
1.43k
    page->page->displaySlice(output_dev, 72.0, 72.0, 0, false, /* useMediaBox */
323
1.43k
                             true, /* Crop */
324
1.43k
                             -1, -1, -1, -1, /* instead of passing -1 we could use cairo_clip_extents() to get a bounding box */
325
326
1.43k
                             printing, nullptr, nullptr, annots_display_decide_cb, GUINT_TO_POINTER((guint)flags));
327
1.43k
    cairo_restore(cairo);
328
329
1.43k
    output_dev->setCairo(nullptr);
330
1.43k
    output_dev->setTextPage(nullptr);
331
1.43k
}
332
333
/**
334
 * poppler_page_render:
335
 * @page: the page to render from
336
 * @cairo: cairo context to render to
337
 *
338
 * Render the page to the given cairo context. This function
339
 * is for rendering a page that will be displayed. If you want
340
 * to render a page that will be printed use
341
 * poppler_page_render_for_printing() instead.  Please see the documentation
342
 * for that function for the differences between rendering to the screen and
343
 * rendering to a printer.
344
 **/
345
void poppler_page_render(PopplerPage *page, cairo_t *cairo)
346
0
{
347
0
    poppler_page_render_full(page, cairo, false, POPPLER_RENDER_ANNOTS_ALL);
348
0
}
349
350
/**
351
 * poppler_page_render_for_printing_with_options:
352
 * @page: the page to render from
353
 * @cairo: cairo context to render to
354
 * @options: print options
355
 *
356
 * Render the page to the given cairo context for printing
357
 * with the specified options
358
 *
359
 * See the documentation for poppler_page_render_for_printing() for the
360
 * differences between rendering to the screen and rendering to a printer.
361
 *
362
 * Since: 0.16
363
 *
364
 * Deprecated: 25.02: Use poppler_page_render_full() instead.
365
 **/
366
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
367
void poppler_page_render_for_printing_with_options(PopplerPage *page, cairo_t *cairo, PopplerPrintFlags options)
368
0
{
369
0
    int flags = static_cast<int>(POPPLER_RENDER_ANNOTS_PRINT_DOCUMENT);
370
371
0
    if (options & POPPLER_PRINT_STAMP_ANNOTS_ONLY) {
372
0
        flags |= POPPLER_RENDER_ANNOTS_PRINT_STAMP;
373
0
    }
374
0
    if (options & POPPLER_PRINT_MARKUP_ANNOTS) {
375
0
        flags |= POPPLER_RENDER_ANNOTS_PRINT_MARKUP;
376
0
    }
377
378
0
    poppler_page_render_full(page, cairo, true, static_cast<PopplerRenderAnnotsFlags>(flags));
379
0
}
380
G_GNUC_END_IGNORE_DEPRECATIONS
381
382
/**
383
 * poppler_page_render_for_printing:
384
 * @page: the page to render from
385
 * @cairo: cairo context to render to
386
 *
387
 * Render the page to the given cairo context for printing with
388
 * #POPPLER_PRINT_ALL flags selected.  If you want a different set of flags,
389
 * use poppler_page_render_full() with printing #TRUE and the corresponding
390
 * flags.
391
 *
392
 * The difference between poppler_page_render() and this function is that some
393
 * things get rendered differently between screens and printers:
394
 *
395
 * <itemizedlist>
396
 *   <listitem>
397
 *     PDF annotations get rendered according to their #PopplerAnnotFlag value.
398
 *     For example, #POPPLER_ANNOT_FLAG_PRINT refers to whether an annotation
399
 *     is printed or not, whereas #POPPLER_ANNOT_FLAG_NO_VIEW refers to whether
400
 *     an annotation is invisible when displaying to the screen.
401
 *   </listitem>
402
 *   <listitem>
403
 *     PDF supports "hairlines" of width 0.0, which often get rendered as
404
 *     having a width of 1 device pixel.  When displaying on a screen, Cairo
405
 *     may render such lines wide so that they are hard to see, and Poppler
406
 *     makes use of PDF's Stroke Adjust graphics parameter to make the lines
407
 *     easier to see.  However, when printing, Poppler is able to directly use a
408
 *     printer's pixel size instead.
409
 *   </listitem>
410
 *   <listitem>
411
 *     Some advanced features in PDF may require an image to be rasterized
412
 *     before sending off to a printer.  This may produce raster images which
413
 *     exceed Cairo's limits.  The "printing" functions will detect this condition
414
 *     and try to down-scale the intermediate surfaces as appropriate.
415
 *   </listitem>
416
 * </itemizedlist>
417
 *
418
 **/
419
void poppler_page_render_for_printing(PopplerPage *page, cairo_t *cairo)
420
1.43k
{
421
1.43k
    poppler_page_render_full(page, cairo, true, POPPLER_RENDER_ANNOTS_PRINT_ALL);
422
1.43k
}
423
424
static cairo_surface_t *create_surface_from_thumbnail_data(guchar *data, gint width, gint height, gint rowstride)
425
0
{
426
0
    guchar *cairo_pixels;
427
0
    gint cairo_stride;
428
0
    cairo_surface_t *surface;
429
0
    int j;
430
431
0
    surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
432
0
    if (cairo_surface_status(surface)) {
433
0
        return nullptr;
434
0
    }
435
436
0
    cairo_pixels = cairo_image_surface_get_data(surface);
437
0
    cairo_stride = cairo_image_surface_get_stride(surface);
438
439
0
    for (j = height; j; j--) {
440
0
        guchar *p = data;
441
0
        guchar *q = cairo_pixels;
442
0
        guchar *end = p + 3 * width;
443
444
0
        while (p < end) {
445
0
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
446
0
            q[0] = p[2];
447
0
            q[1] = p[1];
448
0
            q[2] = p[0];
449
#else
450
            q[1] = p[0];
451
            q[2] = p[1];
452
            q[3] = p[2];
453
#endif
454
0
            p += 3;
455
0
            q += 4;
456
0
        }
457
458
0
        data += rowstride;
459
0
        cairo_pixels += cairo_stride;
460
0
    }
461
462
0
    return surface;
463
0
}
464
465
/**
466
 * poppler_page_get_thumbnail:
467
 * @page: the #PopplerPage to get the thumbnail for
468
 *
469
 * Get the embedded thumbnail for the specified page.  If the document
470
 * doesn't have an embedded thumbnail for the page, this function
471
 * returns %NULL.
472
 *
473
 * Return value: the tumbnail as a cairo_surface_t or %NULL if the document
474
 * doesn't have a thumbnail for this page.
475
 **/
476
cairo_surface_t *poppler_page_get_thumbnail(PopplerPage *page)
477
0
{
478
0
    unsigned char *data;
479
0
    int width, height, rowstride;
480
0
    cairo_surface_t *surface;
481
482
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
483
484
0
    if (!page->page->loadThumb(&data, &width, &height, &rowstride)) {
485
0
        return nullptr;
486
0
    }
487
488
0
    surface = create_surface_from_thumbnail_data(data, width, height, rowstride);
489
0
    gfree(data);
490
491
0
    return surface;
492
0
}
493
494
static void render_selection(PopplerPage *page, cairo_t *cairo, PopplerRectangle *selection, PopplerSelectionStyle style, PopplerColor *glyph_color, PopplerColor *background_color, double background_opacity, bool draw_glyphs)
495
0
{
496
0
    CairoOutputDev *output_dev;
497
0
    TextPage *text;
498
0
    SelectionStyle selection_style = selectionStyleGlyph;
499
0
    PDFRectangle pdf_selection(selection->x1, selection->y1, selection->x2, selection->y2);
500
501
0
    GfxColor gfx_background_color = { { background_color->red, background_color->green, background_color->blue } };
502
0
    GfxColor gfx_glyph_color = { { glyph_color->red, glyph_color->green, glyph_color->blue } };
503
504
0
    switch (style) {
505
0
    case POPPLER_SELECTION_GLYPH:
506
0
        selection_style = selectionStyleGlyph;
507
0
        break;
508
0
    case POPPLER_SELECTION_WORD:
509
0
        selection_style = selectionStyleWord;
510
0
        break;
511
0
    case POPPLER_SELECTION_LINE:
512
0
        selection_style = selectionStyleLine;
513
0
        break;
514
0
    }
515
516
0
    output_dev = page->document->output_dev;
517
0
    output_dev->setCairo(cairo);
518
519
0
    text = poppler_page_get_text_page(page);
520
0
    text->drawSelection(output_dev, 1.0, 0, pdf_selection, selection_style, gfx_glyph_color, gfx_background_color, background_opacity, draw_glyphs);
521
522
0
    output_dev->setCairo(nullptr);
523
0
}
524
525
/**
526
 * poppler_page_render_selection:
527
 * @page: the #PopplerPage for which to render selection
528
 * @cairo: cairo context to render to
529
 * @selection: start and end point of selection as a rectangle
530
 * @old_selection: previous selection
531
 * @style: a #PopplerSelectionStyle
532
 * @glyph_color: color to use for drawing glyphs
533
 * @background_color: color to use for the selection background
534
 *
535
 * Render the selection specified by @selection for @page to
536
 * the given cairo context.  The selection will be rendered, using
537
 * @glyph_color for the glyphs and @background_color for the selection
538
 * background.
539
 *
540
 * If non-NULL, @old_selection specifies the selection that is already
541
 * rendered to @cairo, in which case this function will (some day)
542
 * only render the changed part of the selection.
543
 **/
544
void poppler_page_render_selection(PopplerPage *page, cairo_t *cairo, PopplerRectangle *selection, PopplerRectangle * /*old_selection*/, PopplerSelectionStyle style, PopplerColor *glyph_color, PopplerColor *background_color)
545
0
{
546
0
    render_selection(page, cairo, selection, style, glyph_color, background_color, 1, TRUE);
547
0
}
548
549
/**
550
 * poppler_page_render_transparent_selection:
551
 * @page: the #PopplerPage for which to render selection
552
 * @cairo: cairo context to render to
553
 * @selection: start and end point of selection as a rectangle
554
 * @old_selection: previous selection
555
 * @style: a #PopplerSelectionStyle
556
 * @background_color: color to use for the selection background
557
 * @background_opacity: opacity to use for the selection background
558
 *
559
 * Render the selection specified by @selection for @page to
560
 * the given cairo context.  The selection will be rendered using
561
 * @background_color and @background_opacity for the selection
562
 * background. Glyphs will not be drawn.
563
 *
564
 * If non-NULL, @old_selection specifies the selection that is already
565
 * rendered to @cairo, in which case this function will (some day)
566
 * only render the changed part of the selection.
567
 *
568
 * Since: 25.08
569
 **/
570
void poppler_page_render_transparent_selection(PopplerPage *page, cairo_t *cairo, PopplerRectangle *selection, PopplerRectangle * /*old_selection*/, PopplerSelectionStyle style, PopplerColor *background_color, double background_opacity)
571
0
{
572
0
    PopplerColor glyph_color = { .red = 0, .green = 0, .blue = 0 };
573
574
0
    render_selection(page, cairo, selection, style, &glyph_color, background_color, background_opacity, FALSE);
575
0
}
576
577
/**
578
 * poppler_page_get_thumbnail_size:
579
 * @page: A #PopplerPage
580
 * @width: (out): return location for width
581
 * @height: (out): return location for height
582
 *
583
 * Returns %TRUE if @page has a thumbnail associated with it.  It also
584
 * fills in @width and @height with the width and height of the
585
 * thumbnail.  The values of width and height are not changed if no
586
 * appropriate thumbnail exists.
587
 *
588
 * Return value: %TRUE, if @page has a thumbnail associated with it.
589
 **/
590
gboolean poppler_page_get_thumbnail_size(PopplerPage *page, int *width, int *height)
591
0
{
592
0
    Dict *dict;
593
0
    gboolean retval = FALSE;
594
595
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), FALSE);
596
0
    g_return_val_if_fail(width != nullptr, FALSE);
597
0
    g_return_val_if_fail(height != nullptr, FALSE);
598
599
0
    Object thumb = page->page->getThumb();
600
0
    if (!thumb.isStream()) {
601
0
        return FALSE;
602
0
    }
603
604
0
    dict = thumb.streamGetDict();
605
606
    /* Theoretically, this could succeed and you would still fail when
607
     * loading the thumb */
608
0
    if (dict->lookupInt("Width", "W", width) && dict->lookupInt("Height", "H", height)) {
609
0
        retval = TRUE;
610
0
    }
611
612
0
    return retval;
613
0
}
614
615
/**
616
 * poppler_page_get_selection_region:
617
 * @page: a #PopplerPage
618
 * @scale: scale specified as pixels per point
619
 * @style: a #PopplerSelectionStyle
620
 * @selection: start and end point of selection as a rectangle
621
 *
622
 * Returns a region containing the area that would be rendered by
623
 * poppler_page_render_selection() as a #GList of
624
 * #PopplerRectangle. The returned list must be freed with
625
 * poppler_page_selection_region_free().
626
 *
627
 * Return value: (element-type PopplerRectangle) (transfer full): a #GList of #PopplerRectangle
628
 *
629
 * Deprecated: 0.16: Use poppler_page_get_selected_region() instead.
630
 **/
631
GList *poppler_page_get_selection_region(PopplerPage *page, gdouble scale, PopplerSelectionStyle style, PopplerRectangle *selection)
632
0
{
633
0
    PDFRectangle poppler_selection;
634
0
    TextPage *text;
635
0
    SelectionStyle selection_style = selectionStyleGlyph;
636
0
    GList *region = nullptr;
637
638
0
    poppler_selection.x1 = selection->x1;
639
0
    poppler_selection.y1 = selection->y1;
640
0
    poppler_selection.x2 = selection->x2;
641
0
    poppler_selection.y2 = selection->y2;
642
643
0
    switch (style) {
644
0
    case POPPLER_SELECTION_GLYPH:
645
0
        selection_style = selectionStyleGlyph;
646
0
        break;
647
0
    case POPPLER_SELECTION_WORD:
648
0
        selection_style = selectionStyleWord;
649
0
        break;
650
0
    case POPPLER_SELECTION_LINE:
651
0
        selection_style = selectionStyleLine;
652
0
        break;
653
0
    }
654
655
0
    text = poppler_page_get_text_page(page);
656
0
    std::vector<PDFRectangle *> *list = text->getSelectionRegion(poppler_selection, selection_style, scale);
657
658
0
    for (const PDFRectangle *selection_rect : *list) {
659
0
        PopplerRectangle *rect;
660
661
0
        rect = poppler_rectangle_new_from_pdf_rectangle(selection_rect);
662
663
0
        region = g_list_prepend(region, rect);
664
665
0
        delete selection_rect;
666
0
    }
667
668
0
    delete list;
669
670
0
    return g_list_reverse(region);
671
0
}
672
673
/**
674
 * poppler_page_selection_region_free:
675
 * @region: (element-type PopplerRectangle): a #GList of
676
 *   #PopplerRectangle
677
 *
678
 * Frees @region
679
 *
680
 * Deprecated: 0.16: Use only to free deprecated regions created by
681
 * poppler_page_get_selection_region(). Regions created by
682
 * poppler_page_get_selected_region() should be freed with
683
 * cairo_region_destroy() instead.
684
 */
685
void poppler_page_selection_region_free(GList *region)
686
0
{
687
0
    if (G_UNLIKELY(!region)) {
688
0
        return;
689
0
    }
690
691
0
    g_list_free_full(region, reinterpret_cast<GDestroyNotify>(poppler_rectangle_free));
692
0
}
693
694
/**
695
 * poppler_page_get_selected_region:
696
 * @page: a #PopplerPage
697
 * @scale: scale specified as pixels per point
698
 * @style: a #PopplerSelectionStyle
699
 * @selection: start and end point of selection as a rectangle
700
 *
701
 * Returns a region containing the area that would be rendered by
702
 * poppler_page_render_selection().
703
 * The returned region must be freed with cairo_region_destroy()
704
 *
705
 * Return value: (transfer full): a cairo_region_t
706
 *
707
 * Since: 0.16
708
 **/
709
cairo_region_t *poppler_page_get_selected_region(PopplerPage *page, gdouble scale, PopplerSelectionStyle style, PopplerRectangle *selection)
710
0
{
711
0
    PDFRectangle poppler_selection;
712
0
    TextPage *text;
713
0
    SelectionStyle selection_style = selectionStyleGlyph;
714
0
    cairo_region_t *region;
715
716
0
    poppler_selection.x1 = selection->x1;
717
0
    poppler_selection.y1 = selection->y1;
718
0
    poppler_selection.x2 = selection->x2;
719
0
    poppler_selection.y2 = selection->y2;
720
721
0
    switch (style) {
722
0
    case POPPLER_SELECTION_GLYPH:
723
0
        selection_style = selectionStyleGlyph;
724
0
        break;
725
0
    case POPPLER_SELECTION_WORD:
726
0
        selection_style = selectionStyleWord;
727
0
        break;
728
0
    case POPPLER_SELECTION_LINE:
729
0
        selection_style = selectionStyleLine;
730
0
        break;
731
0
    }
732
733
0
    text = poppler_page_get_text_page(page);
734
0
    std::vector<PDFRectangle *> *list = text->getSelectionRegion(poppler_selection, selection_style, 1.0);
735
736
0
    region = cairo_region_create();
737
738
0
    for (const PDFRectangle *selection_rect : *list) {
739
0
        cairo_rectangle_int_t rect;
740
741
0
        rect.x = static_cast<gint>((selection_rect->x1 * scale) + 0.5);
742
0
        rect.y = static_cast<gint>((selection_rect->y1 * scale) + 0.5);
743
0
        rect.width = static_cast<gint>(((selection_rect->x2 - selection_rect->x1) * scale) + 0.5);
744
0
        rect.height = static_cast<gint>(((selection_rect->y2 - selection_rect->y1) * scale) + 0.5);
745
0
        cairo_region_union_rectangle(region, &rect);
746
747
0
        delete selection_rect;
748
0
    }
749
750
0
    delete list;
751
752
0
    return region;
753
0
}
754
755
/**
756
 * poppler_page_get_selected_text:
757
 * @page: a #PopplerPage
758
 * @style: a #PopplerSelectionStyle
759
 * @selection: the #PopplerRectangle including the text
760
 *
761
 * Retrieves the contents of the specified @selection as text.
762
 *
763
 * Returns: (transfer full): a pointer to the contents of the
764
 * @selection as a string
765
 *
766
 * Since: 0.16
767
 **/
768
char *poppler_page_get_selected_text(PopplerPage *page, PopplerSelectionStyle style, PopplerRectangle *selection)
769
0
{
770
0
    char *result;
771
0
    TextPage *text;
772
0
    SelectionStyle selection_style = selectionStyleGlyph;
773
0
    PDFRectangle pdf_selection;
774
775
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
776
0
    g_return_val_if_fail(selection != nullptr, NULL);
777
778
0
    pdf_selection.x1 = selection->x1;
779
0
    pdf_selection.y1 = selection->y1;
780
0
    pdf_selection.x2 = selection->x2;
781
0
    pdf_selection.y2 = selection->y2;
782
783
0
    switch (style) {
784
0
    case POPPLER_SELECTION_GLYPH:
785
0
        selection_style = selectionStyleGlyph;
786
0
        break;
787
0
    case POPPLER_SELECTION_WORD:
788
0
        selection_style = selectionStyleWord;
789
0
        break;
790
0
    case POPPLER_SELECTION_LINE:
791
0
        selection_style = selectionStyleLine;
792
0
        break;
793
0
    }
794
795
0
    text = poppler_page_get_text_page(page);
796
0
    GooString sel_text = text->getSelectionText(pdf_selection, selection_style);
797
0
    result = g_strdup(sel_text.c_str());
798
799
0
    return result;
800
0
}
801
802
/**
803
 * poppler_page_get_text:
804
 * @page: a #PopplerPage
805
 *
806
 * Retrieves the text of @page.
807
 *
808
 * Return value: a pointer to the text of the @page
809
 *               as a string
810
 * Since: 0.16
811
 **/
812
char *poppler_page_get_text(PopplerPage *page)
813
0
{
814
0
    PopplerRectangle rectangle = { .x1 = 0, .y1 = 0, .x2 = 0, .y2 = 0 };
815
816
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
817
818
0
    poppler_page_get_size(page, &rectangle.x2, &rectangle.y2);
819
820
0
    return poppler_page_get_selected_text(page, POPPLER_SELECTION_GLYPH, &rectangle);
821
0
}
822
823
/**
824
 * poppler_page_get_text_for_area:
825
 * @page: a #PopplerPage
826
 * @area: a #PopplerRectangle
827
 *
828
 * Retrieves the text of @page contained in @area.
829
 *
830
 * Return value: a pointer to the text as a string
831
 *
832
 * Since: 0.26
833
 **/
834
char *poppler_page_get_text_for_area(PopplerPage *page, PopplerRectangle *area)
835
0
{
836
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
837
0
    g_return_val_if_fail(area != nullptr, NULL);
838
839
0
    return poppler_page_get_selected_text(page, POPPLER_SELECTION_GLYPH, area);
840
0
}
841
842
/**
843
 * poppler_page_find_text_with_options:
844
 * @page: a #PopplerPage
845
 * @text: the text to search for (UTF-8 encoded)
846
 * @options: find options
847
 *
848
 * Finds @text in @page with the given #PopplerFindFlags options and
849
 * returns a #GList of rectangles for each occurrence of the text on the page.
850
 * The coordinates are in PDF points.
851
 *
852
 * When %POPPLER_FIND_MULTILINE is passed in @options, matches may span more than
853
 * one line. In this case, the returned list will contain one #PopplerRectangle
854
 * for each part of a match. The function poppler_rectangle_find_get_match_continued()
855
 * will return %TRUE for all rectangles belonging to the same match, except for
856
 * the last one. If a hyphen was ignored at the end of the part of the match,
857
 * poppler_rectangle_find_get_ignored_hyphen() will return %TRUE for that
858
 * rectangle.
859
 *
860
 * Note that currently matches spanning more than two lines are not found.
861
 * (This limitation may be lifted in a future version.)
862
 *
863
 * Note also that currently finding multi-line matches backwards is not
864
 * implemented; if you pass %POPPLER_FIND_BACKWARDS and %POPPLER_FIND_MULTILINE
865
 * together, %POPPLER_FIND_MULTILINE will be ignored.
866
 *
867
 * Return value: (element-type PopplerRectangle) (transfer full): a newly allocated list
868
 * of newly allocated #PopplerRectangle. Free with g_list_free_full() using poppler_rectangle_free().
869
 *
870
 * Since: 0.22
871
 **/
872
GList *poppler_page_find_text_with_options(PopplerPage *page, const char *text, PopplerFindFlags options)
873
9.77k
{
874
9.77k
    PopplerRectangleExtended *match;
875
9.77k
    GList *matches;
876
9.77k
    double xMin, yMin, xMax, yMax;
877
9.77k
    PDFRectangle continueMatch;
878
9.77k
    bool ignoredHyphen;
879
9.77k
    gunichar *ucs4;
880
9.77k
    glong ucs4_len;
881
9.77k
    double height;
882
9.77k
    TextPage *text_dev;
883
9.77k
    gboolean backwards;
884
9.77k
    gboolean start_at_last = FALSE;
885
886
9.77k
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
887
9.77k
    g_return_val_if_fail(text != nullptr, NULL);
888
889
9.77k
    text_dev = poppler_page_get_text_page(page);
890
891
9.77k
    ucs4 = g_utf8_to_ucs4_fast(text, -1, &ucs4_len);
892
9.77k
    poppler_page_get_size(page, nullptr, &height);
893
894
9.77k
    const bool multiline = (options & POPPLER_FIND_MULTILINE);
895
9.77k
    backwards = options & POPPLER_FIND_BACKWARDS;
896
9.77k
    matches = nullptr;
897
9.77k
    xMin = 0;
898
9.77k
    yMin = backwards ? height : 0;
899
900
9.77k
    continueMatch.x1 = std::numeric_limits<double>::max(); // we use this to detect valid returned values
901
902
13.7k
    while (text_dev->findText(ucs4, ucs4_len, false, true, // startAtTop, stopAtBottom
903
13.7k
                              start_at_last,
904
13.7k
                              false, // stopAtLast
905
13.7k
                              options & POPPLER_FIND_CASE_SENSITIVE, options & POPPLER_FIND_IGNORE_DIACRITICS, options & POPPLER_FIND_MULTILINE, backwards, options & POPPLER_FIND_WHOLE_WORDS_ONLY, &xMin, &yMin, &xMax, &yMax, &continueMatch,
906
13.7k
                              &ignoredHyphen)) {
907
3.96k
        match = poppler_rectangle_extended_new();
908
3.96k
        match->x1 = xMin;
909
3.96k
        match->y1 = height - yMax;
910
3.96k
        match->x2 = xMax;
911
3.96k
        match->y2 = height - yMin;
912
3.96k
        match->match_continued = false;
913
3.96k
        match->ignored_hyphen = false;
914
3.96k
        matches = g_list_prepend(matches, match);
915
3.96k
        start_at_last = TRUE;
916
917
3.96k
        if (continueMatch.x1 != std::numeric_limits<double>::max()) {
918
            // received rect for next-line part of a multi-line match, add it.
919
0
            if (multiline) {
920
0
                match->match_continued = true;
921
0
                match->ignored_hyphen = ignoredHyphen;
922
0
                match = poppler_rectangle_extended_new();
923
0
                match->x1 = continueMatch.x1;
924
0
                match->y1 = height - continueMatch.y1;
925
0
                match->x2 = continueMatch.x2;
926
0
                match->y2 = height - continueMatch.y2;
927
0
                match->match_continued = false;
928
0
                match->ignored_hyphen = false;
929
0
                matches = g_list_prepend(matches, match);
930
0
            }
931
932
0
            continueMatch.x1 = std::numeric_limits<double>::max();
933
0
        }
934
3.96k
    }
935
936
9.77k
    g_free(ucs4);
937
938
9.77k
    return g_list_reverse(matches);
939
9.77k
}
940
941
/**
942
 * poppler_page_find_text:
943
 * @page: a #PopplerPage
944
 * @text: the text to search for (UTF-8 encoded)
945
 *
946
 * Finds @text in @page with the default options (%POPPLER_FIND_DEFAULT) and
947
 * returns a #GList of rectangles for each occurrence of the text on the page.
948
 * The coordinates are in PDF points.
949
 *
950
 * Return value: (element-type PopplerRectangle) (transfer full): a #GList of #PopplerRectangle,
951
 **/
952
GList *poppler_page_find_text(PopplerPage *page, const char *text)
953
9.77k
{
954
9.77k
    return poppler_page_find_text_with_options(page, text, POPPLER_FIND_DEFAULT);
955
9.77k
}
956
957
static CairoImageOutputDev *poppler_page_get_image_output_dev(PopplerPage *page, bool (*imgDrawDeviceCbk)(int img_id, void *data), void *imgDrawCbkData)
958
0
{
959
0
    CairoImageOutputDev *image_dev;
960
961
0
    image_dev = new CairoImageOutputDev();
962
963
0
    if (imgDrawDeviceCbk) {
964
0
        image_dev->setImageDrawDecideCbk(imgDrawDeviceCbk, imgDrawCbkData);
965
0
    }
966
967
0
    std::unique_ptr<Gfx> gfx = page->page->createGfx(image_dev, 72.0, 72.0, 0, false, /* useMediaBox */
968
0
                                                     true, /* Crop */
969
0
                                                     -1, -1, -1, -1, nullptr, nullptr);
970
0
    page->page->display(gfx.get());
971
972
0
    return image_dev;
973
0
}
974
975
/**
976
 * poppler_page_get_image_mapping:
977
 * @page: A #PopplerPage
978
 *
979
 * Returns a list of #PopplerImageMapping items that map from a
980
 * location on @page to an image of the page. This list must be freed
981
 * with poppler_page_free_image_mapping() when done.
982
 *
983
 * Return value: (element-type PopplerImageMapping) (transfer full): A #GList of #PopplerImageMapping
984
 **/
985
GList *poppler_page_get_image_mapping(PopplerPage *page)
986
0
{
987
0
    GList *map_list = nullptr;
988
0
    CairoImageOutputDev *out;
989
0
    gint i;
990
991
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
992
993
0
    out = poppler_page_get_image_output_dev(page, nullptr, nullptr);
994
995
0
    for (i = 0; i < out->getNumImages(); i++) {
996
0
        PopplerImageMapping *mapping;
997
0
        CairoImage *image;
998
999
0
        image = out->getImage(i);
1000
1001
        /* Create the mapping */
1002
0
        mapping = poppler_image_mapping_new();
1003
1004
0
        image->getRect(&(mapping->area.x1), &(mapping->area.y1), &(mapping->area.x2), &(mapping->area.y2));
1005
0
        mapping->image_id = i;
1006
1007
0
        mapping->area.x1 -= page->page->getCropBox().x1;
1008
0
        mapping->area.x2 -= page->page->getCropBox().x1;
1009
0
        mapping->area.y1 -= page->page->getCropBox().y1;
1010
0
        mapping->area.y2 -= page->page->getCropBox().y1;
1011
1012
0
        map_list = g_list_prepend(map_list, mapping);
1013
0
    }
1014
1015
0
    delete out;
1016
1017
0
    return map_list;
1018
0
}
1019
1020
static bool image_draw_decide_cb(int image_id, void *data)
1021
0
{
1022
0
    return (image_id == GPOINTER_TO_INT(data));
1023
0
}
1024
1025
/**
1026
 * poppler_page_get_image:
1027
 * @page: A #PopplerPage
1028
 * @image_id: The image identifier
1029
 *
1030
 * Returns a cairo surface for the image of the @page
1031
 *
1032
 * Return value: A cairo surface for the image
1033
 **/
1034
cairo_surface_t *poppler_page_get_image(PopplerPage *page, gint image_id)
1035
0
{
1036
0
    CairoImageOutputDev *out;
1037
0
    cairo_surface_t *image;
1038
1039
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
1040
1041
0
    out = poppler_page_get_image_output_dev(page, image_draw_decide_cb, GINT_TO_POINTER(image_id));
1042
1043
0
    if (image_id >= out->getNumImages()) {
1044
0
        delete out;
1045
1046
0
        return nullptr;
1047
0
    }
1048
1049
0
    image = out->getImage(image_id)->getImage();
1050
0
    if (!image) {
1051
0
        delete out;
1052
1053
0
        return nullptr;
1054
0
    }
1055
1056
0
    cairo_surface_reference(image);
1057
0
    delete out;
1058
1059
0
    return image;
1060
0
}
1061
1062
/**
1063
 * poppler_page_free_image_mapping:
1064
 * @list: (element-type PopplerImageMapping): A list of
1065
 *   #PopplerImageMapping<!-- -->s
1066
 *
1067
 * Frees a list of #PopplerImageMapping<!-- -->s allocated by
1068
 * poppler_page_get_image_mapping().
1069
 **/
1070
void poppler_page_free_image_mapping(GList *list)
1071
0
{
1072
0
    if (G_UNLIKELY(list == nullptr)) {
1073
0
        return;
1074
0
    }
1075
1076
0
    g_list_free_full(list, reinterpret_cast<GDestroyNotify>(poppler_image_mapping_free));
1077
0
}
1078
1079
/**
1080
 * poppler_page_render_to_ps:
1081
 * @page: a #PopplerPage
1082
 * @ps_file: the PopplerPSFile to render to
1083
 *
1084
 * Render the page on a postscript file
1085
 *
1086
 **/
1087
void poppler_page_render_to_ps(PopplerPage *page, PopplerPSFile *ps_file)
1088
0
{
1089
0
    g_return_if_fail(POPPLER_IS_PAGE(page));
1090
0
    g_return_if_fail(ps_file != nullptr);
1091
1092
0
    if (!ps_file->out) {
1093
0
        std::vector<int> pages;
1094
0
        for (int i = ps_file->first_page; i <= ps_file->last_page; ++i) {
1095
0
            pages.push_back(i);
1096
0
        }
1097
0
        if (ps_file->fd != -1) {
1098
0
            ps_file->out = new PSOutputDev(ps_file->fd, ps_file->document->doc.get(), nullptr, pages, psModePS, static_cast<int>(ps_file->paper_width), static_cast<int>(ps_file->paper_height), false, ps_file->duplex, 0, 0, 0, 0,
1099
0
                                           psRasterizeWhenNeeded, false, nullptr, nullptr);
1100
0
        } else {
1101
0
            ps_file->out = new PSOutputDev(ps_file->filename, ps_file->document->doc.get(), nullptr, pages, psModePS, static_cast<int>(ps_file->paper_width), static_cast<int>(ps_file->paper_height), false, ps_file->duplex, 0, 0, 0, 0,
1102
0
                                           psRasterizeWhenNeeded, false, nullptr, nullptr);
1103
0
        }
1104
0
    }
1105
1106
0
    ps_file->document->doc->displayPage(ps_file->out, page->index + 1, 72.0, 72.0, 0, false, true, false);
1107
0
}
1108
1109
static void poppler_page_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
1110
0
{
1111
0
    PopplerPage *page = POPPLER_PAGE(object);
1112
1113
0
    switch (prop_id) {
1114
0
    case PROP_LABEL:
1115
0
        g_value_take_string(value, poppler_page_get_label(page));
1116
0
        break;
1117
0
    default:
1118
0
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1119
0
    }
1120
0
}
1121
1122
static void poppler_page_class_init(PopplerPageClass *klass)
1123
4
{
1124
4
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
1125
1126
4
    gobject_class->finalize = poppler_page_finalize;
1127
4
    gobject_class->get_property = poppler_page_get_property;
1128
1129
    /**
1130
     * PopplerPage:label:
1131
     *
1132
     * The label of the page or %NULL. See also poppler_page_get_label()
1133
     */
1134
4
    g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_LABEL, g_param_spec_string("label", "Page Label", "The label of the page", nullptr, G_PARAM_READABLE));
1135
4
}
1136
1137
13.1k
static void poppler_page_init(PopplerPage * /*page*/) { }
1138
1139
/**
1140
 * poppler_page_get_link_mapping:
1141
 * @page: A #PopplerPage
1142
 *
1143
 * Returns a list of #PopplerLinkMapping items that map from a
1144
 * location on @page to a #PopplerAction.  This list must be freed
1145
 * with poppler_page_free_link_mapping() when done.
1146
 *
1147
 * Return value: (element-type PopplerLinkMapping) (transfer full): A #GList of #PopplerLinkMapping
1148
 **/
1149
GList *poppler_page_get_link_mapping(PopplerPage *page)
1150
0
{
1151
0
    GList *map_list = nullptr;
1152
0
    Links *links;
1153
0
    double width, height;
1154
1155
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
1156
1157
0
    links = new Links(page->page->getAnnots());
1158
1159
0
    if (links == nullptr) {
1160
0
        return nullptr;
1161
0
    }
1162
1163
0
    poppler_page_get_size(page, &width, &height);
1164
1165
0
    for (const std::shared_ptr<AnnotLink> &link : links->getLinks()) {
1166
0
        PopplerLinkMapping *mapping;
1167
0
        PopplerRectangle rect;
1168
0
        LinkAction *link_action;
1169
1170
0
        link_action = link->getAction();
1171
1172
        /* Create the mapping */
1173
0
        mapping = poppler_link_mapping_new();
1174
0
        mapping->action = _poppler_action_new(page->document, link_action, nullptr);
1175
1176
0
        link->getRect(&rect.x1, &rect.y1, &rect.x2, &rect.y2);
1177
1178
0
        rect.x1 -= page->page->getCropBox().x1;
1179
0
        rect.x2 -= page->page->getCropBox().x1;
1180
0
        rect.y1 -= page->page->getCropBox().y1;
1181
0
        rect.y2 -= page->page->getCropBox().y1;
1182
1183
0
        switch (page->page->getRotate()) {
1184
0
        case 90:
1185
0
            mapping->area.x1 = rect.y1;
1186
0
            mapping->area.y1 = height - rect.x2;
1187
0
            mapping->area.x2 = mapping->area.x1 + (rect.y2 - rect.y1);
1188
0
            mapping->area.y2 = mapping->area.y1 + (rect.x2 - rect.x1);
1189
1190
0
            break;
1191
0
        case 180:
1192
0
            mapping->area.x1 = width - rect.x2;
1193
0
            mapping->area.y1 = height - rect.y2;
1194
0
            mapping->area.x2 = mapping->area.x1 + (rect.x2 - rect.x1);
1195
0
            mapping->area.y2 = mapping->area.y1 + (rect.y2 - rect.y1);
1196
1197
0
            break;
1198
0
        case 270:
1199
0
            mapping->area.x1 = width - rect.y2;
1200
0
            mapping->area.y1 = rect.x1;
1201
0
            mapping->area.x2 = mapping->area.x1 + (rect.y2 - rect.y1);
1202
0
            mapping->area.y2 = mapping->area.y1 + (rect.x2 - rect.x1);
1203
1204
0
            break;
1205
0
        default:
1206
0
            mapping->area.x1 = rect.x1;
1207
0
            mapping->area.y1 = rect.y1;
1208
0
            mapping->area.x2 = rect.x2;
1209
0
            mapping->area.y2 = rect.y2;
1210
0
        }
1211
1212
0
        map_list = g_list_prepend(map_list, mapping);
1213
0
    }
1214
1215
0
    delete links;
1216
1217
0
    return map_list;
1218
0
}
1219
1220
/**
1221
 * poppler_page_free_link_mapping:
1222
 * @list: (element-type PopplerLinkMapping): A list of
1223
 *   #PopplerLinkMapping<!-- -->s
1224
 *
1225
 * Frees a list of #PopplerLinkMapping<!-- -->s allocated by
1226
 * poppler_page_get_link_mapping().  It also frees the #PopplerAction<!-- -->s
1227
 * that each mapping contains, so if you want to keep them around, you need to
1228
 * copy them with poppler_action_copy().
1229
 **/
1230
void poppler_page_free_link_mapping(GList *list)
1231
0
{
1232
0
    if (G_UNLIKELY(list == nullptr)) {
1233
0
        return;
1234
0
    }
1235
1236
0
    g_list_free_full(list, reinterpret_cast<GDestroyNotify>(poppler_link_mapping_free));
1237
0
}
1238
1239
/**
1240
 * poppler_page_get_form_field_mapping:
1241
 * @page: A #PopplerPage
1242
 *
1243
 * Returns a list of #PopplerFormFieldMapping items that map from a
1244
 * location on @page to a form field.  This list must be freed
1245
 * with poppler_page_free_form_field_mapping() when done.
1246
 *
1247
 * Return value: (element-type PopplerFormFieldMapping) (transfer full): A #GList of #PopplerFormFieldMapping
1248
 **/
1249
GList *poppler_page_get_form_field_mapping(PopplerPage *page)
1250
0
{
1251
0
    GList *map_list = nullptr;
1252
0
    gint i;
1253
1254
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
1255
1256
0
    const std::unique_ptr<FormPageWidgets> forms = page->page->getFormWidgets();
1257
1258
0
    if (forms == nullptr) {
1259
0
        return nullptr;
1260
0
    }
1261
1262
0
    for (i = 0; i < forms->getNumWidgets(); i++) {
1263
0
        PopplerFormFieldMapping *mapping;
1264
0
        FormWidget *field;
1265
1266
0
        mapping = poppler_form_field_mapping_new();
1267
1268
0
        field = forms->getWidget(i);
1269
1270
0
        mapping->field = _poppler_form_field_new(page->document, field);
1271
0
        field->getRect(&(mapping->area.x1), &(mapping->area.y1), &(mapping->area.x2), &(mapping->area.y2));
1272
1273
0
        mapping->area.x1 -= page->page->getCropBox().x1;
1274
0
        mapping->area.x2 -= page->page->getCropBox().x1;
1275
0
        mapping->area.y1 -= page->page->getCropBox().y1;
1276
0
        mapping->area.y2 -= page->page->getCropBox().y1;
1277
1278
0
        map_list = g_list_prepend(map_list, mapping);
1279
0
    }
1280
1281
0
    return map_list;
1282
0
}
1283
1284
/**
1285
 * poppler_page_free_form_field_mapping:
1286
 * @list: (element-type PopplerFormFieldMapping): A list of
1287
 *   #PopplerFormFieldMapping<!-- -->s
1288
 *
1289
 * Frees a list of #PopplerFormFieldMapping<!-- -->s allocated by
1290
 * poppler_page_get_form_field_mapping().
1291
 **/
1292
void poppler_page_free_form_field_mapping(GList *list)
1293
0
{
1294
0
    if (G_UNLIKELY(list == nullptr)) {
1295
0
        return;
1296
0
    }
1297
1298
0
    g_list_free_full(list, reinterpret_cast<GDestroyNotify>(poppler_form_field_mapping_free));
1299
0
}
1300
1301
/**
1302
 * poppler_page_get_annot_mapping:
1303
 * @page: A #PopplerPage
1304
 *
1305
 * Returns a list of #PopplerAnnotMapping items that map from a location on
1306
 * @page to a #PopplerAnnot.  This list must be freed with
1307
 * poppler_page_free_annot_mapping() when done.
1308
 *
1309
 * Return value: (element-type PopplerAnnotMapping) (transfer full): A #GList of #PopplerAnnotMapping
1310
 **/
1311
GList *poppler_page_get_annot_mapping(PopplerPage *page)
1312
0
{
1313
0
    GList *map_list = nullptr;
1314
0
    double width, height;
1315
0
    Annots *annots;
1316
0
    const PDFRectangle *crop_box;
1317
1318
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
1319
1320
0
    annots = page->page->getAnnots();
1321
0
    if (!annots) {
1322
0
        return nullptr;
1323
0
    }
1324
1325
0
    poppler_page_get_size(page, &width, &height);
1326
0
    crop_box = &page->page->getCropBox();
1327
1328
0
    for (const std::shared_ptr<Annot> &annot : annots->getAnnots()) {
1329
0
        PopplerAnnotMapping *mapping;
1330
0
        PopplerRectangle rect;
1331
0
        gboolean flag_no_rotate;
1332
0
        gint rotation = 0;
1333
1334
0
        flag_no_rotate = annot->getFlags() & Annot::flagNoRotate;
1335
1336
        /* Create the mapping */
1337
0
        mapping = poppler_annot_mapping_new();
1338
1339
0
        switch (annot->getType()) {
1340
0
        case Annot::typeText:
1341
0
            mapping->annot = _poppler_annot_text_new(annot);
1342
0
            break;
1343
0
        case Annot::typeInk:
1344
0
            mapping->annot = _poppler_annot_ink_new(annot);
1345
0
            break;
1346
0
        case Annot::typeFreeText:
1347
0
            mapping->annot = _poppler_annot_free_text_new(annot);
1348
0
            break;
1349
0
        case Annot::typeFileAttachment:
1350
0
            mapping->annot = _poppler_annot_file_attachment_new(annot);
1351
0
            break;
1352
0
        case Annot::typeMovie:
1353
0
            mapping->annot = _poppler_annot_movie_new(annot);
1354
0
            break;
1355
0
        case Annot::typeScreen:
1356
0
            mapping->annot = _poppler_annot_screen_new(page->document, annot);
1357
0
            break;
1358
0
        case Annot::typeLine:
1359
0
            mapping->annot = _poppler_annot_line_new(annot);
1360
0
            break;
1361
0
        case Annot::typeSquare:
1362
0
            mapping->annot = _poppler_annot_square_new(annot);
1363
0
            break;
1364
0
        case Annot::typeCircle:
1365
0
            mapping->annot = _poppler_annot_circle_new(annot);
1366
0
            break;
1367
0
        case Annot::typeHighlight:
1368
0
        case Annot::typeUnderline:
1369
0
        case Annot::typeSquiggly:
1370
0
        case Annot::typeStrikeOut:
1371
0
            mapping->annot = _poppler_annot_text_markup_new(annot);
1372
0
            break;
1373
0
        case Annot::typeStamp:
1374
0
            mapping->annot = _poppler_annot_stamp_new(annot);
1375
0
            break;
1376
0
        default:
1377
0
            mapping->annot = _poppler_annot_new(annot);
1378
0
            break;
1379
0
        }
1380
1381
0
        const PDFRectangle &annot_rect = annot->getRect();
1382
0
        rect.x1 = annot_rect.x1 - crop_box->x1;
1383
0
        rect.y1 = annot_rect.y1 - crop_box->y1;
1384
0
        rect.x2 = annot_rect.x2 - crop_box->x1;
1385
0
        rect.y2 = annot_rect.y2 - crop_box->y1;
1386
1387
0
        rotation = page->page->getRotate();
1388
1389
0
        if (rotation == 0 || !SUPPORTED_ROTATION(rotation)) { /* zero or unknown rotation */
1390
0
            mapping->area.x1 = rect.x1;
1391
0
            mapping->area.y1 = rect.y1;
1392
0
            mapping->area.x2 = rect.x2;
1393
0
            mapping->area.y2 = rect.y2;
1394
0
        } else {
1395
0
            gdouble annot_height = rect.y2 - rect.y1;
1396
0
            gdouble annot_width = rect.x2 - rect.x1;
1397
1398
0
            if (flag_no_rotate) {
1399
0
                if (rotation == 90) {
1400
0
                    mapping->area.x1 = rect.y2;
1401
0
                    mapping->area.y1 = height - (rect.x1 + annot_height);
1402
0
                    mapping->area.x2 = rect.y2 + annot_width;
1403
0
                    mapping->area.y2 = height - rect.x1;
1404
0
                } else if (rotation == 180) {
1405
0
                    mapping->area.x1 = width - rect.x1;
1406
0
                    mapping->area.x2 = MIN(mapping->area.x1 + annot_width, width);
1407
0
                    mapping->area.y2 = height - rect.y2;
1408
0
                    mapping->area.y1 = MAX(0, mapping->area.y2 - annot_height);
1409
0
                } else if (rotation == 270) {
1410
0
                    mapping->area.x1 = width - rect.y2;
1411
0
                    mapping->area.x2 = MIN(mapping->area.x1 + annot_width, width);
1412
0
                    mapping->area.y2 = rect.x1;
1413
0
                    mapping->area.y1 = MAX(0, mapping->area.y2 - annot_height);
1414
0
                }
1415
0
            } else { /* !flag_no_rotate */
1416
0
                if (rotation == 90) {
1417
0
                    mapping->area.x1 = rect.y1;
1418
0
                    mapping->area.y1 = height - rect.x2;
1419
0
                    mapping->area.x2 = mapping->area.x1 + annot_height;
1420
0
                    mapping->area.y2 = mapping->area.y1 + annot_width;
1421
0
                } else if (rotation == 180) {
1422
0
                    mapping->area.x1 = width - rect.x2;
1423
0
                    mapping->area.y1 = height - rect.y2;
1424
0
                    mapping->area.x2 = mapping->area.x1 + annot_width;
1425
0
                    mapping->area.y2 = mapping->area.y1 + annot_height;
1426
0
                } else if (rotation == 270) {
1427
0
                    mapping->area.x1 = width - rect.y2;
1428
0
                    mapping->area.y1 = rect.x1;
1429
0
                    mapping->area.x2 = mapping->area.x1 + annot_height;
1430
0
                    mapping->area.y2 = mapping->area.y1 + annot_width;
1431
0
                }
1432
0
            }
1433
0
        }
1434
1435
0
        map_list = g_list_prepend(map_list, mapping);
1436
0
    }
1437
1438
0
    return g_list_reverse(map_list);
1439
0
}
1440
1441
/**
1442
 * poppler_page_free_annot_mapping:
1443
 * @list: (element-type PopplerAnnotMapping): A list of
1444
 *   #PopplerAnnotMapping<!-- -->s
1445
 *
1446
 * Frees a list of #PopplerAnnotMapping<!-- -->s allocated by
1447
 * poppler_page_get_annot_mapping().  It also unreferences the #PopplerAnnot<!-- -->s
1448
 * that each mapping contains, so if you want to keep them around, you need to
1449
 * reference them with g_object_ref().
1450
 **/
1451
void poppler_page_free_annot_mapping(GList *list)
1452
0
{
1453
0
    if (G_UNLIKELY(!list)) {
1454
0
        return;
1455
0
    }
1456
1457
0
    g_list_free_full(list, reinterpret_cast<GDestroyNotify>(poppler_annot_mapping_free));
1458
0
}
1459
1460
/* Adds or removes (according to @add parameter) the passed in @crop_box from the
1461
 * passed in @quads and returns it as a new #AnnotQuadrilaterals object */
1462
AnnotQuadrilaterals *new_quads_from_offset_cropbox(const PDFRectangle *crop_box, const AnnotQuadrilaterals *quads, gboolean add)
1463
0
{
1464
0
    int len = quads->getQuadrilateralsLength();
1465
0
    auto quads_array = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(len);
1466
0
    for (int i = 0; i < len; i++) {
1467
0
        if (add) {
1468
0
            quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral(quads->getX1(i) + crop_box->x1, quads->getY1(i) + crop_box->y1, quads->getX2(i) + crop_box->x1, quads->getY2(i) + crop_box->y1, quads->getX3(i) + crop_box->x1,
1469
0
                                                                     quads->getY3(i) + crop_box->y1, quads->getX4(i) + crop_box->x1, quads->getY4(i) + crop_box->y1);
1470
0
        } else {
1471
0
            quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral(quads->getX1(i) - crop_box->x1, quads->getY1(i) - crop_box->y1, quads->getX2(i) - crop_box->x1, quads->getY2(i) - crop_box->y1, quads->getX3(i) - crop_box->x1,
1472
0
                                                                     quads->getY3(i) - crop_box->y1, quads->getX4(i) - crop_box->x1, quads->getY4(i) - crop_box->y1);
1473
0
        }
1474
0
    }
1475
1476
0
    return new AnnotQuadrilaterals(std::move(quads_array), len);
1477
0
}
1478
1479
/* This function rotates the passed-in @x @y point with the page rotation.
1480
 * In other words, it moves the point to where it'll be located in a displayed document */
1481
void _page_rotate_xy(Page *page, double *x, double *y)
1482
0
{
1483
0
    double page_width, page_height, temp;
1484
0
    gint rotation = page->getRotate();
1485
1486
0
    if (rotation == 90 || rotation == 270) {
1487
0
        page_height = page->getCropWidth();
1488
0
        page_width = page->getCropHeight();
1489
0
    } else {
1490
0
        page_width = page->getCropWidth();
1491
0
        page_height = page->getCropHeight();
1492
0
    }
1493
1494
0
    if (rotation == 90) {
1495
0
        temp = *x;
1496
0
        *x = *y;
1497
0
        *y = page_height - temp;
1498
0
    } else if (rotation == 180) {
1499
0
        *x = page_width - *x;
1500
0
        *y = page_height - *y;
1501
0
    } else if (rotation == 270) {
1502
0
        temp = *x;
1503
0
        *x = page_width - *y;
1504
0
        *y = temp;
1505
0
    }
1506
0
}
1507
1508
/* This function undoes the rotation of @page in the passed-in @x @y point.
1509
 * In other words, it moves the point to where it'll be located if @page
1510
 * was put to zero rotation (unrotated) */
1511
void _page_unrotate_xy(Page *page, double *x, double *y)
1512
3
{
1513
3
    double page_width, page_height, temp;
1514
3
    gint rotation = page->getRotate();
1515
1516
3
    if (rotation == 90 || rotation == 270) {
1517
3
        page_height = page->getCropWidth();
1518
3
        page_width = page->getCropHeight();
1519
3
    } else {
1520
0
        page_width = page->getCropWidth();
1521
0
        page_height = page->getCropHeight();
1522
0
    }
1523
1524
3
    if (rotation == 90) {
1525
0
        temp = *x;
1526
0
        *x = page_height - *y;
1527
0
        *y = temp;
1528
3
    } else if (rotation == 180) {
1529
0
        *x = page_width - *x;
1530
0
        *y = page_height - *y;
1531
3
    } else if (rotation == 270) {
1532
3
        temp = *x;
1533
3
        *x = *y;
1534
3
        *y = page_width - temp;
1535
3
    }
1536
3
}
1537
1538
AnnotQuadrilaterals *_page_new_quads_unrotated(Page *page, const AnnotQuadrilaterals *quads)
1539
0
{
1540
0
    double x1, y1, x2, y2, x3, y3, x4, y4;
1541
0
    int len = quads->getQuadrilateralsLength();
1542
0
    auto quads_array = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(len);
1543
1544
0
    for (int i = 0; i < len; i++) {
1545
0
        x1 = quads->getX1(i);
1546
0
        y1 = quads->getY1(i);
1547
0
        x2 = quads->getX2(i);
1548
0
        y2 = quads->getY2(i);
1549
0
        x3 = quads->getX3(i);
1550
0
        y3 = quads->getY3(i);
1551
0
        x4 = quads->getX4(i);
1552
0
        y4 = quads->getY4(i);
1553
1554
0
        _page_unrotate_xy(page, &x1, &y1);
1555
0
        _page_unrotate_xy(page, &x2, &y2);
1556
0
        _page_unrotate_xy(page, &x3, &y3);
1557
0
        _page_unrotate_xy(page, &x4, &y4);
1558
1559
0
        quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral(x1, y1, x2, y2, x3, y3, x4, y4);
1560
0
    }
1561
1562
0
    return new AnnotQuadrilaterals(std::move(quads_array), len);
1563
0
}
1564
1565
/* @x1 @y1 @x2 @y2 are both 'in' and 'out' parameters, representing
1566
 * the diagonal of a rectangle which is the 'rect' area of @annot
1567
 * which is inside @page.
1568
 *
1569
 * If @page is unrotated (i.e. has zero rotation) this function does
1570
 * nothing, otherwise this function un-rotates the passed-in rect
1571
 * coords according to @page rotation so as the returned coords are
1572
 * those of the rect if page was put to zero rotation (unrotated).
1573
 * This is mandated by PDF spec when saving annotation coords (see
1574
 * 8.4.2 Annotation Flags) which also explains the special rotation
1575
 * that needs to be done when @annot has the flagNoRotate set to
1576
 * true, which this function follows. */
1577
void _unrotate_rect_for_annot_and_page(Page *page, Annot *annot, double *x1, double *y1, double *x2, double *y2)
1578
3
{
1579
3
    gboolean flag_no_rotate;
1580
1581
3
    if (!SUPPORTED_ROTATION(page->getRotate())) {
1582
0
        return;
1583
0
    }
1584
    /* Normalize received rect diagonal to be from UpperLeft to BottomRight,
1585
     * as our algorithm follows that */
1586
3
    if (*y2 > *y1) {
1587
3
        double temp = *y1;
1588
3
        *y1 = *y2;
1589
3
        *y2 = temp;
1590
3
    }
1591
3
    if (G_UNLIKELY(*x1 > *x2)) {
1592
0
        double temp = *x1;
1593
0
        *x1 = *x2;
1594
0
        *x2 = temp;
1595
0
    }
1596
3
    flag_no_rotate = annot->getFlags() & Annot::flagNoRotate;
1597
3
    if (flag_no_rotate) {
1598
        /* For this case rotating just the upperleft point is enough */
1599
3
        double annot_height = *y1 - *y2;
1600
3
        double annot_width = *x2 - *x1;
1601
3
        _page_unrotate_xy(page, x1, y1);
1602
3
        *x2 = *x1 + annot_width;
1603
3
        *y2 = *y1 - annot_height;
1604
3
    } else {
1605
0
        _page_unrotate_xy(page, x1, y1);
1606
0
        _page_unrotate_xy(page, x2, y2);
1607
0
    }
1608
3
}
1609
1610
/**
1611
 * poppler_page_add_annot:
1612
 * @page: a #PopplerPage
1613
 * @annot: a #PopplerAnnot to add
1614
 *
1615
 * Adds annotation @annot to @page.
1616
 *
1617
 * Since: 0.16
1618
 */
1619
void poppler_page_add_annot(PopplerPage *page, PopplerAnnot *annot)
1620
704
{
1621
704
    double x1, y1, x2, y2;
1622
704
    gboolean page_is_rotated;
1623
704
    const PDFRectangle *crop_box;
1624
704
    const PDFRectangle *page_crop_box;
1625
1626
704
    g_return_if_fail(POPPLER_IS_PAGE(page));
1627
704
    g_return_if_fail(POPPLER_IS_ANNOT(annot));
1628
1629
    /* Add the page's cropBox to the coordinates of rect field of annot */
1630
704
    page_crop_box = &page->page->getCropBox();
1631
704
    annot->annot->getRect(&x1, &y1, &x2, &y2);
1632
1633
704
    page_is_rotated = SUPPORTED_ROTATION(page->page->getRotate());
1634
704
    if (page_is_rotated) {
1635
        /* annot is inside a rotated page, as core poppler rect must be saved
1636
         * un-rotated, let's proceed to un-rotate rect before saving */
1637
3
        _unrotate_rect_for_annot_and_page(page->page, annot->annot.get(), &x1, &y1, &x2, &y2);
1638
3
    }
1639
1640
704
    annot->annot->setRect(x1 + page_crop_box->x1, y1 + page_crop_box->y1, x2 + page_crop_box->x1, y2 + page_crop_box->y1);
1641
1642
704
    auto *annot_markup = dynamic_cast<AnnotTextMarkup *>(annot->annot.get());
1643
704
    if (annot_markup) {
1644
0
        crop_box = _poppler_annot_get_cropbox(annot);
1645
0
        if (crop_box) {
1646
            /* Handle hypothetical case of annot being added is already existing on a prior page, so
1647
             * first remove cropbox of the prior page before adding cropbox of the new page later */
1648
0
            AnnotQuadrilaterals *quads = new_quads_from_offset_cropbox(crop_box, annot_markup->getQuadrilaterals(), FALSE);
1649
0
            annot_markup->setQuadrilaterals(*quads);
1650
0
            delete quads;
1651
0
        }
1652
0
        if (page_is_rotated) {
1653
            /* Quadrilateral's coords need to be saved un-rotated (same as rect coords) */
1654
0
            AnnotQuadrilaterals *quads = _page_new_quads_unrotated(page->page, annot_markup->getQuadrilaterals());
1655
0
            annot_markup->setQuadrilaterals(*quads);
1656
0
            delete quads;
1657
0
        }
1658
        /* Add to annot's quadrilaterals the offset for the cropbox of the new page */
1659
0
        AnnotQuadrilaterals *quads = new_quads_from_offset_cropbox(page_crop_box, annot_markup->getQuadrilaterals(), TRUE);
1660
0
        annot_markup->setQuadrilaterals(*quads);
1661
0
        delete quads;
1662
0
    }
1663
1664
704
    page->page->addAnnot(annot->annot);
1665
704
}
1666
1667
/**
1668
 * poppler_page_remove_annot:
1669
 * @page: a #PopplerPage
1670
 * @annot: a #PopplerAnnot to remove
1671
 *
1672
 * Removes annotation @annot from @page
1673
 *
1674
 * Since: 0.22
1675
 */
1676
void poppler_page_remove_annot(PopplerPage *page, PopplerAnnot *annot)
1677
0
{
1678
0
    g_return_if_fail(POPPLER_IS_PAGE(page));
1679
0
    g_return_if_fail(POPPLER_IS_ANNOT(annot));
1680
1681
0
    page->page->removeAnnot(annot->annot);
1682
0
}
1683
1684
/* PopplerRectangle type */
1685
1686
G_DEFINE_BOXED_TYPE(PopplerRectangle, poppler_rectangle, poppler_rectangle_copy, poppler_rectangle_free)
1687
1688
static PopplerRectangleExtended *poppler_rectangle_extended_new()
1689
3.96k
{
1690
3.96k
    return g_slice_new0(PopplerRectangleExtended);
1691
3.96k
}
1692
1693
PopplerRectangle *poppler_rectangle_new_from_pdf_rectangle(const PDFRectangle *rect)
1694
0
{
1695
0
    auto *r = poppler_rectangle_extended_new();
1696
0
    r->x1 = rect->x1;
1697
0
    r->y1 = rect->y1;
1698
0
    r->x2 = rect->x2;
1699
0
    r->y2 = rect->y2;
1700
1701
0
    return reinterpret_cast<PopplerRectangle *>(r);
1702
0
}
1703
1704
/**
1705
 * poppler_rectangle_new:
1706
 *
1707
 * Creates a new #PopplerRectangle
1708
 *
1709
 * Returns: a new #PopplerRectangle, use poppler_rectangle_free() to free it
1710
 */
1711
PopplerRectangle *poppler_rectangle_new(void)
1712
0
{
1713
0
    return reinterpret_cast<PopplerRectangle *>(poppler_rectangle_extended_new());
1714
0
}
1715
1716
/**
1717
 * poppler_rectangle_copy:
1718
 * @rectangle: a #PopplerRectangle to copy
1719
 *
1720
 * Creates a copy of @rectangle.
1721
 *
1722
 * Note that you must only use this function on an allocated PopplerRectangle, as
1723
 * returned by poppler_rectangle_new(), poppler_rectangle_copy(), or the list elements
1724
 * returned from poppler_page_find_text() or poppler_page_find_text_with_options().
1725
 * Returns: a new allocated copy of @rectangle
1726
 */
1727
PopplerRectangle *poppler_rectangle_copy(PopplerRectangle *rectangle)
1728
0
{
1729
0
    g_return_val_if_fail(rectangle != nullptr, NULL);
1730
1731
0
    auto *ext_rectangle = reinterpret_cast<PopplerRectangleExtended *>(rectangle);
1732
0
    return reinterpret_cast<PopplerRectangle *>(g_slice_dup(PopplerRectangleExtended, ext_rectangle));
1733
0
}
1734
1735
/**
1736
 * poppler_rectangle_free:
1737
 * @rectangle: a #PopplerRectangle
1738
 *
1739
 * Frees the given #PopplerRectangle.
1740
 *
1741
 * Note that you must only use this function on an allocated PopplerRectangle, as
1742
 * returned by poppler_rectangle_new(), poppler_rectangle_copy(), or the list elements
1743
 * returned from poppler_page_find_text() or poppler_page_find_text_with_options().
1744
 */
1745
void poppler_rectangle_free(PopplerRectangle *rectangle)
1746
3.96k
{
1747
3.96k
    auto *ext_rectangle = reinterpret_cast<PopplerRectangleExtended *>(rectangle);
1748
3.96k
    g_slice_free(PopplerRectangleExtended, ext_rectangle);
1749
3.96k
}
1750
1751
/**
1752
 * poppler_rectangle_find_get_match_continued:
1753
 * @rectangle: a #PopplerRectangle
1754
 *
1755
 * When using poppler_page_find_text_with_options() with the
1756
 * %POPPLER_FIND_MULTILINE flag, a match may span more than one line
1757
 * and thus consist of more than one rectangle. Every rectangle belonging
1758
 * to the same match will return %TRUE from this function, except for
1759
 * the last rectangle, where this function will return %FALSE.
1760
 *
1761
 * Note that you must only call this function on a #PopplerRectangle
1762
 * returned in the list from poppler_page_find_text() or
1763
 * poppler_page_find_text_with_options().
1764
 *
1765
 * Returns: whether there are more rectangles belonging to the same match
1766
 *
1767
 * Since: 21.05.0
1768
 */
1769
gboolean poppler_rectangle_find_get_match_continued(const PopplerRectangle *rectangle)
1770
0
{
1771
0
    g_return_val_if_fail(rectangle != nullptr, false);
1772
1773
0
    const auto *ext_rectangle = reinterpret_cast<const PopplerRectangleExtended *>(rectangle);
1774
0
    return ext_rectangle->match_continued;
1775
0
}
1776
1777
/**
1778
 * poppler_rectangle_find_get_ignored_hyphen:
1779
 * @rectangle: a #PopplerRectangle
1780
 *
1781
 * When using poppler_page_find_text_with_options() with the
1782
 * %POPPLER_FIND_MULTILINE flag, a match may span more than one line,
1783
 * and may have been formed by ignoring a hyphen at the end of the line.
1784
 * When this happens at the end of the line corresponding to @rectangle,
1785
 * this function returns %TRUE (and then poppler_rectangle_find_get_match_continued()
1786
 * will also return %TRUE); otherwise it returns %FALSE.
1787
 *
1788
 * Note that you must only call this function on a #PopplerRectangle
1789
 * returned in the list from poppler_page_find_text() or
1790
 * poppler_page_find_text_with_options().
1791
 *
1792
 * Returns: whether a hyphen was ignored at the end of the line corresponding to @rectangle.
1793
 *
1794
 * Since: 21.05.0
1795
 */
1796
gboolean poppler_rectangle_find_get_ignored_hyphen(const PopplerRectangle *rectangle)
1797
0
{
1798
0
    g_return_val_if_fail(rectangle != nullptr, false);
1799
1800
0
    const auto *ext_rectangle = reinterpret_cast<const PopplerRectangleExtended *>(rectangle);
1801
0
    return ext_rectangle->ignored_hyphen;
1802
0
}
1803
1804
G_DEFINE_BOXED_TYPE(PopplerPoint, poppler_point, poppler_point_copy, poppler_point_free)
1805
1806
/**
1807
 * poppler_point_new:
1808
 *
1809
 * Creates a new #PopplerPoint. It must be freed with poppler_point_free() after use.
1810
 *
1811
 * Returns: a new #PopplerPoint
1812
 *
1813
 * Since: 0.26
1814
 **/
1815
PopplerPoint *poppler_point_new(void)
1816
0
{
1817
0
    return g_slice_new0(PopplerPoint);
1818
0
}
1819
1820
/**
1821
 * poppler_point_copy:
1822
 * @point: a #PopplerPoint to copy
1823
 *
1824
 * Creates a copy of @point. The copy must be freed with poppler_point_free()
1825
 * after use.
1826
 *
1827
 * Returns: a new allocated copy of @point
1828
 *
1829
 * Since: 0.26
1830
 **/
1831
PopplerPoint *poppler_point_copy(PopplerPoint *point)
1832
0
{
1833
0
    g_return_val_if_fail(point != nullptr, NULL);
1834
1835
0
    return g_slice_dup(PopplerPoint, point);
1836
0
}
1837
1838
/**
1839
 * poppler_point_free:
1840
 * @point: a #PopplerPoint
1841
 *
1842
 * Frees the memory used by @point
1843
 *
1844
 * Since: 0.26
1845
 **/
1846
void poppler_point_free(PopplerPoint *point)
1847
0
{
1848
0
    g_slice_free(PopplerPoint, point);
1849
0
}
1850
1851
/* PopplerQuadrilateral type */
1852
1853
G_DEFINE_BOXED_TYPE(PopplerQuadrilateral, poppler_quadrilateral, poppler_quadrilateral_copy, poppler_quadrilateral_free)
1854
1855
/**
1856
 * poppler_quadrilateral_new:
1857
 *
1858
 * Creates a new #PopplerQuadrilateral. It must be freed with poppler_quadrilateral_free() after use.
1859
 *
1860
 * Returns: a new #PopplerQuadrilateral.
1861
 *
1862
 * Since: 0.26
1863
 **/
1864
PopplerQuadrilateral *poppler_quadrilateral_new(void)
1865
0
{
1866
0
    return g_slice_new0(PopplerQuadrilateral);
1867
0
}
1868
1869
/**
1870
 * poppler_quadrilateral_copy:
1871
 * @quad: a #PopplerQuadrilateral to copy
1872
 *
1873
 * Creates a copy of @quad. The copy must be freed with poppler_quadrilateral_free() after use.
1874
 *
1875
 * Returns: a new allocated copy of @quad
1876
 *
1877
 * Since: 0.26
1878
 **/
1879
PopplerQuadrilateral *poppler_quadrilateral_copy(PopplerQuadrilateral *quad)
1880
0
{
1881
0
    g_return_val_if_fail(quad != nullptr, NULL);
1882
1883
0
    return g_slice_dup(PopplerQuadrilateral, quad);
1884
0
}
1885
1886
/**
1887
 * poppler_quadrilateral_free:
1888
 * @quad: a #PopplerQuadrilateral
1889
 *
1890
 * Frees the memory used by @quad
1891
 *
1892
 * Since: 0.26
1893
 **/
1894
void poppler_quadrilateral_free(PopplerQuadrilateral *quad)
1895
0
{
1896
0
    g_slice_free(PopplerQuadrilateral, quad);
1897
0
}
1898
1899
/* PopplerTextAttributes type */
1900
1901
G_DEFINE_BOXED_TYPE(PopplerTextAttributes, poppler_text_attributes, poppler_text_attributes_copy, poppler_text_attributes_free)
1902
1903
/**
1904
 * poppler_text_attributes_new:
1905
 *
1906
 * Creates a new #PopplerTextAttributes
1907
 *
1908
 * Returns: a new #PopplerTextAttributes, use poppler_text_attributes_free() to free it
1909
 *
1910
 * Since: 0.18
1911
 */
1912
PopplerTextAttributes *poppler_text_attributes_new(void)
1913
0
{
1914
0
    return g_slice_new0(PopplerTextAttributes);
1915
0
}
1916
1917
static gchar *get_font_name_from_word(const TextWord *word, gint word_i)
1918
0
{
1919
0
    const GooString *font_name = word->getFontName(word_i);
1920
0
    const gchar *name;
1921
0
    gboolean subset;
1922
0
    size_t i;
1923
1924
0
    if (!font_name || font_name->empty()) {
1925
0
        return g_strdup("Default");
1926
0
    }
1927
1928
    // check for a font subset name: capital letters followed by a '+' sign
1929
0
    for (i = 0; i < font_name->size(); ++i) {
1930
0
        if (font_name->getChar(i) < 'A' || font_name->getChar(i) > 'Z') {
1931
0
            break;
1932
0
        }
1933
0
    }
1934
0
    subset = i > 0 && i < font_name->size() && font_name->getChar(i) == '+';
1935
0
    name = font_name->c_str();
1936
0
    if (subset) {
1937
0
        name += i + 1;
1938
0
    }
1939
1940
0
    return g_strdup(name);
1941
0
}
1942
1943
/*
1944
 * Allocates a new PopplerTextAttributes with word attributes
1945
 */
1946
static PopplerTextAttributes *poppler_text_attributes_new_from_word(const TextWord *word, gint i)
1947
0
{
1948
0
    PopplerTextAttributes *attrs = poppler_text_attributes_new();
1949
0
    gdouble r, g, b;
1950
1951
0
    attrs->font_name = get_font_name_from_word(word, i);
1952
0
    attrs->font_size = word->getFontSize();
1953
0
    attrs->is_underlined = word->isUnderlined();
1954
0
    word->getColor(&r, &g, &b);
1955
0
    attrs->color.red = static_cast<int>(r * 65535. + 0.5);
1956
0
    attrs->color.green = static_cast<int>(g * 65535. + 0.5);
1957
0
    attrs->color.blue = static_cast<int>(b * 65535. + 0.5);
1958
1959
0
    return attrs;
1960
0
}
1961
1962
/**
1963
 * poppler_text_attributes_copy:
1964
 * @text_attrs: a #PopplerTextAttributes to copy
1965
 *
1966
 * Creates a copy of @text_attrs
1967
 *
1968
 * Returns: a new allocated copy of @text_attrs
1969
 *
1970
 * Since: 0.18
1971
 */
1972
PopplerTextAttributes *poppler_text_attributes_copy(PopplerTextAttributes *text_attrs)
1973
0
{
1974
0
    PopplerTextAttributes *attrs;
1975
1976
0
    attrs = g_slice_dup(PopplerTextAttributes, text_attrs);
1977
0
    attrs->font_name = g_strdup(text_attrs->font_name);
1978
0
    return attrs;
1979
0
}
1980
1981
/**
1982
 * poppler_text_attributes_free:
1983
 * @text_attrs: a #PopplerTextAttributes
1984
 *
1985
 * Frees the given #PopplerTextAttributes
1986
 *
1987
 * Since: 0.18
1988
 */
1989
void poppler_text_attributes_free(PopplerTextAttributes *text_attrs)
1990
0
{
1991
0
    g_free(text_attrs->font_name);
1992
0
    g_slice_free(PopplerTextAttributes, text_attrs);
1993
0
}
1994
1995
/**
1996
 * SECTION:poppler-color
1997
 * @short_description: Colors
1998
 * @title: PopplerColor
1999
 */
2000
2001
/* PopplerColor type */
2002
G_DEFINE_BOXED_TYPE(PopplerColor, poppler_color, poppler_color_copy, poppler_color_free)
2003
2004
/**
2005
 * poppler_color_new:
2006
 *
2007
 * Creates a new #PopplerColor
2008
 *
2009
 * Returns: a new #PopplerColor, use poppler_color_free() to free it
2010
 */
2011
PopplerColor *poppler_color_new(void)
2012
0
{
2013
0
    return g_new0(PopplerColor, 1);
2014
0
}
2015
2016
/**
2017
 * poppler_color_copy:
2018
 * @color: a #PopplerColor to copy
2019
 *
2020
 * Creates a copy of @color
2021
 *
2022
 * Returns: a new allocated copy of @color
2023
 */
2024
PopplerColor *poppler_color_copy(PopplerColor *color)
2025
0
{
2026
0
    PopplerColor *new_color;
2027
2028
0
    new_color = g_new(PopplerColor, 1);
2029
0
    *new_color = *color;
2030
2031
0
    return new_color;
2032
0
}
2033
2034
/**
2035
 * poppler_color_free:
2036
 * @color: a #PopplerColor
2037
 *
2038
 * Frees the given #PopplerColor
2039
 */
2040
void poppler_color_free(PopplerColor *color)
2041
0
{
2042
0
    g_free(color);
2043
0
}
2044
2045
/* PopplerLinkMapping type */
2046
G_DEFINE_BOXED_TYPE(PopplerLinkMapping, poppler_link_mapping, poppler_link_mapping_copy, poppler_link_mapping_free)
2047
2048
/**
2049
 * poppler_link_mapping_new:
2050
 *
2051
 * Creates a new #PopplerLinkMapping
2052
 *
2053
 * Returns: a new #PopplerLinkMapping, use poppler_link_mapping_free() to free it
2054
 */
2055
PopplerLinkMapping *poppler_link_mapping_new(void)
2056
0
{
2057
0
    return g_slice_new0(PopplerLinkMapping);
2058
0
}
2059
2060
/**
2061
 * poppler_link_mapping_copy:
2062
 * @mapping: a #PopplerLinkMapping to copy
2063
 *
2064
 * Creates a copy of @mapping
2065
 *
2066
 * Returns: a new allocated copy of @mapping
2067
 */
2068
PopplerLinkMapping *poppler_link_mapping_copy(PopplerLinkMapping *mapping)
2069
0
{
2070
0
    PopplerLinkMapping *new_mapping;
2071
2072
0
    new_mapping = g_slice_dup(PopplerLinkMapping, mapping);
2073
2074
0
    if (new_mapping->action) {
2075
0
        new_mapping->action = poppler_action_copy(new_mapping->action);
2076
0
    }
2077
2078
0
    return new_mapping;
2079
0
}
2080
2081
/**
2082
 * poppler_link_mapping_free:
2083
 * @mapping: a #PopplerLinkMapping
2084
 *
2085
 * Frees the given #PopplerLinkMapping
2086
 */
2087
void poppler_link_mapping_free(PopplerLinkMapping *mapping)
2088
0
{
2089
0
    if (G_UNLIKELY(!mapping)) {
2090
0
        return;
2091
0
    }
2092
2093
0
    if (mapping->action) {
2094
0
        poppler_action_free(mapping->action);
2095
0
    }
2096
2097
0
    g_slice_free(PopplerLinkMapping, mapping);
2098
0
}
2099
2100
/* Poppler Image mapping type */
2101
G_DEFINE_BOXED_TYPE(PopplerImageMapping, poppler_image_mapping, poppler_image_mapping_copy, poppler_image_mapping_free)
2102
2103
/**
2104
 * poppler_image_mapping_new:
2105
 *
2106
 * Creates a new #PopplerImageMapping
2107
 *
2108
 * Returns: a new #PopplerImageMapping, use poppler_image_mapping_free() to free it
2109
 */
2110
PopplerImageMapping *poppler_image_mapping_new(void)
2111
0
{
2112
0
    return g_slice_new0(PopplerImageMapping);
2113
0
}
2114
2115
/**
2116
 * poppler_image_mapping_copy:
2117
 * @mapping: a #PopplerImageMapping to copy
2118
 *
2119
 * Creates a copy of @mapping
2120
 *
2121
 * Returns: a new allocated copy of @mapping
2122
 */
2123
PopplerImageMapping *poppler_image_mapping_copy(PopplerImageMapping *mapping)
2124
0
{
2125
0
    return g_slice_dup(PopplerImageMapping, mapping);
2126
0
}
2127
2128
/**
2129
 * poppler_image_mapping_free:
2130
 * @mapping: a #PopplerImageMapping
2131
 *
2132
 * Frees the given #PopplerImageMapping
2133
 */
2134
void poppler_image_mapping_free(PopplerImageMapping *mapping)
2135
0
{
2136
0
    g_slice_free(PopplerImageMapping, mapping);
2137
0
}
2138
2139
/* Page Transition */
2140
G_DEFINE_BOXED_TYPE(PopplerPageTransition, poppler_page_transition, poppler_page_transition_copy, poppler_page_transition_free)
2141
2142
/**
2143
 * poppler_page_transition_new:
2144
 *
2145
 * Creates a new #PopplerPageTransition
2146
 *
2147
 * Returns: a new #PopplerPageTransition, use poppler_page_transition_free() to free it
2148
 */
2149
PopplerPageTransition *poppler_page_transition_new(void)
2150
0
{
2151
0
    return g_new0(PopplerPageTransition, 1);
2152
0
}
2153
2154
/**
2155
 * poppler_page_transition_copy:
2156
 * @transition: a #PopplerPageTransition to copy
2157
 *
2158
 * Creates a copy of @transition
2159
 *
2160
 * Returns: a new allocated copy of @transition
2161
 */
2162
PopplerPageTransition *poppler_page_transition_copy(PopplerPageTransition *transition)
2163
0
{
2164
0
    PopplerPageTransition *new_transition;
2165
2166
0
    new_transition = poppler_page_transition_new();
2167
0
    *new_transition = *transition;
2168
2169
0
    return new_transition;
2170
0
}
2171
2172
/**
2173
 * poppler_page_transition_free:
2174
 * @transition: a #PopplerPageTransition
2175
 *
2176
 * Frees the given #PopplerPageTransition
2177
 */
2178
void poppler_page_transition_free(PopplerPageTransition *transition)
2179
0
{
2180
0
    g_free(transition);
2181
0
}
2182
2183
/* Form Field Mapping Type */
2184
G_DEFINE_BOXED_TYPE(PopplerFormFieldMapping, poppler_form_field_mapping, poppler_form_field_mapping_copy, poppler_form_field_mapping_free)
2185
2186
/**
2187
 * poppler_form_field_mapping_new:
2188
 *
2189
 * Creates a new #PopplerFormFieldMapping
2190
 *
2191
 * Returns: a new #PopplerFormFieldMapping, use poppler_form_field_mapping_free() to free it
2192
 */
2193
PopplerFormFieldMapping *poppler_form_field_mapping_new(void)
2194
0
{
2195
0
    return g_slice_new0(PopplerFormFieldMapping);
2196
0
}
2197
2198
/**
2199
 * poppler_form_field_mapping_copy:
2200
 * @mapping: a #PopplerFormFieldMapping to copy
2201
 *
2202
 * Creates a copy of @mapping
2203
 *
2204
 * Returns: a new allocated copy of @mapping
2205
 */
2206
PopplerFormFieldMapping *poppler_form_field_mapping_copy(PopplerFormFieldMapping *mapping)
2207
0
{
2208
0
    PopplerFormFieldMapping *new_mapping;
2209
2210
0
    new_mapping = g_slice_dup(PopplerFormFieldMapping, mapping);
2211
2212
0
    if (mapping->field) {
2213
0
        new_mapping->field = static_cast<PopplerFormField *> g_object_ref(mapping->field);
2214
0
    }
2215
2216
0
    return new_mapping;
2217
0
}
2218
2219
/**
2220
 * poppler_form_field_mapping_free:
2221
 * @mapping: a #PopplerFormFieldMapping
2222
 *
2223
 * Frees the given #PopplerFormFieldMapping
2224
 */
2225
void poppler_form_field_mapping_free(PopplerFormFieldMapping *mapping)
2226
0
{
2227
0
    if (G_UNLIKELY(!mapping)) {
2228
0
        return;
2229
0
    }
2230
2231
0
    if (mapping->field) {
2232
0
        g_object_unref(mapping->field);
2233
0
    }
2234
2235
0
    g_slice_free(PopplerFormFieldMapping, mapping);
2236
0
}
2237
2238
/* PopplerAnnot Mapping Type */
2239
G_DEFINE_BOXED_TYPE(PopplerAnnotMapping, poppler_annot_mapping, poppler_annot_mapping_copy, poppler_annot_mapping_free)
2240
2241
/**
2242
 * poppler_annot_mapping_new:
2243
 *
2244
 * Creates a new #PopplerAnnotMapping
2245
 *
2246
 * Returns: a new #PopplerAnnotMapping, use poppler_annot_mapping_free() to free it
2247
 */
2248
PopplerAnnotMapping *poppler_annot_mapping_new(void)
2249
0
{
2250
0
    return g_slice_new0(PopplerAnnotMapping);
2251
0
}
2252
2253
/**
2254
 * poppler_annot_mapping_copy:
2255
 * @mapping: a #PopplerAnnotMapping to copy
2256
 *
2257
 * Creates a copy of @mapping
2258
 *
2259
 * Returns: a new allocated copy of @mapping
2260
 */
2261
PopplerAnnotMapping *poppler_annot_mapping_copy(PopplerAnnotMapping *mapping)
2262
0
{
2263
0
    PopplerAnnotMapping *new_mapping;
2264
2265
0
    new_mapping = g_slice_dup(PopplerAnnotMapping, mapping);
2266
2267
0
    if (mapping->annot) {
2268
0
        new_mapping->annot = static_cast<PopplerAnnot *> g_object_ref(mapping->annot);
2269
0
    }
2270
2271
0
    return new_mapping;
2272
0
}
2273
2274
/**
2275
 * poppler_annot_mapping_free:
2276
 * @mapping: a #PopplerAnnotMapping
2277
 *
2278
 * Frees the given #PopplerAnnotMapping
2279
 */
2280
void poppler_annot_mapping_free(PopplerAnnotMapping *mapping)
2281
0
{
2282
0
    if (G_UNLIKELY(!mapping)) {
2283
0
        return;
2284
0
    }
2285
2286
0
    if (mapping->annot) {
2287
0
        g_object_unref(mapping->annot);
2288
0
    }
2289
2290
0
    g_slice_free(PopplerAnnotMapping, mapping);
2291
0
}
2292
2293
/**
2294
 * poppler_page_get_crop_box:
2295
 * @page: a #PopplerPage
2296
 * @rect: (out): a #PopplerRectangle to fill
2297
 *
2298
 * Retrurns the crop box of @page
2299
 */
2300
void poppler_page_get_crop_box(PopplerPage *page, PopplerRectangle *rect)
2301
0
{
2302
0
    const PDFRectangle &cropBox = page->page->getCropBox();
2303
2304
0
    rect->x1 = cropBox.x1;
2305
0
    rect->x2 = cropBox.x2;
2306
0
    rect->y1 = cropBox.y1;
2307
0
    rect->y2 = cropBox.y2;
2308
0
}
2309
2310
/*
2311
 * poppler_page_get_bounding_box:
2312
 * @page: A #PopplerPage
2313
 * @rect: (out) return the bounding box of the page
2314
 *
2315
 * Returns the bounding box of the page, a rectangle enclosing all text, vector
2316
 * graphics (lines, rectangles and curves) and raster images in the page.
2317
 * Includes invisible text but not (yet) annotations like highlights and form
2318
 * elements.
2319
 *
2320
 * Return value: %TRUE if the page contains graphics, %FALSE otherwise
2321
 *
2322
 * Since: 0.88
2323
 */
2324
gboolean poppler_page_get_bounding_box(PopplerPage *page, PopplerRectangle *rect)
2325
1.49k
{
2326
1.49k
    BBoxOutputDev *bb_out;
2327
1.49k
    bool hasGraphics;
2328
2329
1.49k
    g_return_val_if_fail(POPPLER_IS_PAGE(page), false);
2330
1.49k
    g_return_val_if_fail(rect != nullptr, false);
2331
2332
1.49k
    bb_out = new BBoxOutputDev();
2333
2334
1.49k
    page->page->displaySlice(bb_out, 72.0, 72.0, 0, false, /* useMediaBox */
2335
1.49k
                             true, /* Crop */
2336
1.49k
                             -1, -1, -1, -1, false, /* printing */
2337
1.49k
                             nullptr, nullptr, nullptr, nullptr);
2338
1.49k
    hasGraphics = bb_out->getHasGraphics();
2339
1.49k
    if (hasGraphics) {
2340
1.10k
        rect->x1 = bb_out->getX1();
2341
1.10k
        rect->y1 = bb_out->getY1();
2342
1.10k
        rect->x2 = bb_out->getX2();
2343
1.10k
        rect->y2 = bb_out->getY2();
2344
1.10k
    }
2345
2346
1.49k
    delete bb_out;
2347
1.49k
    return hasGraphics;
2348
1.49k
}
2349
2350
/**
2351
 * poppler_page_get_text_layout:
2352
 * @page: A #PopplerPage
2353
 * @rectangles: (out) (array length=n_rectangles) (transfer container): return location for an array of #PopplerRectangle
2354
 * @n_rectangles: (out): length of returned array
2355
 *
2356
 * Obtains the layout of the text as a list of #PopplerRectangle
2357
 * This array must be freed with g_free() when done.
2358
 *
2359
 * The position in the array represents an offset in the text returned by
2360
 * poppler_page_get_text()
2361
 *
2362
 * See also poppler_page_get_text_layout_for_area().
2363
 *
2364
 * Return value: %TRUE if the page contains text, %FALSE otherwise
2365
 *
2366
 * Since: 0.16
2367
 **/
2368
gboolean poppler_page_get_text_layout(PopplerPage *page, PopplerRectangle **rectangles, guint *n_rectangles)
2369
0
{
2370
0
    PopplerRectangle selection = { .x1 = 0, .y1 = 0, .x2 = 0, .y2 = 0 };
2371
2372
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), FALSE);
2373
2374
0
    poppler_page_get_size(page, &selection.x2, &selection.y2);
2375
2376
0
    return poppler_page_get_text_layout_for_area(page, &selection, rectangles, n_rectangles);
2377
0
}
2378
2379
/**
2380
 * poppler_page_get_text_layout_for_area:
2381
 * @page: A #PopplerPage
2382
 * @area: a #PopplerRectangle
2383
 * @rectangles: (out) (array length=n_rectangles) (transfer container): return location for an array of #PopplerRectangle
2384
 * @n_rectangles: (out): length of returned array
2385
 *
2386
 * Obtains the layout of the text contained in @area as a list of #PopplerRectangle
2387
 * This array must be freed with g_free() when done.
2388
 *
2389
 * The position in the array represents an offset in the text returned by
2390
 * poppler_page_get_text_for_area()
2391
 *
2392
 * Return value: %TRUE if the page contains text, %FALSE otherwise
2393
 *
2394
 * Since: 0.26
2395
 **/
2396
gboolean poppler_page_get_text_layout_for_area(PopplerPage *page, PopplerRectangle *area, PopplerRectangle **rectangles, guint *n_rectangles)
2397
0
{
2398
0
    TextPage *text;
2399
0
    PopplerRectangle *rect;
2400
0
    PDFRectangle selection;
2401
0
    guint offset = 0;
2402
0
    guint n_rects = 0;
2403
0
    gdouble x1 = 0, y1 = 0, x2 = 0, y2 = 0;
2404
0
    gdouble x3, y3, x4, y4;
2405
2406
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), FALSE);
2407
0
    g_return_val_if_fail(area != nullptr, FALSE);
2408
2409
0
    *n_rectangles = 0;
2410
2411
0
    selection.x1 = area->x1;
2412
0
    selection.y1 = area->y1;
2413
0
    selection.x2 = area->x2;
2414
0
    selection.y2 = area->y2;
2415
2416
0
    text = poppler_page_get_text_page(page);
2417
0
    std::vector<std::vector<std::unique_ptr<TextWordSelection>>> word_list = text->getSelectionWords(selection, selectionStyleGlyph);
2418
0
    if (word_list.empty()) {
2419
0
        return FALSE;
2420
0
    }
2421
2422
0
    n_rects += word_list.size() - 1;
2423
0
    for (const std::vector<std::unique_ptr<TextWordSelection>> &line_words : word_list) {
2424
0
        n_rects += line_words.size() - 1;
2425
0
        for (std::size_t j = 0; j < line_words.size(); j++) {
2426
0
            const TextWordSelection *word_sel = line_words[j].get();
2427
0
            n_rects += word_sel->getEnd() - word_sel->getBegin();
2428
0
            if (!word_sel->getWord()->hasSpaceAfter() && j < line_words.size() - 1) {
2429
0
                n_rects--;
2430
0
            }
2431
0
        }
2432
0
    }
2433
2434
0
    *rectangles = g_new(PopplerRectangle, n_rects);
2435
0
    *n_rectangles = n_rects;
2436
2437
0
    for (size_t i = 0; i < word_list.size(); i++) {
2438
0
        std::vector<std::unique_ptr<TextWordSelection>> &line_words = word_list[i];
2439
0
        for (std::size_t j = 0; j < line_words.size(); j++) {
2440
0
            TextWordSelection *word_sel = line_words[j].get();
2441
0
            const TextWord *word = word_sel->getWord();
2442
0
            int end = word_sel->getEnd();
2443
2444
0
            for (int k = word_sel->getBegin(); k < end; k++) {
2445
0
                rect = *rectangles + offset;
2446
0
                word->getCharBBox(k, &(rect->x1), &(rect->y1), &(rect->x2), &(rect->y2));
2447
0
                offset++;
2448
0
            }
2449
2450
0
            rect = *rectangles + offset;
2451
0
            word->getBBox(&x1, &y1, &x2, &y2);
2452
2453
0
            if (word->hasSpaceAfter() && j < line_words.size() - 1) {
2454
0
                TextWordSelection *next_word_sel = line_words[j + 1].get();
2455
2456
0
                next_word_sel->getWord()->getBBox(&x3, &y3, &x4, &y4);
2457
                // space is from one word to other and with the same height as
2458
                // first word.
2459
0
                rect->x1 = x2;
2460
0
                rect->y1 = y1;
2461
0
                rect->x2 = x3;
2462
0
                rect->y2 = y2;
2463
0
                offset++;
2464
0
            }
2465
0
        }
2466
2467
0
        if (i < word_list.size() - 1 && offset > 0) {
2468
            // end of line
2469
0
            rect->x1 = x2;
2470
0
            rect->y1 = y2;
2471
0
            rect->x2 = x2;
2472
0
            rect->y2 = y2;
2473
0
            offset++;
2474
0
        }
2475
0
    }
2476
0
    return TRUE;
2477
0
}
2478
2479
/**
2480
 * poppler_page_free_text_attributes:
2481
 * @list: (element-type PopplerTextAttributes): A list of
2482
 *   #PopplerTextAttributes<!-- -->s
2483
 *
2484
 * Frees a list of #PopplerTextAttributes<!-- -->s allocated by
2485
 * poppler_page_get_text_attributes().
2486
 *
2487
 * Since: 0.18
2488
 **/
2489
void poppler_page_free_text_attributes(GList *list)
2490
0
{
2491
0
    if (G_UNLIKELY(list == nullptr)) {
2492
0
        return;
2493
0
    }
2494
2495
0
    g_list_free_full(list, reinterpret_cast<GDestroyNotify>(poppler_text_attributes_free));
2496
0
}
2497
2498
static gboolean word_text_attributes_equal(const TextWord *a, gint ai, const TextWord *b, gint bi)
2499
0
{
2500
0
    double ar, ag, ab, br, bg, bb;
2501
2502
0
    if (!a->getFontInfo(ai)->matches(b->getFontInfo(bi))) {
2503
0
        return FALSE;
2504
0
    }
2505
2506
0
    if (a->getFontSize() != b->getFontSize()) {
2507
0
        return FALSE;
2508
0
    }
2509
2510
0
    if (a->isUnderlined() != b->isUnderlined()) {
2511
0
        return FALSE;
2512
0
    }
2513
2514
0
    a->getColor(&ar, &ag, &ab);
2515
0
    b->getColor(&br, &bg, &bb);
2516
0
    return (ar == br && ag == bg && ab == bb);
2517
0
}
2518
2519
/**
2520
 * poppler_page_get_text_attributes:
2521
 * @page: A #PopplerPage
2522
 *
2523
 * Obtains the attributes of the text as a #GList of #PopplerTextAttributes.
2524
 * This list must be freed with poppler_page_free_text_attributes() when done.
2525
 *
2526
 * Each list element is a #PopplerTextAttributes struct where start_index and
2527
 * end_index indicates the range of text (as returned by poppler_page_get_text())
2528
 * to which text attributes apply.
2529
 *
2530
 * See also poppler_page_get_text_attributes_for_area()
2531
 *
2532
 * Return value: (element-type PopplerTextAttributes) (transfer full): A #GList of #PopplerTextAttributes
2533
 *
2534
 * Since: 0.18
2535
 **/
2536
GList *poppler_page_get_text_attributes(PopplerPage *page)
2537
0
{
2538
0
    PopplerRectangle selection = { .x1 = 0, .y1 = 0, .x2 = 0, .y2 = 0 };
2539
2540
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
2541
2542
0
    poppler_page_get_size(page, &selection.x2, &selection.y2);
2543
2544
0
    return poppler_page_get_text_attributes_for_area(page, &selection);
2545
0
}
2546
2547
/**
2548
 * poppler_page_get_text_attributes_for_area:
2549
 * @page: A #PopplerPage
2550
 * @area: a #PopplerRectangle
2551
 *
2552
 * Obtains the attributes of the text in @area as a #GList of #PopplerTextAttributes.
2553
 * This list must be freed with poppler_page_free_text_attributes() when done.
2554
 *
2555
 * Each list element is a #PopplerTextAttributes struct where start_index and
2556
 * end_index indicates the range of text (as returned by poppler_page_get_text_for_area())
2557
 * to which text attributes apply.
2558
 *
2559
 * Return value: (element-type PopplerTextAttributes) (transfer full): A #GList of #PopplerTextAttributes
2560
 *
2561
 * Since: 0.26
2562
 **/
2563
GList *poppler_page_get_text_attributes_for_area(PopplerPage *page, PopplerRectangle *area)
2564
0
{
2565
0
    TextPage *text;
2566
0
    PDFRectangle selection;
2567
0
    PopplerTextAttributes *attrs = nullptr;
2568
0
    const TextWord *word, *prev_word = nullptr;
2569
0
    gint word_i, prev_word_i;
2570
0
    gint offset = 0;
2571
0
    GList *attributes = nullptr;
2572
2573
0
    g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL);
2574
0
    g_return_val_if_fail(area != nullptr, nullptr);
2575
2576
0
    selection.x1 = area->x1;
2577
0
    selection.y1 = area->y1;
2578
0
    selection.x2 = area->x2;
2579
0
    selection.y2 = area->y2;
2580
2581
0
    text = poppler_page_get_text_page(page);
2582
0
    std::vector<std::vector<std::unique_ptr<TextWordSelection>>> word_list = text->getSelectionWords(selection, selectionStyleGlyph);
2583
0
    if (word_list.empty()) {
2584
0
        return nullptr;
2585
0
    }
2586
2587
0
    for (size_t i = 0; i < word_list.size(); i++) {
2588
0
        std::vector<std::unique_ptr<TextWordSelection>> &line_words = word_list[i];
2589
0
        for (std::size_t j = 0; j < line_words.size(); j++) {
2590
0
            TextWordSelection *word_sel = line_words[j].get();
2591
0
            int end = word_sel->getEnd();
2592
2593
0
            word = word_sel->getWord();
2594
2595
0
            for (word_i = word_sel->getBegin(); word_i < end; word_i++) {
2596
0
                if (!prev_word || !word_text_attributes_equal(word, word_i, prev_word, prev_word_i)) {
2597
0
                    attrs = poppler_text_attributes_new_from_word(word, word_i);
2598
0
                    attrs->start_index = offset;
2599
0
                    attributes = g_list_prepend(attributes, attrs);
2600
0
                }
2601
0
                attrs->end_index = offset;
2602
0
                offset++;
2603
0
                prev_word = word;
2604
0
                prev_word_i = word_i;
2605
0
            }
2606
2607
0
            if (word->hasSpaceAfter() && j < line_words.size() - 1) {
2608
0
                attrs->end_index = offset;
2609
0
                offset++;
2610
0
            }
2611
0
        }
2612
2613
0
        if (i < word_list.size() - 1) {
2614
0
            attrs->end_index = offset;
2615
0
            offset++;
2616
0
        }
2617
0
    }
2618
0
    return g_list_reverse(attributes);
2619
0
}