Coverage Report

Created: 2025-09-27 07:50

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