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 | |
36 | static 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 | |
44 | enum |
45 | { |
46 | PROP_0, |
47 | PROP_LABEL |
48 | }; |
49 | |
50 | static PopplerRectangleExtended *poppler_rectangle_extended_new(); |
51 | |
52 | typedef struct _PopplerPageClass PopplerPageClass; |
53 | struct _PopplerPageClass |
54 | { |
55 | GObjectClass parent_class; |
56 | }; |
57 | |
58 | G_DEFINE_TYPE(PopplerPage, poppler_page, G_TYPE_OBJECT) |
59 | |
60 | PopplerPage *_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 | |
74 | static 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 | **/ |
97 | void 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 | **/ |
129 | int 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 | **/ |
148 | gchar *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 | **/ |
166 | double 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 | **/ |
181 | PopplerPageTransition *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 | |
254 | static 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 | |
276 | static 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 | |
294 | static 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 | |
314 | static 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 | **/ |
352 | void 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 | **/ |
373 | void 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 | **/ |
416 | void 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 | |
423 | static 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 | **/ |
475 | cairo_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 | **/ |
512 | void 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 | **/ |
556 | gboolean 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 | **/ |
597 | GList *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 | */ |
651 | void 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 | **/ |
675 | cairo_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 | **/ |
733 | char *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 | **/ |
779 | char *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 | **/ |
801 | char *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 | **/ |
839 | GList *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 | **/ |
919 | GList *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 | |
924 | static 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 | **/ |
955 | GList *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 | |
990 | static 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 | **/ |
1004 | cairo_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 | **/ |
1040 | void 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 | **/ |
1057 | void 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 | |
1079 | static 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 | |
1092 | static 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 | |
1107 | static 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 | **/ |
1119 | GList *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 | **/ |
1200 | void 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 | **/ |
1219 | GList *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 | **/ |
1262 | void 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 | **/ |
1281 | GList *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 | **/ |
1418 | void 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 */ |
1429 | AnnotQuadrilaterals *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) */ |
1449 | static 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 | |
1476 | AnnotQuadrilaterals *_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. */ |
1515 | void _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 | */ |
1557 | void 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 | */ |
1612 | void 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 | |
1622 | G_DEFINE_BOXED_TYPE(PopplerRectangle, poppler_rectangle, poppler_rectangle_copy, poppler_rectangle_free) |
1623 | |
1624 | static PopplerRectangleExtended *poppler_rectangle_extended_new() |
1625 | { |
1626 | return g_slice_new0(PopplerRectangleExtended); |
1627 | } |
1628 | |
1629 | PopplerRectangle *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 | */ |
1647 | PopplerRectangle *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 | */ |
1663 | PopplerRectangle *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 | */ |
1681 | void 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 | */ |
1705 | gboolean 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 | */ |
1732 | gboolean 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 | |
1740 | G_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 | **/ |
1751 | PopplerPoint *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 | **/ |
1767 | PopplerPoint *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 | **/ |
1782 | void poppler_point_free(PopplerPoint *point) |
1783 | { |
1784 | g_slice_free(PopplerPoint, point); |
1785 | } |
1786 | |
1787 | /* PopplerQuadrilateral type */ |
1788 | |
1789 | G_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 | **/ |
1800 | PopplerQuadrilateral *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 | **/ |
1815 | PopplerQuadrilateral *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 | **/ |
1830 | void poppler_quadrilateral_free(PopplerQuadrilateral *quad) |
1831 | { |
1832 | g_slice_free(PopplerQuadrilateral, quad); |
1833 | } |
1834 | |
1835 | /* PopplerTextAttributes type */ |
1836 | |
1837 | G_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 | */ |
1848 | PopplerTextAttributes *poppler_text_attributes_new(void) |
1849 | { |
1850 | return (PopplerTextAttributes *)g_slice_new0(PopplerTextAttributes); |
1851 | } |
1852 | |
1853 | static 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 | */ |
1882 | static 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 | */ |
1908 | PopplerTextAttributes *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 | */ |
1925 | void 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 */ |
1938 | G_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 | */ |
1947 | PopplerColor *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 | */ |
1960 | PopplerColor *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 | */ |
1976 | void poppler_color_free(PopplerColor *color) |
1977 | { |
1978 | g_free(mem: color); |
1979 | } |
1980 | |
1981 | /* PopplerLinkMapping type */ |
1982 | G_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 | */ |
1991 | PopplerLinkMapping *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 | */ |
2004 | PopplerLinkMapping *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 | */ |
2023 | void 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 */ |
2037 | G_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 | */ |
2046 | PopplerImageMapping *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 | */ |
2059 | PopplerImageMapping *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 | */ |
2070 | void poppler_image_mapping_free(PopplerImageMapping *mapping) |
2071 | { |
2072 | g_slice_free(PopplerImageMapping, mapping); |
2073 | } |
2074 | |
2075 | /* Page Transition */ |
2076 | G_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 | */ |
2085 | PopplerPageTransition *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 | */ |
2098 | PopplerPageTransition *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 | */ |
2114 | void poppler_page_transition_free(PopplerPageTransition *transition) |
2115 | { |
2116 | g_free(mem: transition); |
2117 | } |
2118 | |
2119 | /* Form Field Mapping Type */ |
2120 | G_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 | */ |
2129 | PopplerFormFieldMapping *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 | */ |
2142 | PopplerFormFieldMapping *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 | */ |
2161 | void 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 */ |
2175 | G_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 | */ |
2184 | PopplerAnnotMapping *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 | */ |
2197 | PopplerAnnotMapping *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 | */ |
2216 | void 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 | */ |
2236 | void 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 | */ |
2260 | gboolean 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 | **/ |
2304 | gboolean 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 | **/ |
2332 | gboolean 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 | **/ |
2435 | void 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 | |
2444 | static 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 | **/ |
2482 | GList *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 | **/ |
2509 | GList *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 | |