| /* poppler-page.cc: glib wrapper for poppler |
| * Copyright (C) 2005, Red Hat, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include <cmath> |
| |
| #ifndef __GI_SCANNER__ |
| # include <GlobalParams.h> |
| # include <PDFDoc.h> |
| # include <Outline.h> |
| # include <ErrorCodes.h> |
| # include <UnicodeMap.h> |
| # include <GfxState.h> |
| # include <PageTransition.h> |
| # include <BBoxOutputDev.h> |
| #endif |
| |
| #include "poppler.h" |
| #include "poppler-private.h" |
| |
| static void _page_unrotate_xy(Page *page, double *x, double *y); |
| |
| /** |
| * SECTION:poppler-page |
| * @short_description: Information about a page in a document |
| * @title: PopplerPage |
| */ |
| |
| enum |
| { |
| PROP_0, |
| PROP_LABEL |
| }; |
| |
| static PopplerRectangleExtended *poppler_rectangle_extended_new(); |
| |
| typedef struct _PopplerPageClass PopplerPageClass; |
| struct _PopplerPageClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| G_DEFINE_TYPE(PopplerPage, poppler_page, G_TYPE_OBJECT) |
| |
| PopplerPage *_poppler_page_new(PopplerDocument *document, Page *page, int index) |
| { |
| PopplerPage *poppler_page; |
| |
| g_return_val_if_fail(POPPLER_IS_DOCUMENT(document), NULL); |
| |
| poppler_page = (PopplerPage *)g_object_new(POPPLER_TYPE_PAGE, nullptr, NULL); |
| poppler_page->document = (PopplerDocument *)g_object_ref(document); |
| poppler_page->page = page; |
| poppler_page->index = index; |
| |
| return poppler_page; |
| } |
| |
| static void poppler_page_finalize(GObject *object) |
| { |
| PopplerPage *page = POPPLER_PAGE(object); |
| |
| g_object_unref(page->document); |
| page->document = nullptr; |
| |
| if (page->text != nullptr) { |
| page->text->decRefCnt(); |
| } |
| /* page->page is owned by the document */ |
| |
| G_OBJECT_CLASS(poppler_page_parent_class)->finalize(object); |
| } |
| |
| /** |
| * poppler_page_get_size: |
| * @page: A #PopplerPage |
| * @width: (out) (allow-none): return location for the width of @page |
| * @height: (out) (allow-none): return location for the height of @page |
| * |
| * Gets the size of @page at the current scale and rotation. |
| **/ |
| void poppler_page_get_size(PopplerPage *page, double *width, double *height) |
| { |
| double page_width, page_height; |
| int rotate; |
| |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| |
| rotate = page->page->getRotate(); |
| if (rotate == 90 || rotate == 270) { |
| page_height = page->page->getCropWidth(); |
| page_width = page->page->getCropHeight(); |
| } else { |
| page_width = page->page->getCropWidth(); |
| page_height = page->page->getCropHeight(); |
| } |
| |
| if (width != nullptr) { |
| *width = page_width; |
| } |
| if (height != nullptr) { |
| *height = page_height; |
| } |
| } |
| |
| /** |
| * poppler_page_get_index: |
| * @page: a #PopplerPage |
| * |
| * Returns the index of @page |
| * |
| * Return value: index value of @page |
| **/ |
| int poppler_page_get_index(PopplerPage *page) |
| { |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), 0); |
| |
| return page->index; |
| } |
| |
| /** |
| * poppler_page_get_label: |
| * @page: a #PopplerPage |
| * |
| * Returns the label of @page. Note that page labels |
| * and page indices might not coincide. |
| * |
| * Return value: a new allocated string containing the label of @page, |
| * or %NULL if @page doesn't have a label |
| * |
| * Since: 0.16 |
| **/ |
| gchar *poppler_page_get_label(PopplerPage *page) |
| { |
| GooString label; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| page->document->doc->getCatalog()->indexToLabel(page->index, &label); |
| return _poppler_goo_string_to_utf8(&label); |
| } |
| |
| /** |
| * poppler_page_get_duration: |
| * @page: a #PopplerPage |
| * |
| * Returns the duration of @page |
| * |
| * Return value: duration in seconds of @page or -1. |
| **/ |
| double poppler_page_get_duration(PopplerPage *page) |
| { |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), -1); |
| |
| return page->page->getDuration(); |
| } |
| |
| /** |
| * poppler_page_get_transition: |
| * @page: a #PopplerPage |
| * |
| * Returns the transition effect of @page |
| * |
| * Return value: a #PopplerPageTransition or %NULL. |
| **/ |
| PopplerPageTransition *poppler_page_get_transition(PopplerPage *page) |
| { |
| PageTransition *trans; |
| PopplerPageTransition *transition; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| Object obj = page->page->getTrans(); |
| trans = new PageTransition(&obj); |
| |
| if (!trans->isOk()) { |
| delete trans; |
| return nullptr; |
| } |
| |
| transition = poppler_page_transition_new(); |
| |
| switch (trans->getType()) { |
| case transitionReplace: |
| transition->type = POPPLER_PAGE_TRANSITION_REPLACE; |
| break; |
| case transitionSplit: |
| transition->type = POPPLER_PAGE_TRANSITION_SPLIT; |
| break; |
| case transitionBlinds: |
| transition->type = POPPLER_PAGE_TRANSITION_BLINDS; |
| break; |
| case transitionBox: |
| transition->type = POPPLER_PAGE_TRANSITION_BOX; |
| break; |
| case transitionWipe: |
| transition->type = POPPLER_PAGE_TRANSITION_WIPE; |
| break; |
| case transitionDissolve: |
| transition->type = POPPLER_PAGE_TRANSITION_DISSOLVE; |
| break; |
| case transitionGlitter: |
| transition->type = POPPLER_PAGE_TRANSITION_GLITTER; |
| break; |
| case transitionFly: |
| transition->type = POPPLER_PAGE_TRANSITION_FLY; |
| break; |
| case transitionPush: |
| transition->type = POPPLER_PAGE_TRANSITION_PUSH; |
| break; |
| case transitionCover: |
| transition->type = POPPLER_PAGE_TRANSITION_COVER; |
| break; |
| case transitionUncover: |
| transition->type = POPPLER_PAGE_TRANSITION_UNCOVER; |
| break; |
| case transitionFade: |
| transition->type = POPPLER_PAGE_TRANSITION_FADE; |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| |
| transition->alignment = (trans->getAlignment() == transitionHorizontal) ? POPPLER_PAGE_TRANSITION_HORIZONTAL : POPPLER_PAGE_TRANSITION_VERTICAL; |
| |
| transition->direction = (trans->getDirection() == transitionInward) ? POPPLER_PAGE_TRANSITION_INWARD : POPPLER_PAGE_TRANSITION_OUTWARD; |
| |
| transition->duration = trans->getDuration(); |
| transition->duration_real = trans->getDuration(); |
| transition->angle = trans->getAngle(); |
| transition->scale = trans->getScale(); |
| transition->rectangular = trans->isRectangular(); |
| |
| delete trans; |
| |
| return transition; |
| } |
| |
| static TextPage *poppler_page_get_text_page(PopplerPage *page) |
| { |
| if (page->text == nullptr) { |
| TextOutputDev *text_dev; |
| Gfx *gfx; |
| |
| text_dev = new TextOutputDev(nullptr, true, 0, false, false); |
| gfx = page->page->createGfx(text_dev, 72.0, 72.0, 0, false, /* useMediaBox */ |
| true, /* Crop */ |
| -1, -1, -1, -1, false, /* printing */ |
| nullptr, nullptr); |
| page->page->display(gfx); |
| text_dev->endPage(); |
| |
| page->text = text_dev->takeText(); |
| delete gfx; |
| delete text_dev; |
| } |
| |
| return page->text; |
| } |
| |
| static gboolean annot_is_markup(Annot *annot) |
| { |
| switch (annot->getType()) { |
| case Annot::typeLink: |
| case Annot::typePopup: |
| case Annot::typeMovie: |
| case Annot::typeScreen: |
| case Annot::typePrinterMark: |
| case Annot::typeTrapNet: |
| case Annot::typeWatermark: |
| case Annot::type3D: |
| case Annot::typeWidget: |
| return FALSE; |
| default: |
| return TRUE; |
| } |
| } |
| |
| static bool poppler_print_annot_cb(Annot *annot, void *user_data) |
| { |
| PopplerPrintFlags user_print_flags = (PopplerPrintFlags)GPOINTER_TO_INT(user_data); |
| |
| if (annot->getFlags() & Annot::flagHidden) { |
| return false; |
| } |
| |
| if (user_print_flags & POPPLER_PRINT_STAMP_ANNOTS_ONLY) { |
| return (annot->getType() == Annot::typeStamp) ? (annot->getFlags() & Annot::flagPrint) : (annot->getType() == Annot::typeWidget); |
| } |
| |
| if (user_print_flags & POPPLER_PRINT_MARKUP_ANNOTS) { |
| return annot_is_markup(annot) ? (annot->getFlags() & Annot::flagPrint) : (annot->getType() == Annot::typeWidget); |
| } |
| |
| /* Print document only, form fields are always printed */ |
| return (annot->getType() == Annot::typeWidget); |
| } |
| |
| static void _poppler_page_render(PopplerPage *page, cairo_t *cairo, bool printing, PopplerPrintFlags print_flags) |
| { |
| CairoOutputDev *output_dev; |
| |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| |
| output_dev = page->document->output_dev; |
| output_dev->setCairo(cairo); |
| output_dev->setPrinting(printing); |
| |
| if (!printing && page->text == nullptr) { |
| page->text = new TextPage(false); |
| output_dev->setTextPage(page->text); |
| } |
| /* NOTE: instead of passing -1 we should/could use cairo_clip_extents() |
| * to get a bounding box */ |
| cairo_save(cairo); |
| page->page->displaySlice(output_dev, 72.0, 72.0, 0, false, /* useMediaBox */ |
| true, /* Crop */ |
| -1, -1, -1, -1, printing, nullptr, nullptr, printing ? poppler_print_annot_cb : nullptr, printing ? GINT_TO_POINTER((gint)print_flags) : nullptr); |
| cairo_restore(cairo); |
| |
| output_dev->setCairo(nullptr); |
| output_dev->setTextPage(nullptr); |
| } |
| |
| /** |
| * poppler_page_render: |
| * @page: the page to render from |
| * @cairo: cairo context to render to |
| * |
| * Render the page to the given cairo context. This function |
| * is for rendering a page that will be displayed. If you want |
| * to render a page that will be printed use |
| * poppler_page_render_for_printing() instead. Please see the documentation |
| * for that function for the differences between rendering to the screen and |
| * rendering to a printer. |
| **/ |
| void poppler_page_render(PopplerPage *page, cairo_t *cairo) |
| { |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| |
| _poppler_page_render(page, cairo, false, (PopplerPrintFlags)0); |
| } |
| |
| /** |
| * poppler_page_render_for_printing_with_options: |
| * @page: the page to render from |
| * @cairo: cairo context to render to |
| * @options: print options |
| * |
| * Render the page to the given cairo context for printing |
| * with the specified options |
| * |
| * See the documentation for poppler_page_render_for_printing() for the |
| * differences between rendering to the screen and rendering to a printer. |
| * |
| * Since: 0.16 |
| **/ |
| void poppler_page_render_for_printing_with_options(PopplerPage *page, cairo_t *cairo, PopplerPrintFlags options) |
| { |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| |
| _poppler_page_render(page, cairo, true, options); |
| } |
| |
| /** |
| * poppler_page_render_for_printing: |
| * @page: the page to render from |
| * @cairo: cairo context to render to |
| * |
| * Render the page to the given cairo context for printing with |
| * #POPPLER_PRINT_ALL flags selected. If you want a different set of flags, |
| * use poppler_page_render_for_printing_with_options(). |
| * |
| * The difference between poppler_page_render() and this function is that some |
| * things get rendered differently between screens and printers: |
| * |
| * <itemizedlist> |
| * <listitem> |
| * PDF annotations get rendered according to their #PopplerAnnotFlag value. |
| * For example, #POPPLER_ANNOT_FLAG_PRINT refers to whether an annotation |
| * is printed or not, whereas #POPPLER_ANNOT_FLAG_NO_VIEW refers to whether |
| * an annotation is invisible when displaying to the screen. |
| * </listitem> |
| * <listitem> |
| * PDF supports "hairlines" of width 0.0, which often get rendered as |
| * having a width of 1 device pixel. When displaying on a screen, Cairo |
| * may render such lines wide so that they are hard to see, and Poppler |
| * makes use of PDF's Stroke Adjust graphics parameter to make the lines |
| * easier to see. However, when printing, Poppler is able to directly use a |
| * printer's pixel size instead. |
| * </listitem> |
| * <listitem> |
| * Some advanced features in PDF may require an image to be rasterized |
| * before sending off to a printer. This may produce raster images which |
| * exceed Cairo's limits. The "printing" functions will detect this condition |
| * and try to down-scale the intermediate surfaces as appropriate. |
| * </listitem> |
| * </itemizedlist> |
| * |
| **/ |
| void poppler_page_render_for_printing(PopplerPage *page, cairo_t *cairo) |
| { |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| |
| _poppler_page_render(page, cairo, true, POPPLER_PRINT_ALL); |
| } |
| |
| static cairo_surface_t *create_surface_from_thumbnail_data(guchar *data, gint width, gint height, gint rowstride) |
| { |
| guchar *cairo_pixels; |
| gint cairo_stride; |
| cairo_surface_t *surface; |
| int j; |
| |
| surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); |
| if (cairo_surface_status(surface)) { |
| return nullptr; |
| } |
| |
| cairo_pixels = cairo_image_surface_get_data(surface); |
| cairo_stride = cairo_image_surface_get_stride(surface); |
| |
| for (j = height; j; j--) { |
| guchar *p = data; |
| guchar *q = cairo_pixels; |
| guchar *end = p + 3 * width; |
| |
| while (p < end) { |
| #if G_BYTE_ORDER == G_LITTLE_ENDIAN |
| q[0] = p[2]; |
| q[1] = p[1]; |
| q[2] = p[0]; |
| #else |
| q[1] = p[0]; |
| q[2] = p[1]; |
| q[3] = p[2]; |
| #endif |
| p += 3; |
| q += 4; |
| } |
| |
| data += rowstride; |
| cairo_pixels += cairo_stride; |
| } |
| |
| return surface; |
| } |
| |
| /** |
| * poppler_page_get_thumbnail: |
| * @page: the #PopplerPage to get the thumbnail for |
| * |
| * Get the embedded thumbnail for the specified page. If the document |
| * doesn't have an embedded thumbnail for the page, this function |
| * returns %NULL. |
| * |
| * Return value: the tumbnail as a cairo_surface_t or %NULL if the document |
| * doesn't have a thumbnail for this page. |
| **/ |
| cairo_surface_t *poppler_page_get_thumbnail(PopplerPage *page) |
| { |
| unsigned char *data; |
| int width, height, rowstride; |
| cairo_surface_t *surface; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| if (!page->page->loadThumb(&data, &width, &height, &rowstride)) { |
| return nullptr; |
| } |
| |
| surface = create_surface_from_thumbnail_data(data, width, height, rowstride); |
| gfree(data); |
| |
| return surface; |
| } |
| |
| /** |
| * poppler_page_render_selection: |
| * @page: the #PopplerPage for which to render selection |
| * @cairo: cairo context to render to |
| * @selection: start and end point of selection as a rectangle |
| * @old_selection: previous selection |
| * @style: a #PopplerSelectionStyle |
| * @glyph_color: color to use for drawing glyphs |
| * @background_color: color to use for the selection background |
| * |
| * Render the selection specified by @selection for @page to |
| * the given cairo context. The selection will be rendered, using |
| * @glyph_color for the glyphs and @background_color for the selection |
| * background. |
| * |
| * If non-NULL, @old_selection specifies the selection that is already |
| * rendered to @cairo, in which case this function will (some day) |
| * only render the changed part of the selection. |
| **/ |
| void poppler_page_render_selection(PopplerPage *page, cairo_t *cairo, PopplerRectangle *selection, PopplerRectangle *old_selection, PopplerSelectionStyle style, PopplerColor *glyph_color, PopplerColor *background_color) |
| { |
| CairoOutputDev *output_dev; |
| TextPage *text; |
| SelectionStyle selection_style = selectionStyleGlyph; |
| PDFRectangle pdf_selection(selection->x1, selection->y1, selection->x2, selection->y2); |
| |
| GfxColor gfx_background_color = { { background_color->red, background_color->green, background_color->blue } }; |
| GfxColor gfx_glyph_color = { { glyph_color->red, glyph_color->green, glyph_color->blue } }; |
| |
| switch (style) { |
| case POPPLER_SELECTION_GLYPH: |
| selection_style = selectionStyleGlyph; |
| break; |
| case POPPLER_SELECTION_WORD: |
| selection_style = selectionStyleWord; |
| break; |
| case POPPLER_SELECTION_LINE: |
| selection_style = selectionStyleLine; |
| break; |
| } |
| |
| output_dev = page->document->output_dev; |
| output_dev->setCairo(cairo); |
| |
| text = poppler_page_get_text_page(page); |
| text->drawSelection(output_dev, 1.0, 0, &pdf_selection, selection_style, &gfx_glyph_color, &gfx_background_color); |
| |
| output_dev->setCairo(nullptr); |
| } |
| |
| /** |
| * poppler_page_get_thumbnail_size: |
| * @page: A #PopplerPage |
| * @width: (out): return location for width |
| * @height: (out): return location for height |
| * |
| * Returns %TRUE if @page has a thumbnail associated with it. It also |
| * fills in @width and @height with the width and height of the |
| * thumbnail. The values of width and height are not changed if no |
| * appropriate thumbnail exists. |
| * |
| * Return value: %TRUE, if @page has a thumbnail associated with it. |
| **/ |
| gboolean poppler_page_get_thumbnail_size(PopplerPage *page, int *width, int *height) |
| { |
| Dict *dict; |
| gboolean retval = FALSE; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), FALSE); |
| g_return_val_if_fail(width != nullptr, FALSE); |
| g_return_val_if_fail(height != nullptr, FALSE); |
| |
| Object thumb = page->page->getThumb(); |
| if (!thumb.isStream()) { |
| return FALSE; |
| } |
| |
| dict = thumb.streamGetDict(); |
| |
| /* Theoretically, this could succeed and you would still fail when |
| * loading the thumb */ |
| if (dict->lookupInt("Width", "W", width) && dict->lookupInt("Height", "H", height)) { |
| retval = TRUE; |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * poppler_page_get_selection_region: |
| * @page: a #PopplerPage |
| * @scale: scale specified as pixels per point |
| * @style: a #PopplerSelectionStyle |
| * @selection: start and end point of selection as a rectangle |
| * |
| * Returns a region containing the area that would be rendered by |
| * poppler_page_render_selection() as a #GList of |
| * #PopplerRectangle. The returned list must be freed with |
| * poppler_page_selection_region_free(). |
| * |
| * Return value: (element-type PopplerRectangle) (transfer full): a #GList of #PopplerRectangle |
| * |
| * Deprecated: 0.16: Use poppler_page_get_selected_region() instead. |
| **/ |
| GList *poppler_page_get_selection_region(PopplerPage *page, gdouble scale, PopplerSelectionStyle style, PopplerRectangle *selection) |
| { |
| PDFRectangle poppler_selection; |
| TextPage *text; |
| SelectionStyle selection_style = selectionStyleGlyph; |
| GList *region = nullptr; |
| |
| poppler_selection.x1 = selection->x1; |
| poppler_selection.y1 = selection->y1; |
| poppler_selection.x2 = selection->x2; |
| poppler_selection.y2 = selection->y2; |
| |
| switch (style) { |
| case POPPLER_SELECTION_GLYPH: |
| selection_style = selectionStyleGlyph; |
| break; |
| case POPPLER_SELECTION_WORD: |
| selection_style = selectionStyleWord; |
| break; |
| case POPPLER_SELECTION_LINE: |
| selection_style = selectionStyleLine; |
| break; |
| } |
| |
| text = poppler_page_get_text_page(page); |
| std::vector<PDFRectangle *> *list = text->getSelectionRegion(&poppler_selection, selection_style, scale); |
| |
| for (const PDFRectangle *selection_rect : *list) { |
| PopplerRectangle *rect; |
| |
| rect = poppler_rectangle_new_from_pdf_rectangle(selection_rect); |
| |
| region = g_list_prepend(region, rect); |
| |
| delete selection_rect; |
| } |
| |
| delete list; |
| |
| return g_list_reverse(region); |
| } |
| |
| /** |
| * poppler_page_selection_region_free: |
| * @region: (element-type PopplerRectangle): a #GList of |
| * #PopplerRectangle |
| * |
| * Frees @region |
| * |
| * Deprecated: 0.16: Use only to free deprecated regions created by |
| * poppler_page_get_selection_region(). Regions created by |
| * poppler_page_get_selected_region() should be freed with |
| * cairo_region_destroy() instead. |
| */ |
| void poppler_page_selection_region_free(GList *region) |
| { |
| if (G_UNLIKELY(!region)) { |
| return; |
| } |
| |
| g_list_free_full(region, (GDestroyNotify)poppler_rectangle_free); |
| } |
| |
| /** |
| * poppler_page_get_selected_region: |
| * @page: a #PopplerPage |
| * @scale: scale specified as pixels per point |
| * @style: a #PopplerSelectionStyle |
| * @selection: start and end point of selection as a rectangle |
| * |
| * Returns a region containing the area that would be rendered by |
| * poppler_page_render_selection(). |
| * The returned region must be freed with cairo_region_destroy() |
| * |
| * Return value: (transfer full): a cairo_region_t |
| * |
| * Since: 0.16 |
| **/ |
| cairo_region_t *poppler_page_get_selected_region(PopplerPage *page, gdouble scale, PopplerSelectionStyle style, PopplerRectangle *selection) |
| { |
| PDFRectangle poppler_selection; |
| TextPage *text; |
| SelectionStyle selection_style = selectionStyleGlyph; |
| cairo_region_t *region; |
| |
| poppler_selection.x1 = selection->x1; |
| poppler_selection.y1 = selection->y1; |
| poppler_selection.x2 = selection->x2; |
| poppler_selection.y2 = selection->y2; |
| |
| switch (style) { |
| case POPPLER_SELECTION_GLYPH: |
| selection_style = selectionStyleGlyph; |
| break; |
| case POPPLER_SELECTION_WORD: |
| selection_style = selectionStyleWord; |
| break; |
| case POPPLER_SELECTION_LINE: |
| selection_style = selectionStyleLine; |
| break; |
| } |
| |
| text = poppler_page_get_text_page(page); |
| std::vector<PDFRectangle *> *list = text->getSelectionRegion(&poppler_selection, selection_style, 1.0); |
| |
| region = cairo_region_create(); |
| |
| for (const PDFRectangle *selection_rect : *list) { |
| cairo_rectangle_int_t rect; |
| |
| rect.x = (gint)((selection_rect->x1 * scale) + 0.5); |
| rect.y = (gint)((selection_rect->y1 * scale) + 0.5); |
| rect.width = (gint)(((selection_rect->x2 - selection_rect->x1) * scale) + 0.5); |
| rect.height = (gint)(((selection_rect->y2 - selection_rect->y1) * scale) + 0.5); |
| cairo_region_union_rectangle(region, &rect); |
| |
| delete selection_rect; |
| } |
| |
| delete list; |
| |
| return region; |
| } |
| |
| /** |
| * poppler_page_get_selected_text: |
| * @page: a #PopplerPage |
| * @style: a #PopplerSelectionStyle |
| * @selection: the #PopplerRectangle including the text |
| * |
| * Retrieves the contents of the specified @selection as text. |
| * |
| * Return value: a pointer to the contents of the @selection |
| * as a string |
| * Since: 0.16 |
| **/ |
| char *poppler_page_get_selected_text(PopplerPage *page, PopplerSelectionStyle style, PopplerRectangle *selection) |
| { |
| GooString *sel_text; |
| char *result; |
| TextPage *text; |
| SelectionStyle selection_style = selectionStyleGlyph; |
| PDFRectangle pdf_selection; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| g_return_val_if_fail(selection != nullptr, NULL); |
| |
| pdf_selection.x1 = selection->x1; |
| pdf_selection.y1 = selection->y1; |
| pdf_selection.x2 = selection->x2; |
| pdf_selection.y2 = selection->y2; |
| |
| switch (style) { |
| case POPPLER_SELECTION_GLYPH: |
| selection_style = selectionStyleGlyph; |
| break; |
| case POPPLER_SELECTION_WORD: |
| selection_style = selectionStyleWord; |
| break; |
| case POPPLER_SELECTION_LINE: |
| selection_style = selectionStyleLine; |
| break; |
| } |
| |
| text = poppler_page_get_text_page(page); |
| sel_text = text->getSelectionText(&pdf_selection, selection_style); |
| result = g_strdup(sel_text->c_str()); |
| delete sel_text; |
| |
| return result; |
| } |
| |
| /** |
| * poppler_page_get_text: |
| * @page: a #PopplerPage |
| * |
| * Retrieves the text of @page. |
| * |
| * Return value: a pointer to the text of the @page |
| * as a string |
| * Since: 0.16 |
| **/ |
| char *poppler_page_get_text(PopplerPage *page) |
| { |
| PopplerRectangle rectangle = { 0, 0, 0, 0 }; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| poppler_page_get_size(page, &rectangle.x2, &rectangle.y2); |
| |
| return poppler_page_get_selected_text(page, POPPLER_SELECTION_GLYPH, &rectangle); |
| } |
| |
| /** |
| * poppler_page_get_text_for_area: |
| * @page: a #PopplerPage |
| * @area: a #PopplerRectangle |
| * |
| * Retrieves the text of @page contained in @area. |
| * |
| * Return value: a pointer to the text as a string |
| * |
| * Since: 0.26 |
| **/ |
| char *poppler_page_get_text_for_area(PopplerPage *page, PopplerRectangle *area) |
| { |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| g_return_val_if_fail(area != nullptr, NULL); |
| |
| return poppler_page_get_selected_text(page, POPPLER_SELECTION_GLYPH, area); |
| } |
| |
| /** |
| * poppler_page_find_text_with_options: |
| * @page: a #PopplerPage |
| * @text: the text to search for (UTF-8 encoded) |
| * @options: find options |
| * |
| * Finds @text in @page with the given #PopplerFindFlags options and |
| * returns a #GList of rectangles for each occurrence of the text on the page. |
| * The coordinates are in PDF points. |
| * |
| * When %POPPLER_FIND_MULTILINE is passed in @options, matches may span more than |
| * one line. In this case, the returned list will contain one #PopplerRectangle |
| * for each part of a match. The function poppler_rectangle_find_get_match_continued() |
| * will return %TRUE for all rectangles belonging to the same match, except for |
| * the last one. If a hyphen was ignored at the end of the part of the match, |
| * poppler_rectangle_find_get_ignored_hyphen() will return %TRUE for that |
| * rectangle. |
| * |
| * Note that currently matches spanning more than two lines are not found. |
| * (This limitation may be lifted in a future version.) |
| * |
| * Note also that currently finding multi-line matches backwards is not |
| * implemented; if you pass %POPPLER_FIND_BACKWARDS and %POPPLER_FIND_MULTILINE |
| * together, %POPPLER_FIND_MULTILINE will be ignored. |
| * |
| * Return value: (element-type PopplerRectangle) (transfer full): a newly allocated list |
| * of newly allocated #PopplerRectangle. Free with g_list_free_full() using poppler_rectangle_free(). |
| * |
| * Since: 0.22 |
| **/ |
| GList *poppler_page_find_text_with_options(PopplerPage *page, const char *text, PopplerFindFlags options) |
| { |
| PopplerRectangleExtended *match; |
| GList *matches; |
| double xMin, yMin, xMax, yMax; |
| PDFRectangle continueMatch; |
| bool ignoredHyphen; |
| gunichar *ucs4; |
| glong ucs4_len; |
| double height; |
| TextPage *text_dev; |
| gboolean backwards; |
| gboolean start_at_last = FALSE; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| g_return_val_if_fail(text != nullptr, NULL); |
| |
| text_dev = poppler_page_get_text_page(page); |
| |
| ucs4 = g_utf8_to_ucs4_fast(text, -1, &ucs4_len); |
| poppler_page_get_size(page, nullptr, &height); |
| |
| const bool multiline = (options & POPPLER_FIND_MULTILINE); |
| backwards = options & POPPLER_FIND_BACKWARDS; |
| matches = nullptr; |
| xMin = 0; |
| yMin = backwards ? height : 0; |
| |
| continueMatch.x1 = std::numeric_limits<double>::max(); // we use this to detect valid returned values |
| |
| while (text_dev->findText(ucs4, ucs4_len, false, true, // startAtTop, stopAtBottom |
| start_at_last, |
| false, // stopAtLast |
| options & POPPLER_FIND_CASE_SENSITIVE, options & POPPLER_FIND_IGNORE_DIACRITICS, options & POPPLER_FIND_MULTILINE, backwards, options & POPPLER_FIND_WHOLE_WORDS_ONLY, &xMin, &yMin, &xMax, &yMax, &continueMatch, |
| &ignoredHyphen)) { |
| match = poppler_rectangle_extended_new(); |
| match->x1 = xMin; |
| match->y1 = height - yMax; |
| match->x2 = xMax; |
| match->y2 = height - yMin; |
| match->match_continued = false; |
| match->ignored_hyphen = false; |
| matches = g_list_prepend(matches, match); |
| start_at_last = TRUE; |
| |
| if (continueMatch.x1 != std::numeric_limits<double>::max()) { |
| // received rect for next-line part of a multi-line match, add it. |
| if (multiline) { |
| match->match_continued = true; |
| match->ignored_hyphen = ignoredHyphen; |
| match = poppler_rectangle_extended_new(); |
| match->x1 = continueMatch.x1; |
| match->y1 = height - continueMatch.y1; |
| match->x2 = continueMatch.x2; |
| match->y2 = height - continueMatch.y2; |
| match->match_continued = false; |
| match->ignored_hyphen = false; |
| matches = g_list_prepend(matches, match); |
| } |
| |
| continueMatch.x1 = std::numeric_limits<double>::max(); |
| } |
| } |
| |
| g_free(ucs4); |
| |
| return g_list_reverse(matches); |
| } |
| |
| /** |
| * poppler_page_find_text: |
| * @page: a #PopplerPage |
| * @text: the text to search for (UTF-8 encoded) |
| * |
| * Finds @text in @page with the default options (%POPPLER_FIND_DEFAULT) and |
| * returns a #GList of rectangles for each occurrence of the text on the page. |
| * The coordinates are in PDF points. |
| * |
| * Return value: (element-type PopplerRectangle) (transfer full): a #GList of #PopplerRectangle, |
| **/ |
| GList *poppler_page_find_text(PopplerPage *page, const char *text) |
| { |
| return poppler_page_find_text_with_options(page, text, POPPLER_FIND_DEFAULT); |
| } |
| |
| static CairoImageOutputDev *poppler_page_get_image_output_dev(PopplerPage *page, bool (*imgDrawDeviceCbk)(int img_id, void *data), void *imgDrawCbkData) |
| { |
| CairoImageOutputDev *image_dev; |
| Gfx *gfx; |
| |
| image_dev = new CairoImageOutputDev(); |
| |
| if (imgDrawDeviceCbk) { |
| image_dev->setImageDrawDecideCbk(imgDrawDeviceCbk, imgDrawCbkData); |
| } |
| |
| gfx = page->page->createGfx(image_dev, 72.0, 72.0, 0, false, /* useMediaBox */ |
| true, /* Crop */ |
| -1, -1, -1, -1, false, /* printing */ |
| nullptr, nullptr); |
| page->page->display(gfx); |
| delete gfx; |
| |
| return image_dev; |
| } |
| |
| /** |
| * poppler_page_get_image_mapping: |
| * @page: A #PopplerPage |
| * |
| * Returns a list of #PopplerImageMapping items that map from a |
| * location on @page to an image of the page. This list must be freed |
| * with poppler_page_free_image_mapping() when done. |
| * |
| * Return value: (element-type PopplerImageMapping) (transfer full): A #GList of #PopplerImageMapping |
| **/ |
| GList *poppler_page_get_image_mapping(PopplerPage *page) |
| { |
| GList *map_list = nullptr; |
| CairoImageOutputDev *out; |
| gint i; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| out = poppler_page_get_image_output_dev(page, nullptr, nullptr); |
| |
| for (i = 0; i < out->getNumImages(); i++) { |
| PopplerImageMapping *mapping; |
| CairoImage *image; |
| |
| image = out->getImage(i); |
| |
| /* Create the mapping */ |
| mapping = poppler_image_mapping_new(); |
| |
| image->getRect(&(mapping->area.x1), &(mapping->area.y1), &(mapping->area.x2), &(mapping->area.y2)); |
| mapping->image_id = i; |
| |
| mapping->area.x1 -= page->page->getCropBox()->x1; |
| mapping->area.x2 -= page->page->getCropBox()->x1; |
| mapping->area.y1 -= page->page->getCropBox()->y1; |
| mapping->area.y2 -= page->page->getCropBox()->y1; |
| |
| map_list = g_list_prepend(map_list, mapping); |
| } |
| |
| delete out; |
| |
| return map_list; |
| } |
| |
| static bool image_draw_decide_cb(int image_id, void *data) |
| { |
| return (image_id == GPOINTER_TO_INT(data)); |
| } |
| |
| /** |
| * poppler_page_get_image: |
| * @page: A #PopplerPage |
| * @image_id: The image identifier |
| * |
| * Returns a cairo surface for the image of the @page |
| * |
| * Return value: A cairo surface for the image |
| **/ |
| cairo_surface_t *poppler_page_get_image(PopplerPage *page, gint image_id) |
| { |
| CairoImageOutputDev *out; |
| cairo_surface_t *image; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| out = poppler_page_get_image_output_dev(page, image_draw_decide_cb, GINT_TO_POINTER(image_id)); |
| |
| if (image_id >= out->getNumImages()) { |
| delete out; |
| |
| return nullptr; |
| } |
| |
| image = out->getImage(image_id)->getImage(); |
| if (!image) { |
| delete out; |
| |
| return nullptr; |
| } |
| |
| cairo_surface_reference(image); |
| delete out; |
| |
| return image; |
| } |
| |
| /** |
| * poppler_page_free_image_mapping: |
| * @list: (element-type PopplerImageMapping): A list of |
| * #PopplerImageMapping<!-- -->s |
| * |
| * Frees a list of #PopplerImageMapping<!-- -->s allocated by |
| * poppler_page_get_image_mapping(). |
| **/ |
| void poppler_page_free_image_mapping(GList *list) |
| { |
| if (G_UNLIKELY(list == nullptr)) { |
| return; |
| } |
| |
| g_list_free_full(list, (GDestroyNotify)poppler_image_mapping_free); |
| } |
| |
| /** |
| * poppler_page_render_to_ps: |
| * @page: a #PopplerPage |
| * @ps_file: the PopplerPSFile to render to |
| * |
| * Render the page on a postscript file |
| * |
| **/ |
| void poppler_page_render_to_ps(PopplerPage *page, PopplerPSFile *ps_file) |
| { |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| g_return_if_fail(ps_file != nullptr); |
| |
| if (!ps_file->out) { |
| std::vector<int> pages; |
| for (int i = ps_file->first_page; i <= ps_file->last_page; ++i) { |
| pages.push_back(i); |
| } |
| if (ps_file->fd != -1) { |
| ps_file->out = |
| 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); |
| } else { |
| 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, |
| nullptr, nullptr); |
| } |
| } |
| |
| ps_file->document->doc->displayPage(ps_file->out, page->index + 1, 72.0, 72.0, 0, false, true, false); |
| } |
| |
| static void poppler_page_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) |
| { |
| PopplerPage *page = POPPLER_PAGE(object); |
| |
| switch (prop_id) { |
| case PROP_LABEL: |
| g_value_take_string(value, poppler_page_get_label(page)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
| } |
| } |
| |
| static void poppler_page_class_init(PopplerPageClass *klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS(klass); |
| |
| gobject_class->finalize = poppler_page_finalize; |
| gobject_class->get_property = poppler_page_get_property; |
| |
| /** |
| * PopplerPage:label: |
| * |
| * The label of the page or %NULL. See also poppler_page_get_label() |
| */ |
| g_object_class_install_property(G_OBJECT_CLASS(klass), PROP_LABEL, g_param_spec_string("label", "Page Label", "The label of the page", nullptr, G_PARAM_READABLE)); |
| } |
| |
| static void poppler_page_init(PopplerPage *page) { } |
| |
| /** |
| * poppler_page_get_link_mapping: |
| * @page: A #PopplerPage |
| * |
| * Returns a list of #PopplerLinkMapping items that map from a |
| * location on @page to a #PopplerAction. This list must be freed |
| * with poppler_page_free_link_mapping() when done. |
| * |
| * Return value: (element-type PopplerLinkMapping) (transfer full): A #GList of #PopplerLinkMapping |
| **/ |
| GList *poppler_page_get_link_mapping(PopplerPage *page) |
| { |
| GList *map_list = nullptr; |
| Links *links; |
| double width, height; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| links = new Links(page->page->getAnnots()); |
| |
| if (links == nullptr) { |
| return nullptr; |
| } |
| |
| poppler_page_get_size(page, &width, &height); |
| |
| for (AnnotLink *link : links->getLinks()) { |
| PopplerLinkMapping *mapping; |
| PopplerRectangle rect; |
| LinkAction *link_action; |
| |
| link_action = link->getAction(); |
| |
| /* Create the mapping */ |
| mapping = poppler_link_mapping_new(); |
| mapping->action = _poppler_action_new(page->document, link_action, nullptr); |
| |
| link->getRect(&rect.x1, &rect.y1, &rect.x2, &rect.y2); |
| |
| rect.x1 -= page->page->getCropBox()->x1; |
| rect.x2 -= page->page->getCropBox()->x1; |
| rect.y1 -= page->page->getCropBox()->y1; |
| rect.y2 -= page->page->getCropBox()->y1; |
| |
| switch (page->page->getRotate()) { |
| case 90: |
| mapping->area.x1 = rect.y1; |
| mapping->area.y1 = height - rect.x2; |
| mapping->area.x2 = mapping->area.x1 + (rect.y2 - rect.y1); |
| mapping->area.y2 = mapping->area.y1 + (rect.x2 - rect.x1); |
| |
| break; |
| case 180: |
| mapping->area.x1 = width - rect.x2; |
| mapping->area.y1 = height - rect.y2; |
| mapping->area.x2 = mapping->area.x1 + (rect.x2 - rect.x1); |
| mapping->area.y2 = mapping->area.y1 + (rect.y2 - rect.y1); |
| |
| break; |
| case 270: |
| mapping->area.x1 = width - rect.y2; |
| mapping->area.y1 = rect.x1; |
| mapping->area.x2 = mapping->area.x1 + (rect.y2 - rect.y1); |
| mapping->area.y2 = mapping->area.y1 + (rect.x2 - rect.x1); |
| |
| break; |
| default: |
| mapping->area.x1 = rect.x1; |
| mapping->area.y1 = rect.y1; |
| mapping->area.x2 = rect.x2; |
| mapping->area.y2 = rect.y2; |
| } |
| |
| map_list = g_list_prepend(map_list, mapping); |
| } |
| |
| delete links; |
| |
| return map_list; |
| } |
| |
| /** |
| * poppler_page_free_link_mapping: |
| * @list: (element-type PopplerLinkMapping): A list of |
| * #PopplerLinkMapping<!-- -->s |
| * |
| * Frees a list of #PopplerLinkMapping<!-- -->s allocated by |
| * poppler_page_get_link_mapping(). It also frees the #PopplerAction<!-- -->s |
| * that each mapping contains, so if you want to keep them around, you need to |
| * copy them with poppler_action_copy(). |
| **/ |
| void poppler_page_free_link_mapping(GList *list) |
| { |
| if (G_UNLIKELY(list == nullptr)) { |
| return; |
| } |
| |
| g_list_free_full(list, (GDestroyNotify)poppler_link_mapping_free); |
| } |
| |
| /** |
| * poppler_page_get_form_field_mapping: |
| * @page: A #PopplerPage |
| * |
| * Returns a list of #PopplerFormFieldMapping items that map from a |
| * location on @page to a form field. This list must be freed |
| * with poppler_page_free_form_field_mapping() when done. |
| * |
| * Return value: (element-type PopplerFormFieldMapping) (transfer full): A #GList of #PopplerFormFieldMapping |
| **/ |
| GList *poppler_page_get_form_field_mapping(PopplerPage *page) |
| { |
| GList *map_list = nullptr; |
| gint i; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| const std::unique_ptr<FormPageWidgets> forms = page->page->getFormWidgets(); |
| |
| if (forms == nullptr) { |
| return nullptr; |
| } |
| |
| for (i = 0; i < forms->getNumWidgets(); i++) { |
| PopplerFormFieldMapping *mapping; |
| FormWidget *field; |
| |
| mapping = poppler_form_field_mapping_new(); |
| |
| field = forms->getWidget(i); |
| |
| mapping->field = _poppler_form_field_new(page->document, field); |
| field->getRect(&(mapping->area.x1), &(mapping->area.y1), &(mapping->area.x2), &(mapping->area.y2)); |
| |
| mapping->area.x1 -= page->page->getCropBox()->x1; |
| mapping->area.x2 -= page->page->getCropBox()->x1; |
| mapping->area.y1 -= page->page->getCropBox()->y1; |
| mapping->area.y2 -= page->page->getCropBox()->y1; |
| |
| map_list = g_list_prepend(map_list, mapping); |
| } |
| |
| return map_list; |
| } |
| |
| /** |
| * poppler_page_free_form_field_mapping: |
| * @list: (element-type PopplerFormFieldMapping): A list of |
| * #PopplerFormFieldMapping<!-- -->s |
| * |
| * Frees a list of #PopplerFormFieldMapping<!-- -->s allocated by |
| * poppler_page_get_form_field_mapping(). |
| **/ |
| void poppler_page_free_form_field_mapping(GList *list) |
| { |
| if (G_UNLIKELY(list == nullptr)) { |
| return; |
| } |
| |
| g_list_free_full(list, (GDestroyNotify)poppler_form_field_mapping_free); |
| } |
| |
| /** |
| * poppler_page_get_annot_mapping: |
| * @page: A #PopplerPage |
| * |
| * Returns a list of #PopplerAnnotMapping items that map from a location on |
| * @page to a #PopplerAnnot. This list must be freed with |
| * poppler_page_free_annot_mapping() when done. |
| * |
| * Return value: (element-type PopplerAnnotMapping) (transfer full): A #GList of #PopplerAnnotMapping |
| **/ |
| GList *poppler_page_get_annot_mapping(PopplerPage *page) |
| { |
| GList *map_list = nullptr; |
| double width, height; |
| Annots *annots; |
| const PDFRectangle *crop_box; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| annots = page->page->getAnnots(); |
| if (!annots) { |
| return nullptr; |
| } |
| |
| poppler_page_get_size(page, &width, &height); |
| crop_box = page->page->getCropBox(); |
| |
| for (Annot *annot : annots->getAnnots()) { |
| PopplerAnnotMapping *mapping; |
| PopplerRectangle rect; |
| gboolean flag_no_rotate; |
| gint rotation = 0; |
| |
| flag_no_rotate = annot->getFlags() & Annot::flagNoRotate; |
| |
| /* Create the mapping */ |
| mapping = poppler_annot_mapping_new(); |
| |
| switch (annot->getType()) { |
| case Annot::typeText: |
| mapping->annot = _poppler_annot_text_new(annot); |
| break; |
| case Annot::typeFreeText: |
| mapping->annot = _poppler_annot_free_text_new(annot); |
| break; |
| case Annot::typeFileAttachment: |
| mapping->annot = _poppler_annot_file_attachment_new(annot); |
| break; |
| case Annot::typeMovie: |
| mapping->annot = _poppler_annot_movie_new(annot); |
| break; |
| case Annot::typeScreen: |
| mapping->annot = _poppler_annot_screen_new(page->document, annot); |
| break; |
| case Annot::typeLine: |
| mapping->annot = _poppler_annot_line_new(annot); |
| break; |
| case Annot::typeSquare: |
| mapping->annot = _poppler_annot_square_new(annot); |
| break; |
| case Annot::typeCircle: |
| mapping->annot = _poppler_annot_circle_new(annot); |
| break; |
| case Annot::typeHighlight: |
| case Annot::typeUnderline: |
| case Annot::typeSquiggly: |
| case Annot::typeStrikeOut: |
| mapping->annot = _poppler_annot_text_markup_new(annot); |
| break; |
| case Annot::typeStamp: |
| mapping->annot = _poppler_annot_stamp_new(annot); |
| break; |
| default: |
| mapping->annot = _poppler_annot_new(annot); |
| break; |
| } |
| |
| const PDFRectangle &annot_rect = annot->getRect(); |
| rect.x1 = annot_rect.x1 - crop_box->x1; |
| rect.y1 = annot_rect.y1 - crop_box->y1; |
| rect.x2 = annot_rect.x2 - crop_box->x1; |
| rect.y2 = annot_rect.y2 - crop_box->y1; |
| |
| rotation = page->page->getRotate(); |
| |
| if (rotation == 0 || !SUPPORTED_ROTATION(rotation)) { /* zero or unknown rotation */ |
| mapping->area.x1 = rect.x1; |
| mapping->area.y1 = rect.y1; |
| mapping->area.x2 = rect.x2; |
| mapping->area.y2 = rect.y2; |
| } else { |
| gdouble annot_height = rect.y2 - rect.y1; |
| gdouble annot_width = rect.x2 - rect.x1; |
| |
| if (flag_no_rotate) { |
| if (rotation == 90) { |
| mapping->area.x1 = rect.y2; |
| mapping->area.y1 = height - (rect.x1 + annot_height); |
| mapping->area.x2 = rect.y2 + annot_width; |
| mapping->area.y2 = height - rect.x1; |
| } else if (rotation == 180) { |
| mapping->area.x1 = width - rect.x1; |
| mapping->area.x2 = MIN(mapping->area.x1 + annot_width, width); |
| mapping->area.y2 = height - rect.y2; |
| mapping->area.y1 = MAX(0, mapping->area.y2 - annot_height); |
| } else if (rotation == 270) { |
| mapping->area.x1 = width - rect.y2; |
| mapping->area.x2 = MIN(mapping->area.x1 + annot_width, width); |
| mapping->area.y2 = rect.x1; |
| mapping->area.y1 = MAX(0, mapping->area.y2 - annot_height); |
| } |
| } else { /* !flag_no_rotate */ |
| if (rotation == 90) { |
| mapping->area.x1 = rect.y1; |
| mapping->area.y1 = height - rect.x2; |
| mapping->area.x2 = mapping->area.x1 + annot_height; |
| mapping->area.y2 = mapping->area.y1 + annot_width; |
| } else if (rotation == 180) { |
| mapping->area.x1 = width - rect.x2; |
| mapping->area.y1 = height - rect.y2; |
| mapping->area.x2 = mapping->area.x1 + annot_width; |
| mapping->area.y2 = mapping->area.y1 + annot_height; |
| } else if (rotation == 270) { |
| mapping->area.x1 = width - rect.y2; |
| mapping->area.y1 = rect.x1; |
| mapping->area.x2 = mapping->area.x1 + annot_height; |
| mapping->area.y2 = mapping->area.y1 + annot_width; |
| } |
| } |
| } |
| |
| map_list = g_list_prepend(map_list, mapping); |
| } |
| |
| return g_list_reverse(map_list); |
| } |
| |
| /** |
| * poppler_page_free_annot_mapping: |
| * @list: (element-type PopplerAnnotMapping): A list of |
| * #PopplerAnnotMapping<!-- -->s |
| * |
| * Frees a list of #PopplerAnnotMapping<!-- -->s allocated by |
| * poppler_page_get_annot_mapping(). It also unreferences the #PopplerAnnot<!-- -->s |
| * that each mapping contains, so if you want to keep them around, you need to |
| * reference them with g_object_ref(). |
| **/ |
| void poppler_page_free_annot_mapping(GList *list) |
| { |
| if (G_UNLIKELY(!list)) { |
| return; |
| } |
| |
| g_list_free_full(list, (GDestroyNotify)poppler_annot_mapping_free); |
| } |
| |
| /* Adds or removes (according to @add parameter) the passed in @crop_box from the |
| * passed in @quads and returns it as a new #AnnotQuadrilaterals object */ |
| AnnotQuadrilaterals *new_quads_from_offset_cropbox(const PDFRectangle *crop_box, AnnotQuadrilaterals *quads, gboolean add) |
| { |
| int len = quads->getQuadrilateralsLength(); |
| auto quads_array = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(len); |
| for (int i = 0; i < len; i++) { |
| if (add) { |
| quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral(quads->getX1(i) + crop_box->x1, quads->getY1(i) + crop_box->y1, quads->getX2(i) + crop_box->x1, quads->getY2(i) + crop_box->y1, quads->getX3(i) + crop_box->x1, |
| quads->getY3(i) + crop_box->y1, quads->getX4(i) + crop_box->x1, quads->getY4(i) + crop_box->y1); |
| } else { |
| quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral(quads->getX1(i) - crop_box->x1, quads->getY1(i) - crop_box->y1, quads->getX2(i) - crop_box->x1, quads->getY2(i) - crop_box->y1, quads->getX3(i) - crop_box->x1, |
| quads->getY3(i) - crop_box->y1, quads->getX4(i) - crop_box->x1, quads->getY4(i) - crop_box->y1); |
| } |
| } |
| |
| return new AnnotQuadrilaterals(std::move(quads_array), len); |
| } |
| |
| /* This function undoes the rotation of @page in the passed-in @x @y point. |
| * In other words, it moves the point to where it'll be located if @page |
| * was put to zero rotation (unrotated) */ |
| static void _page_unrotate_xy(Page *page, double *x, double *y) |
| { |
| double page_width, page_height, temp; |
| gint rotation = page->getRotate(); |
| |
| if (rotation == 90 || rotation == 270) { |
| page_height = page->getCropWidth(); |
| page_width = page->getCropHeight(); |
| } else { |
| page_width = page->getCropWidth(); |
| page_height = page->getCropHeight(); |
| } |
| |
| if (rotation == 90) { |
| temp = *x; |
| *x = page_height - *y; |
| *y = temp; |
| } else if (rotation == 180) { |
| *x = page_width - *x; |
| *y = page_height - *y; |
| } else if (rotation == 270) { |
| temp = *x; |
| *x = *y; |
| *y = page_width - temp; |
| } |
| } |
| |
| AnnotQuadrilaterals *_page_new_quads_unrotated(Page *page, AnnotQuadrilaterals *quads) |
| { |
| double x1, y1, x2, y2, x3, y3, x4, y4; |
| int len = quads->getQuadrilateralsLength(); |
| auto quads_array = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(len); |
| |
| for (int i = 0; i < len; i++) { |
| x1 = quads->getX1(i); |
| y1 = quads->getY1(i); |
| x2 = quads->getX2(i); |
| y2 = quads->getY2(i); |
| x3 = quads->getX3(i); |
| y3 = quads->getY3(i); |
| x4 = quads->getX4(i); |
| y4 = quads->getY4(i); |
| |
| _page_unrotate_xy(page, &x1, &y1); |
| _page_unrotate_xy(page, &x2, &y2); |
| _page_unrotate_xy(page, &x3, &y3); |
| _page_unrotate_xy(page, &x4, &y4); |
| |
| quads_array[i] = AnnotQuadrilaterals::AnnotQuadrilateral(x1, y1, x2, y2, x3, y3, x4, y4); |
| } |
| |
| return new AnnotQuadrilaterals(std::move(quads_array), len); |
| } |
| |
| /* @x1 @y1 @x2 @y2 are both 'in' and 'out' parameters, representing |
| * the diagonal of a rectangle which is the 'rect' area of @annot |
| * which is inside @page. |
| * |
| * If @page is unrotated (i.e. has zero rotation) this function does |
| * nothing, otherwise this function un-rotates the passed-in rect |
| * coords according to @page rotation so as the returned coords are |
| * those of the rect if page was put to zero rotation (unrotated). |
| * This is mandated by PDF spec when saving annotation coords (see |
| * 8.4.2 Annotation Flags) which also explains the special rotation |
| * that needs to be done when @annot has the flagNoRotate set to |
| * true, which this function follows. */ |
| void _unrotate_rect_for_annot_and_page(Page *page, Annot *annot, double *x1, double *y1, double *x2, double *y2) |
| { |
| gboolean flag_no_rotate; |
| |
| if (!SUPPORTED_ROTATION(page->getRotate())) { |
| return; |
| } |
| /* Normalize received rect diagonal to be from UpperLeft to BottomRight, |
| * as our algorithm follows that */ |
| if (*y2 > *y1) { |
| double temp = *y1; |
| *y1 = *y2; |
| *y2 = temp; |
| } |
| if (G_UNLIKELY(*x1 > *x2)) { |
| double temp = *x1; |
| *x1 = *x2; |
| *x2 = temp; |
| } |
| flag_no_rotate = annot->getFlags() & Annot::flagNoRotate; |
| if (flag_no_rotate) { |
| /* For this case rotating just the upperleft point is enough */ |
| double annot_height = *y1 - *y2; |
| double annot_width = *x2 - *x1; |
| _page_unrotate_xy(page, x1, y1); |
| *x2 = *x1 + annot_width; |
| *y2 = *y1 - annot_height; |
| } else { |
| _page_unrotate_xy(page, x1, y1); |
| _page_unrotate_xy(page, x2, y2); |
| } |
| } |
| |
| /** |
| * poppler_page_add_annot: |
| * @page: a #PopplerPage |
| * @annot: a #PopplerAnnot to add |
| * |
| * Adds annotation @annot to @page. |
| * |
| * Since: 0.16 |
| */ |
| void poppler_page_add_annot(PopplerPage *page, PopplerAnnot *annot) |
| { |
| double x1, y1, x2, y2; |
| gboolean page_is_rotated; |
| const PDFRectangle *crop_box; |
| const PDFRectangle *page_crop_box; |
| |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| g_return_if_fail(POPPLER_IS_ANNOT(annot)); |
| |
| /* Add the page's cropBox to the coordinates of rect field of annot */ |
| page_crop_box = page->page->getCropBox(); |
| annot->annot->getRect(&x1, &y1, &x2, &y2); |
| |
| page_is_rotated = SUPPORTED_ROTATION(page->page->getRotate()); |
| if (page_is_rotated) { |
| /* annot is inside a rotated page, as core poppler rect must be saved |
| * un-rotated, let's proceed to un-rotate rect before saving */ |
| _unrotate_rect_for_annot_and_page(page->page, annot->annot, &x1, &y1, &x2, &y2); |
| } |
| |
| annot->annot->setRect(x1 + page_crop_box->x1, y1 + page_crop_box->y1, x2 + page_crop_box->x1, y2 + page_crop_box->y1); |
| |
| AnnotTextMarkup *annot_markup = dynamic_cast<AnnotTextMarkup *>(annot->annot); |
| if (annot_markup) { |
| AnnotQuadrilaterals *quads; |
| crop_box = _poppler_annot_get_cropbox(annot); |
| if (crop_box) { |
| /* Handle hypothetical case of annot being added is already existing on a prior page, so |
| * first remove cropbox of the prior page before adding cropbox of the new page later */ |
| quads = new_quads_from_offset_cropbox(crop_box, annot_markup->getQuadrilaterals(), FALSE); |
| annot_markup->setQuadrilaterals(quads); |
| } |
| if (page_is_rotated) { |
| /* Quadrilateral's coords need to be saved un-rotated (same as rect coords) */ |
| quads = _page_new_quads_unrotated(page->page, annot_markup->getQuadrilaterals()); |
| annot_markup->setQuadrilaterals(quads); |
| } |
| /* Add to annot's quadrilaterals the offset for the cropbox of the new page */ |
| quads = new_quads_from_offset_cropbox(page_crop_box, annot_markup->getQuadrilaterals(), TRUE); |
| annot_markup->setQuadrilaterals(quads); |
| } |
| |
| page->page->addAnnot(annot->annot); |
| } |
| |
| /** |
| * poppler_page_remove_annot: |
| * @page: a #PopplerPage |
| * @annot: a #PopplerAnnot to remove |
| * |
| * Removes annotation @annot from @page |
| * |
| * Since: 0.22 |
| */ |
| void poppler_page_remove_annot(PopplerPage *page, PopplerAnnot *annot) |
| { |
| g_return_if_fail(POPPLER_IS_PAGE(page)); |
| g_return_if_fail(POPPLER_IS_ANNOT(annot)); |
| |
| page->page->removeAnnot(annot->annot); |
| } |
| |
| /* PopplerRectangle type */ |
| |
| G_DEFINE_BOXED_TYPE(PopplerRectangle, poppler_rectangle, poppler_rectangle_copy, poppler_rectangle_free) |
| |
| static PopplerRectangleExtended *poppler_rectangle_extended_new() |
| { |
| return g_slice_new0(PopplerRectangleExtended); |
| } |
| |
| PopplerRectangle *poppler_rectangle_new_from_pdf_rectangle(const PDFRectangle *rect) |
| { |
| auto r = poppler_rectangle_extended_new(); |
| r->x1 = rect->x1; |
| r->y1 = rect->y1; |
| r->x2 = rect->x2; |
| r->y2 = rect->y2; |
| |
| return reinterpret_cast<PopplerRectangle *>(r); |
| } |
| |
| /** |
| * poppler_rectangle_new: |
| * |
| * Creates a new #PopplerRectangle |
| * |
| * Returns: a new #PopplerRectangle, use poppler_rectangle_free() to free it |
| */ |
| PopplerRectangle *poppler_rectangle_new(void) |
| { |
| return reinterpret_cast<PopplerRectangle *>(poppler_rectangle_extended_new()); |
| } |
| |
| /** |
| * poppler_rectangle_copy: |
| * @rectangle: a #PopplerRectangle to copy |
| * |
| * Creates a copy of @rectangle. |
| * |
| * Note that you must only use this function on an allocated PopplerRectangle, as |
| * returned by poppler_rectangle_new(), poppler_rectangle_copy(), or the list elements |
| * returned from poppler_page_find_text() or poppler_page_find_text_with_options(). |
| * Returns: a new allocated copy of @rectangle |
| */ |
| PopplerRectangle *poppler_rectangle_copy(PopplerRectangle *rectangle) |
| { |
| g_return_val_if_fail(rectangle != nullptr, NULL); |
| |
| auto ext_rectangle = reinterpret_cast<PopplerRectangleExtended *>(rectangle); |
| return reinterpret_cast<PopplerRectangle *>(g_slice_dup(PopplerRectangleExtended, ext_rectangle)); |
| } |
| |
| /** |
| * poppler_rectangle_free: |
| * @rectangle: a #PopplerRectangle |
| * |
| * Frees the given #PopplerRectangle. |
| * |
| * Note that you must only use this function on an allocated PopplerRectangle, as |
| * returned by poppler_rectangle_new(), poppler_rectangle_copy(), or the list elements |
| * returned from poppler_page_find_text() or poppler_page_find_text_with_options(). |
| */ |
| void poppler_rectangle_free(PopplerRectangle *rectangle) |
| { |
| auto ext_rectangle = reinterpret_cast<PopplerRectangleExtended *>(rectangle); |
| g_slice_free(PopplerRectangleExtended, ext_rectangle); |
| } |
| |
| /** |
| * poppler_rectangle_find_get_match_continued: |
| * @rectangle: a #PopplerRectangle |
| * |
| * When using poppler_page_find_text_with_options() with the |
| * %POPPLER_FIND_MULTILINE flag, a match may span more than one line |
| * and thus consist of more than one rectangle. Every rectangle belonging |
| * to the same match will return %TRUE from this function, except for |
| * the last rectangle, where this function will return %FALSE. |
| * |
| * Note that you must only call this function on a #PopplerRectangle |
| * returned in the list from poppler_page_find_text() or |
| * poppler_page_find_text_with_options(). |
| * |
| * Returns: whether there are more rectangles belonging to the same match |
| * |
| * Since: 21.05.0 |
| */ |
| gboolean poppler_rectangle_find_get_match_continued(const PopplerRectangle *rectangle) |
| { |
| g_return_val_if_fail(rectangle != nullptr, false); |
| |
| auto ext_rectangle = reinterpret_cast<const PopplerRectangleExtended *>(rectangle); |
| return ext_rectangle->match_continued; |
| } |
| |
| /** |
| * poppler_rectangle_find_get_ignored_hyphen: |
| * @rectangle: a #PopplerRectangle |
| * |
| * When using poppler_page_find_text_with_options() with the |
| * %POPPLER_FIND_MULTILINE flag, a match may span more than one line, |
| * and may have been formed by ignoring a hyphen at the end of the line. |
| * When this happens at the end of the line corresponding to @rectangle, |
| * this function returns %TRUE (and then poppler_rectangle_find_get_match_continued() |
| * will also return %TRUE); otherwise it returns %FALSE. |
| * |
| * Note that you must only call this function on a #PopplerRectangle |
| * returned in the list from poppler_page_find_text() or |
| * poppler_page_find_text_with_options(). |
| * |
| * Returns: whether a hyphen was ignored at the end of the line corresponding to @rectangle. |
| * |
| * Since: 21.05.0 |
| */ |
| gboolean poppler_rectangle_find_get_ignored_hyphen(const PopplerRectangle *rectangle) |
| { |
| g_return_val_if_fail(rectangle != nullptr, false); |
| |
| auto ext_rectangle = reinterpret_cast<const PopplerRectangleExtended *>(rectangle); |
| return ext_rectangle->ignored_hyphen; |
| } |
| |
| G_DEFINE_BOXED_TYPE(PopplerPoint, poppler_point, poppler_point_copy, poppler_point_free) |
| |
| /** |
| * poppler_point_new: |
| * |
| * Creates a new #PopplerPoint. It must be freed with poppler_point_free() after use. |
| * |
| * Returns: a new #PopplerPoint |
| * |
| * Since: 0.26 |
| **/ |
| PopplerPoint *poppler_point_new(void) |
| { |
| return g_slice_new0(PopplerPoint); |
| } |
| |
| /** |
| * poppler_point_copy: |
| * @point: a #PopplerPoint to copy |
| * |
| * Creates a copy of @point. The copy must be freed with poppler_point_free() |
| * after use. |
| * |
| * Returns: a new allocated copy of @point |
| * |
| * Since: 0.26 |
| **/ |
| PopplerPoint *poppler_point_copy(PopplerPoint *point) |
| { |
| g_return_val_if_fail(point != nullptr, NULL); |
| |
| return g_slice_dup(PopplerPoint, point); |
| } |
| |
| /** |
| * poppler_point_free: |
| * @point: a #PopplerPoint |
| * |
| * Frees the memory used by @point |
| * |
| * Since: 0.26 |
| **/ |
| void poppler_point_free(PopplerPoint *point) |
| { |
| g_slice_free(PopplerPoint, point); |
| } |
| |
| /* PopplerQuadrilateral type */ |
| |
| G_DEFINE_BOXED_TYPE(PopplerQuadrilateral, poppler_quadrilateral, poppler_quadrilateral_copy, poppler_quadrilateral_free) |
| |
| /** |
| * poppler_quadrilateral_new: |
| * |
| * Creates a new #PopplerQuadrilateral. It must be freed with poppler_quadrilateral_free() after use. |
| * |
| * Returns: a new #PopplerQuadrilateral. |
| * |
| * Since: 0.26 |
| **/ |
| PopplerQuadrilateral *poppler_quadrilateral_new(void) |
| { |
| return g_slice_new0(PopplerQuadrilateral); |
| } |
| |
| /** |
| * poppler_quadrilateral_copy: |
| * @quad: a #PopplerQuadrilateral to copy |
| * |
| * Creates a copy of @quad. The copy must be freed with poppler_quadrilateral_free() after use. |
| * |
| * Returns: a new allocated copy of @quad |
| * |
| * Since: 0.26 |
| **/ |
| PopplerQuadrilateral *poppler_quadrilateral_copy(PopplerQuadrilateral *quad) |
| { |
| g_return_val_if_fail(quad != nullptr, NULL); |
| |
| return g_slice_dup(PopplerQuadrilateral, quad); |
| } |
| |
| /** |
| * poppler_quadrilateral_free: |
| * @quad: a #PopplerQuadrilateral |
| * |
| * Frees the memory used by @quad |
| * |
| * Since: 0.26 |
| **/ |
| void poppler_quadrilateral_free(PopplerQuadrilateral *quad) |
| { |
| g_slice_free(PopplerQuadrilateral, quad); |
| } |
| |
| /* PopplerTextAttributes type */ |
| |
| G_DEFINE_BOXED_TYPE(PopplerTextAttributes, poppler_text_attributes, poppler_text_attributes_copy, poppler_text_attributes_free) |
| |
| /** |
| * poppler_text_attributes_new: |
| * |
| * Creates a new #PopplerTextAttributes |
| * |
| * Returns: a new #PopplerTextAttributes, use poppler_text_attributes_free() to free it |
| * |
| * Since: 0.18 |
| */ |
| PopplerTextAttributes *poppler_text_attributes_new(void) |
| { |
| return (PopplerTextAttributes *)g_slice_new0(PopplerTextAttributes); |
| } |
| |
| static gchar *get_font_name_from_word(const TextWord *word, gint word_i) |
| { |
| const GooString *font_name = word->getFontName(word_i); |
| const gchar *name; |
| gboolean subset; |
| gint i; |
| |
| if (!font_name || font_name->getLength() == 0) { |
| return g_strdup("Default"); |
| } |
| |
| // check for a font subset name: capital letters followed by a '+' sign |
| for (i = 0; i < font_name->getLength(); ++i) { |
| if (font_name->getChar(i) < 'A' || font_name->getChar(i) > 'Z') { |
| break; |
| } |
| } |
| subset = i > 0 && i < font_name->getLength() && font_name->getChar(i) == '+'; |
| name = font_name->c_str(); |
| if (subset) { |
| name += i + 1; |
| } |
| |
| return g_strdup(name); |
| } |
| |
| /* |
| * Allocates a new PopplerTextAttributes with word attributes |
| */ |
| static PopplerTextAttributes *poppler_text_attributes_new_from_word(const TextWord *word, gint i) |
| { |
| PopplerTextAttributes *attrs = poppler_text_attributes_new(); |
| gdouble r, g, b; |
| |
| attrs->font_name = get_font_name_from_word(word, i); |
| attrs->font_size = word->getFontSize(); |
| attrs->is_underlined = word->isUnderlined(); |
| word->getColor(&r, &g, &b); |
| attrs->color.red = (int)(r * 65535. + 0.5); |
| attrs->color.green = (int)(g * 65535. + 0.5); |
| attrs->color.blue = (int)(b * 65535. + 0.5); |
| |
| return attrs; |
| } |
| |
| /** |
| * poppler_text_attributes_copy: |
| * @text_attrs: a #PopplerTextAttributes to copy |
| * |
| * Creates a copy of @text_attrs |
| * |
| * Returns: a new allocated copy of @text_attrs |
| * |
| * Since: 0.18 |
| */ |
| PopplerTextAttributes *poppler_text_attributes_copy(PopplerTextAttributes *text_attrs) |
| { |
| PopplerTextAttributes *attrs; |
| |
| attrs = g_slice_dup(PopplerTextAttributes, text_attrs); |
| attrs->font_name = g_strdup(text_attrs->font_name); |
| return attrs; |
| } |
| |
| /** |
| * poppler_text_attributes_free: |
| * @text_attrs: a #PopplerTextAttributes |
| * |
| * Frees the given #PopplerTextAttributes |
| * |
| * Since: 0.18 |
| */ |
| void poppler_text_attributes_free(PopplerTextAttributes *text_attrs) |
| { |
| g_free(text_attrs->font_name); |
| g_slice_free(PopplerTextAttributes, text_attrs); |
| } |
| |
| /** |
| * SECTION:poppler-color |
| * @short_description: Colors |
| * @title: PopplerColor |
| */ |
| |
| /* PopplerColor type */ |
| G_DEFINE_BOXED_TYPE(PopplerColor, poppler_color, poppler_color_copy, poppler_color_free) |
| |
| /** |
| * poppler_color_new: |
| * |
| * Creates a new #PopplerColor |
| * |
| * Returns: a new #PopplerColor, use poppler_color_free() to free it |
| */ |
| PopplerColor *poppler_color_new(void) |
| { |
| return (PopplerColor *)g_new0(PopplerColor, 1); |
| } |
| |
| /** |
| * poppler_color_copy: |
| * @color: a #PopplerColor to copy |
| * |
| * Creates a copy of @color |
| * |
| * Returns: a new allocated copy of @color |
| */ |
| PopplerColor *poppler_color_copy(PopplerColor *color) |
| { |
| PopplerColor *new_color; |
| |
| new_color = g_new(PopplerColor, 1); |
| *new_color = *color; |
| |
| return new_color; |
| } |
| |
| /** |
| * poppler_color_free: |
| * @color: a #PopplerColor |
| * |
| * Frees the given #PopplerColor |
| */ |
| void poppler_color_free(PopplerColor *color) |
| { |
| g_free(color); |
| } |
| |
| /* PopplerLinkMapping type */ |
| G_DEFINE_BOXED_TYPE(PopplerLinkMapping, poppler_link_mapping, poppler_link_mapping_copy, poppler_link_mapping_free) |
| |
| /** |
| * poppler_link_mapping_new: |
| * |
| * Creates a new #PopplerLinkMapping |
| * |
| * Returns: a new #PopplerLinkMapping, use poppler_link_mapping_free() to free it |
| */ |
| PopplerLinkMapping *poppler_link_mapping_new(void) |
| { |
| return g_slice_new0(PopplerLinkMapping); |
| } |
| |
| /** |
| * poppler_link_mapping_copy: |
| * @mapping: a #PopplerLinkMapping to copy |
| * |
| * Creates a copy of @mapping |
| * |
| * Returns: a new allocated copy of @mapping |
| */ |
| PopplerLinkMapping *poppler_link_mapping_copy(PopplerLinkMapping *mapping) |
| { |
| PopplerLinkMapping *new_mapping; |
| |
| new_mapping = g_slice_dup(PopplerLinkMapping, mapping); |
| |
| if (new_mapping->action) { |
| new_mapping->action = poppler_action_copy(new_mapping->action); |
| } |
| |
| return new_mapping; |
| } |
| |
| /** |
| * poppler_link_mapping_free: |
| * @mapping: a #PopplerLinkMapping |
| * |
| * Frees the given #PopplerLinkMapping |
| */ |
| void poppler_link_mapping_free(PopplerLinkMapping *mapping) |
| { |
| if (G_UNLIKELY(!mapping)) { |
| return; |
| } |
| |
| if (mapping->action) { |
| poppler_action_free(mapping->action); |
| } |
| |
| g_slice_free(PopplerLinkMapping, mapping); |
| } |
| |
| /* Poppler Image mapping type */ |
| G_DEFINE_BOXED_TYPE(PopplerImageMapping, poppler_image_mapping, poppler_image_mapping_copy, poppler_image_mapping_free) |
| |
| /** |
| * poppler_image_mapping_new: |
| * |
| * Creates a new #PopplerImageMapping |
| * |
| * Returns: a new #PopplerImageMapping, use poppler_image_mapping_free() to free it |
| */ |
| PopplerImageMapping *poppler_image_mapping_new(void) |
| { |
| return g_slice_new0(PopplerImageMapping); |
| } |
| |
| /** |
| * poppler_image_mapping_copy: |
| * @mapping: a #PopplerImageMapping to copy |
| * |
| * Creates a copy of @mapping |
| * |
| * Returns: a new allocated copy of @mapping |
| */ |
| PopplerImageMapping *poppler_image_mapping_copy(PopplerImageMapping *mapping) |
| { |
| return g_slice_dup(PopplerImageMapping, mapping); |
| } |
| |
| /** |
| * poppler_image_mapping_free: |
| * @mapping: a #PopplerImageMapping |
| * |
| * Frees the given #PopplerImageMapping |
| */ |
| void poppler_image_mapping_free(PopplerImageMapping *mapping) |
| { |
| g_slice_free(PopplerImageMapping, mapping); |
| } |
| |
| /* Page Transition */ |
| G_DEFINE_BOXED_TYPE(PopplerPageTransition, poppler_page_transition, poppler_page_transition_copy, poppler_page_transition_free) |
| |
| /** |
| * poppler_page_transition_new: |
| * |
| * Creates a new #PopplerPageTransition |
| * |
| * Returns: a new #PopplerPageTransition, use poppler_page_transition_free() to free it |
| */ |
| PopplerPageTransition *poppler_page_transition_new(void) |
| { |
| return (PopplerPageTransition *)g_new0(PopplerPageTransition, 1); |
| } |
| |
| /** |
| * poppler_page_transition_copy: |
| * @transition: a #PopplerPageTransition to copy |
| * |
| * Creates a copy of @transition |
| * |
| * Returns: a new allocated copy of @transition |
| */ |
| PopplerPageTransition *poppler_page_transition_copy(PopplerPageTransition *transition) |
| { |
| PopplerPageTransition *new_transition; |
| |
| new_transition = poppler_page_transition_new(); |
| *new_transition = *transition; |
| |
| return new_transition; |
| } |
| |
| /** |
| * poppler_page_transition_free: |
| * @transition: a #PopplerPageTransition |
| * |
| * Frees the given #PopplerPageTransition |
| */ |
| void poppler_page_transition_free(PopplerPageTransition *transition) |
| { |
| g_free(transition); |
| } |
| |
| /* Form Field Mapping Type */ |
| G_DEFINE_BOXED_TYPE(PopplerFormFieldMapping, poppler_form_field_mapping, poppler_form_field_mapping_copy, poppler_form_field_mapping_free) |
| |
| /** |
| * poppler_form_field_mapping_new: |
| * |
| * Creates a new #PopplerFormFieldMapping |
| * |
| * Returns: a new #PopplerFormFieldMapping, use poppler_form_field_mapping_free() to free it |
| */ |
| PopplerFormFieldMapping *poppler_form_field_mapping_new(void) |
| { |
| return g_slice_new0(PopplerFormFieldMapping); |
| } |
| |
| /** |
| * poppler_form_field_mapping_copy: |
| * @mapping: a #PopplerFormFieldMapping to copy |
| * |
| * Creates a copy of @mapping |
| * |
| * Returns: a new allocated copy of @mapping |
| */ |
| PopplerFormFieldMapping *poppler_form_field_mapping_copy(PopplerFormFieldMapping *mapping) |
| { |
| PopplerFormFieldMapping *new_mapping; |
| |
| new_mapping = g_slice_dup(PopplerFormFieldMapping, mapping); |
| |
| if (mapping->field) { |
| new_mapping->field = (PopplerFormField *)g_object_ref(mapping->field); |
| } |
| |
| return new_mapping; |
| } |
| |
| /** |
| * poppler_form_field_mapping_free: |
| * @mapping: a #PopplerFormFieldMapping |
| * |
| * Frees the given #PopplerFormFieldMapping |
| */ |
| void poppler_form_field_mapping_free(PopplerFormFieldMapping *mapping) |
| { |
| if (G_UNLIKELY(!mapping)) { |
| return; |
| } |
| |
| if (mapping->field) { |
| g_object_unref(mapping->field); |
| } |
| |
| g_slice_free(PopplerFormFieldMapping, mapping); |
| } |
| |
| /* PopplerAnnot Mapping Type */ |
| G_DEFINE_BOXED_TYPE(PopplerAnnotMapping, poppler_annot_mapping, poppler_annot_mapping_copy, poppler_annot_mapping_free) |
| |
| /** |
| * poppler_annot_mapping_new: |
| * |
| * Creates a new #PopplerAnnotMapping |
| * |
| * Returns: a new #PopplerAnnotMapping, use poppler_annot_mapping_free() to free it |
| */ |
| PopplerAnnotMapping *poppler_annot_mapping_new(void) |
| { |
| return g_slice_new0(PopplerAnnotMapping); |
| } |
| |
| /** |
| * poppler_annot_mapping_copy: |
| * @mapping: a #PopplerAnnotMapping to copy |
| * |
| * Creates a copy of @mapping |
| * |
| * Returns: a new allocated copy of @mapping |
| */ |
| PopplerAnnotMapping *poppler_annot_mapping_copy(PopplerAnnotMapping *mapping) |
| { |
| PopplerAnnotMapping *new_mapping; |
| |
| new_mapping = g_slice_dup(PopplerAnnotMapping, mapping); |
| |
| if (mapping->annot) { |
| new_mapping->annot = (PopplerAnnot *)g_object_ref(mapping->annot); |
| } |
| |
| return new_mapping; |
| } |
| |
| /** |
| * poppler_annot_mapping_free: |
| * @mapping: a #PopplerAnnotMapping |
| * |
| * Frees the given #PopplerAnnotMapping |
| */ |
| void poppler_annot_mapping_free(PopplerAnnotMapping *mapping) |
| { |
| if (G_UNLIKELY(!mapping)) { |
| return; |
| } |
| |
| if (mapping->annot) { |
| g_object_unref(mapping->annot); |
| } |
| |
| g_slice_free(PopplerAnnotMapping, mapping); |
| } |
| |
| /** |
| * poppler_page_get_crop_box: |
| * @page: a #PopplerPage |
| * @rect: (out): a #PopplerRectangle to fill |
| * |
| * Retrurns the crop box of @page |
| */ |
| void poppler_page_get_crop_box(PopplerPage *page, PopplerRectangle *rect) |
| { |
| const PDFRectangle *cropBox = page->page->getCropBox(); |
| |
| rect->x1 = cropBox->x1; |
| rect->x2 = cropBox->x2; |
| rect->y1 = cropBox->y1; |
| rect->y2 = cropBox->y2; |
| } |
| |
| /* |
| * poppler_page_get_bounding_box: |
| * @page: A #PopplerPage |
| * @rect: (out) return the bounding box of the page |
| * |
| * Returns the bounding box of the page, a rectangle enclosing all text, vector |
| * graphics (lines, rectangles and curves) and raster images in the page. |
| * Includes invisible text but not (yet) annotations like highlights and form |
| * elements. |
| * |
| * Return value: %TRUE if the page contains graphics, %FALSE otherwise |
| * |
| * Since: 0.88 |
| */ |
| gboolean poppler_page_get_bounding_box(PopplerPage *page, PopplerRectangle *rect) |
| { |
| BBoxOutputDev *bb_out; |
| bool hasGraphics; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), false); |
| g_return_val_if_fail(rect != nullptr, false); |
| |
| bb_out = new BBoxOutputDev(); |
| |
| page->page->displaySlice(bb_out, 72.0, 72.0, 0, false, /* useMediaBox */ |
| true, /* Crop */ |
| -1, -1, -1, -1, false, /* printing */ |
| nullptr, nullptr, nullptr, nullptr); |
| hasGraphics = bb_out->getHasGraphics(); |
| if (hasGraphics) { |
| rect->x1 = bb_out->getX1(); |
| rect->y1 = bb_out->getY1(); |
| rect->x2 = bb_out->getX2(); |
| rect->y2 = bb_out->getY2(); |
| } |
| |
| delete bb_out; |
| return hasGraphics; |
| } |
| |
| /** |
| * poppler_page_get_text_layout: |
| * @page: A #PopplerPage |
| * @rectangles: (out) (array length=n_rectangles) (transfer container): return location for an array of #PopplerRectangle |
| * @n_rectangles: (out): length of returned array |
| * |
| * Obtains the layout of the text as a list of #PopplerRectangle |
| * This array must be freed with g_free() when done. |
| * |
| * The position in the array represents an offset in the text returned by |
| * poppler_page_get_text() |
| * |
| * See also poppler_page_get_text_layout_for_area(). |
| * |
| * Return value: %TRUE if the page contains text, %FALSE otherwise |
| * |
| * Since: 0.16 |
| **/ |
| gboolean poppler_page_get_text_layout(PopplerPage *page, PopplerRectangle **rectangles, guint *n_rectangles) |
| { |
| PopplerRectangle selection = { 0, 0, 0, 0 }; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), FALSE); |
| |
| poppler_page_get_size(page, &selection.x2, &selection.y2); |
| |
| return poppler_page_get_text_layout_for_area(page, &selection, rectangles, n_rectangles); |
| } |
| |
| /** |
| * poppler_page_get_text_layout_for_area: |
| * @page: A #PopplerPage |
| * @area: a #PopplerRectangle |
| * @rectangles: (out) (array length=n_rectangles) (transfer container): return location for an array of #PopplerRectangle |
| * @n_rectangles: (out): length of returned array |
| * |
| * Obtains the layout of the text contained in @area as a list of #PopplerRectangle |
| * This array must be freed with g_free() when done. |
| * |
| * The position in the array represents an offset in the text returned by |
| * poppler_page_get_text_for_area() |
| * |
| * Return value: %TRUE if the page contains text, %FALSE otherwise |
| * |
| * Since: 0.26 |
| **/ |
| gboolean poppler_page_get_text_layout_for_area(PopplerPage *page, PopplerRectangle *area, PopplerRectangle **rectangles, guint *n_rectangles) |
| { |
| TextPage *text; |
| PopplerRectangle *rect; |
| PDFRectangle selection; |
| int i, k; |
| guint offset = 0; |
| guint n_rects = 0; |
| gdouble x1, y1, x2, y2; |
| gdouble x3, y3, x4, y4; |
| int n_lines; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), FALSE); |
| g_return_val_if_fail(area != nullptr, FALSE); |
| |
| *n_rectangles = 0; |
| |
| selection.x1 = area->x1; |
| selection.y1 = area->y1; |
| selection.x2 = area->x2; |
| selection.y2 = area->y2; |
| |
| text = poppler_page_get_text_page(page); |
| std::vector<TextWordSelection *> **word_list = text->getSelectionWords(&selection, selectionStyleGlyph, &n_lines); |
| if (!word_list) { |
| return FALSE; |
| } |
| |
| n_rects += n_lines - 1; |
| for (i = 0; i < n_lines; i++) { |
| std::vector<TextWordSelection *> *line_words = word_list[i]; |
| n_rects += line_words->size() - 1; |
| for (std::size_t j = 0; j < line_words->size(); j++) { |
| const TextWordSelection *word_sel = (*line_words)[j]; |
| n_rects += word_sel->getEnd() - word_sel->getBegin(); |
| if (!word_sel->getWord()->hasSpaceAfter() && j < line_words->size() - 1) { |
| n_rects--; |
| } |
| } |
| } |
| |
| *rectangles = g_new(PopplerRectangle, n_rects); |
| *n_rectangles = n_rects; |
| |
| for (i = 0; i < n_lines; i++) { |
| std::vector<TextWordSelection *> *line_words = word_list[i]; |
| for (std::size_t j = 0; j < line_words->size(); j++) { |
| TextWordSelection *word_sel = (*line_words)[j]; |
| const TextWord *word = word_sel->getWord(); |
| int end = word_sel->getEnd(); |
| |
| for (k = word_sel->getBegin(); k < end; k++) { |
| rect = *rectangles + offset; |
| word->getCharBBox(k, &(rect->x1), &(rect->y1), &(rect->x2), &(rect->y2)); |
| offset++; |
| } |
| |
| rect = *rectangles + offset; |
| word->getBBox(&x1, &y1, &x2, &y2); |
| |
| if (word->hasSpaceAfter() && j < line_words->size() - 1) { |
| TextWordSelection *next_word_sel = (*line_words)[j + 1]; |
| |
| next_word_sel->getWord()->getBBox(&x3, &y3, &x4, &y4); |
| // space is from one word to other and with the same height as |
| // first word. |
| rect->x1 = x2; |
| rect->y1 = y1; |
| rect->x2 = x3; |
| rect->y2 = y2; |
| offset++; |
| } |
| |
| delete word_sel; |
| } |
| |
| if (i < n_lines - 1 && offset > 0) { |
| // end of line |
| rect->x1 = x2; |
| rect->y1 = y2; |
| rect->x2 = x2; |
| rect->y2 = y2; |
| offset++; |
| } |
| |
| delete line_words; |
| } |
| |
| gfree(word_list); |
| |
| return TRUE; |
| } |
| |
| /** |
| * poppler_page_free_text_attributes: |
| * @list: (element-type PopplerTextAttributes): A list of |
| * #PopplerTextAttributes<!-- -->s |
| * |
| * Frees a list of #PopplerTextAttributes<!-- -->s allocated by |
| * poppler_page_get_text_attributes(). |
| * |
| * Since: 0.18 |
| **/ |
| void poppler_page_free_text_attributes(GList *list) |
| { |
| if (G_UNLIKELY(list == nullptr)) { |
| return; |
| } |
| |
| g_list_free_full(list, (GDestroyNotify)poppler_text_attributes_free); |
| } |
| |
| static gboolean word_text_attributes_equal(const TextWord *a, gint ai, const TextWord *b, gint bi) |
| { |
| double ar, ag, ab, br, bg, bb; |
| |
| if (!a->getFontInfo(ai)->matches(b->getFontInfo(bi))) { |
| return FALSE; |
| } |
| |
| if (a->getFontSize() != b->getFontSize()) { |
| return FALSE; |
| } |
| |
| if (a->isUnderlined() != b->isUnderlined()) { |
| return FALSE; |
| } |
| |
| a->getColor(&ar, &ag, &ab); |
| b->getColor(&br, &bg, &bb); |
| return (ar == br && ag == bg && ab == bb); |
| } |
| |
| /** |
| * poppler_page_get_text_attributes: |
| * @page: A #PopplerPage |
| * |
| * Obtains the attributes of the text as a #GList of #PopplerTextAttributes. |
| * This list must be freed with poppler_page_free_text_attributes() when done. |
| * |
| * Each list element is a #PopplerTextAttributes struct where start_index and |
| * end_index indicates the range of text (as returned by poppler_page_get_text()) |
| * to which text attributes apply. |
| * |
| * See also poppler_page_get_text_attributes_for_area() |
| * |
| * Return value: (element-type PopplerTextAttributes) (transfer full): A #GList of #PopplerTextAttributes |
| * |
| * Since: 0.18 |
| **/ |
| GList *poppler_page_get_text_attributes(PopplerPage *page) |
| { |
| PopplerRectangle selection = { 0, 0, 0, 0 }; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| |
| poppler_page_get_size(page, &selection.x2, &selection.y2); |
| |
| return poppler_page_get_text_attributes_for_area(page, &selection); |
| } |
| |
| /** |
| * poppler_page_get_text_attributes_for_area: |
| * @page: A #PopplerPage |
| * @area: a #PopplerRectangle |
| * |
| * Obtains the attributes of the text in @area as a #GList of #PopplerTextAttributes. |
| * This list must be freed with poppler_page_free_text_attributes() when done. |
| * |
| * Each list element is a #PopplerTextAttributes struct where start_index and |
| * end_index indicates the range of text (as returned by poppler_page_get_text_for_area()) |
| * to which text attributes apply. |
| * |
| * Return value: (element-type PopplerTextAttributes) (transfer full): A #GList of #PopplerTextAttributes |
| * |
| * Since: 0.26 |
| **/ |
| GList *poppler_page_get_text_attributes_for_area(PopplerPage *page, PopplerRectangle *area) |
| { |
| TextPage *text; |
| PDFRectangle selection; |
| int n_lines; |
| PopplerTextAttributes *attrs = nullptr; |
| const TextWord *word, *prev_word = nullptr; |
| gint word_i, prev_word_i; |
| gint i; |
| gint offset = 0; |
| GList *attributes = nullptr; |
| |
| g_return_val_if_fail(POPPLER_IS_PAGE(page), NULL); |
| g_return_val_if_fail(area != nullptr, nullptr); |
| |
| selection.x1 = area->x1; |
| selection.y1 = area->y1; |
| selection.x2 = area->x2; |
| selection.y2 = area->y2; |
| |
| text = poppler_page_get_text_page(page); |
| std::vector<TextWordSelection *> **word_list = text->getSelectionWords(&selection, selectionStyleGlyph, &n_lines); |
| if (!word_list) { |
| return nullptr; |
| } |
| |
| for (i = 0; i < n_lines; i++) { |
| std::vector<TextWordSelection *> *line_words = word_list[i]; |
| for (std::size_t j = 0; j < line_words->size(); j++) { |
| TextWordSelection *word_sel = (*line_words)[j]; |
| int end = word_sel->getEnd(); |
| |
| word = word_sel->getWord(); |
| |
| for (word_i = word_sel->getBegin(); word_i < end; word_i++) { |
| if (!prev_word || !word_text_attributes_equal(word, word_i, prev_word, prev_word_i)) { |
| attrs = poppler_text_attributes_new_from_word(word, word_i); |
| attrs->start_index = offset; |
| attributes = g_list_prepend(attributes, attrs); |
| } |
| attrs->end_index = offset; |
| offset++; |
| prev_word = word; |
| prev_word_i = word_i; |
| } |
| |
| if (word->hasSpaceAfter() && j < line_words->size() - 1) { |
| attrs->end_index = offset; |
| offset++; |
| } |
| |
| delete word_sel; |
| } |
| |
| if (i < n_lines - 1) { |
| attrs->end_index = offset; |
| offset++; |
| } |
| |
| delete line_words; |
| } |
| |
| gfree(word_list); |
| |
| return g_list_reverse(attributes); |
| } |