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

source code of poppler/glib/poppler-page.cc