/* Copyright (C) 2013, 2014 Andreas Politz * * Author: Andreas Politz * * 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 3 of the License, 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, see . */ #include #include #ifdef HAVE_ERR_H # include #endif #ifdef HAVE_ERROR_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "synctex_parser.h" #include "epdfinfo.h" /* ================================================================== * * Helper Functions * ================================================================== */ #ifndef HAVE_ERR_H /** * Print error message and quit. * * @param eval Return code * @param fmt Formatting string */ static void err(int eval, const char *fmt, ...) { va_list args; fprintf (stderr, "epdfinfo: "); if (fmt != NULL) { va_start (args, fmt); vfprintf (stderr, fmt, args); va_end (args); fprintf (stderr, ": %s\n", strerror(errno)); } else { fprintf (stderr, "\n"); } fflush (stderr); exit (eval); } #endif #ifndef HAVE_GETLINE /** * Read one line from a file. * * @param lineptr Pointer to malloc() allocated buffer * @param n Pointer to size of buffer * @param stream File pointer to read from */ static ssize_t getline(char **lineptr, size_t *n, FILE *stream) { size_t len = 0; int ch; if ((lineptr == NULL) || (n == NULL)) { errno = EINVAL; return -1; } if (*lineptr == NULL) { *lineptr = malloc (128); *n = 128; } while ((ch = fgetc (stream)) != EOF) { (*lineptr)[len] = ch; if (++len >= *n) { *n += 128; *lineptr = realloc (*lineptr, *n); } if (ch == '\n') break; } (*lineptr)[len] = '\0'; if (!len) { len = -1; } return len; } #endif /** * Free a list of command arguments. * * @param args An array of command arguments. * @param n The length of the array. */ static void free_command_args (command_arg_t *args, size_t n) { if (! args) return; g_free (args); } /** * Free resources held by document. * * @param doc The document to be freed. */ static void free_document (document_t *doc) { if (! doc) return; g_free (doc->filename); g_free (doc->passwd); if (doc->annotations.pages) { int npages = poppler_document_get_n_pages (doc->pdf); int i; for (i = 0; i < npages; ++i) { GList *item; GList *annots = doc->annotations.pages[i]; for (item = annots; item; item = item->next) { annotation_t *a = (annotation_t*) item->data; poppler_annot_mapping_free(a->amap); g_free (a->key); g_free (a); } g_list_free (annots); } g_hash_table_destroy (doc->annotations.keys); g_free (doc->annotations.pages); } g_object_unref (doc->pdf); g_free (doc); } /** * Parse a list of whitespace separated double values. * * @param str The input string. * @param values[out] Values are put here. * @param nvalues How many values to parse. * * @return TRUE, if str contained exactly nvalues, else FALSE. */ static gboolean parse_double_list (const char *str, gdouble *values, size_t nvalues) { char *end; int i; if (! str) return FALSE; errno = 0; for (i = 0; i < nvalues; ++i) { gdouble n = g_ascii_strtod (str, &end); if (str == end || errno) return FALSE; values[i] = n; str = end; } if (*end) return FALSE; return TRUE; } static gboolean parse_rectangle (const char *str, PopplerRectangle *r) { gdouble values[4]; if (! r) return FALSE; if (! parse_double_list (str, values, 4)) return FALSE; r->x1 = values[0]; r->y1 = values[1]; r->x2 = values[2]; r->y2 = values[3]; return TRUE; } static gboolean parse_edges_or_position (const char *str, PopplerRectangle *r) { return (parse_rectangle (str, r) && r->x1 >= 0 && r->x1 <= 1 && r->x2 <= 1 && r->y1 >= 0 && r->y1 <= 1 && r->y2 <= 1); } static gboolean parse_edges (const char *str, PopplerRectangle *r) { return (parse_rectangle (str, r) && r->x1 >= 0 && r->x1 <= 1 && r->x2 >= 0 && r->x2 <= 1 && r->y1 >= 0 && r->y1 <= 1 && r->y2 >= 0 && r->y2 <= 1); } /** * Print a string properly escaped for a response. * * @param str The string to be printed. * @param suffix_char Append a newline if NEWLINE, a colon if COLON. */ static void print_response_string (const char *str, enum suffix_char suffix) { if (str) { while (*str) { switch (*str) { case '\n': printf ("\\n"); break; case '\\': printf ("\\\\"); break; case ':': printf ("\\:"); break; default: putchar (*str); } ++str; } } switch (suffix) { case NEWLINE: putchar ('\n'); break; case COLON: putchar (':'); break; default: ; } } /** * Print a formatted error response. * * @param fmt The printf-like format string. */ static void printf_error_response (const char *fmt, ...) { va_list va; puts ("ERR"); va_start (va, fmt); vprintf (fmt, va); va_end (va); puts ("\n."); fflush (stdout); } /** * Remove one trailing newline character. Does nothing, if str does * not end with a newline. * * @param str The string. * * @return str with trailing newline removed. */ static char* strchomp (char *str) { size_t length; if (! str) return str; length = strlen (str); if (str[length - 1] == '\n') str[length - 1] = '\0'; return str; } /** * Create a new, temporary file and returns its name. * * @return The filename. */ static char* mktempfile() { char *filename = NULL; int tries = 3; while (! filename && tries-- > 0) { filename = tempnam(NULL, "epdfinfo"); if (filename) { int fd = open(filename, O_CREAT | O_EXCL | O_RDONLY, S_IRUSR | S_IWUSR); if (fd > 0) close (fd); else { free (filename); filename = NULL; } } } if (! filename) fprintf (stderr, "Unable to create tempfile"); return filename; } static void image_recolor (cairo_surface_t * surface, const PopplerColor * fg, const PopplerColor * bg) { /* uses a representation of a rgb color as follows: - a lightness scalar (between 0,1), which is a weighted average of r, g, b, - a hue vector, which indicates a radian direction from the grey axis, inside the equal lightness plane. - a saturation scalar between 0,1. It is 0 when grey, 1 when the color is in the boundary of the rgb cube. */ const unsigned int page_width = cairo_image_surface_get_width (surface); const unsigned int page_height = cairo_image_surface_get_height (surface); const int rowstride = cairo_image_surface_get_stride (surface); unsigned char *image = cairo_image_surface_get_data (surface); /* RGB weights for computing lightness. Must sum to one */ static const double a[] = { 0.30, 0.59, 0.11 }; const double f = 65535.; const double rgb_fg[] = { fg->red / f, fg->green / f, fg->blue / f }; const double rgb_bg[] = { bg->red / f, bg->green / f, bg->blue / f }; const double rgb_diff[] = { rgb_bg[0] - rgb_fg[0], rgb_bg[1] - rgb_fg[1], rgb_bg[2] - rgb_fg[2] }; unsigned int y; for (y = 0; y < page_height * rowstride; y += rowstride) { unsigned char *data = image + y; unsigned int x; for (x = 0; x < page_width; x++, data += 4) { /* Careful. data color components blue, green, red. */ const double rgb[3] = { (double) data[2] / 256., (double) data[1] / 256., (double) data[0] / 256. }; /* compute h, s, l data */ double l = a[0] * rgb[0] + a[1] * rgb[1] + a[2] * rgb[2]; /* linear interpolation between dark and light with color ligtness as * a parameter */ data[2] = (unsigned char) round (255. * (l * rgb_diff[0] + rgb_fg[0])); data[1] = (unsigned char) round (255. * (l * rgb_diff[1] + rgb_fg[1])); data[0] = (unsigned char) round (255. * (l * rgb_diff[2] + rgb_fg[2])); } } } /** * Render a PDF page. * * @param pdf The PDF document. * @param page The page to be rendered. * @param width The desired width of the image. * * @return A cairo_t context encapsulating the rendered image, or * NULL, if rendering failed for some reason. */ static cairo_surface_t* image_render_page(PopplerDocument *pdf, PopplerPage *page, int width, gboolean do_render_annotaions, const render_options_t *options) { cairo_t *cr = NULL; cairo_surface_t *surface = NULL; double pt_width, pt_height; int height; double scale = 1; if (! page || ! pdf) return NULL; if (width < 1) width = 1; poppler_page_get_size (page, &pt_width, &pt_height); scale = width / pt_width; height = (int) ((scale * pt_height) + 0.5); surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) { fprintf (stderr, "Failed to create cairo surface\n"); goto error; } cr = cairo_create (surface); if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) { fprintf (stderr, "Failed to create cairo handle\n"); goto error; } cairo_translate (cr, 0, 0); cairo_scale (cr, scale, scale); /* Render w/o annotations. */ if (! do_render_annotaions || (options && options->printed)) poppler_page_render_for_printing_with_options (page, cr, POPPLER_PRINT_DOCUMENT); else poppler_page_render (page, cr) ; if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) { fprintf (stderr, "Failed to render page\n"); goto error; } /* This makes the colors look right. */ cairo_set_operator (cr, CAIRO_OPERATOR_DEST_OVER); cairo_set_source_rgb (cr, 1., 1., 1.); cairo_paint (cr); if (options && options->usecolors) image_recolor (surface, &options->fg, &options->bg); cairo_destroy (cr); return surface; error: if (surface != NULL) cairo_surface_destroy (surface); if (cr != NULL) cairo_destroy (cr); return NULL; } /** * Write an image to a filename. * * @param cr The cairo context encapsulating the image. * @param filename The filename to be written to. * @param type The desired image type. * * @return 1 if the image was written successfully, else 0. */ static gboolean image_write (cairo_surface_t *surface, const char *filename, enum image_type type) { int i, j; unsigned char *data; int width, height; FILE *file = NULL; gboolean success = 0; if (! surface || cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { fprintf (stderr, "Invalid cairo surface\n"); return 0; } if (! (file = fopen (filename, "wb"))) { fprintf (stderr, "Can not open file: %s\n", filename); return 0; } cairo_surface_flush (surface); width = cairo_image_surface_get_width (surface); height = cairo_image_surface_get_height (surface); data = cairo_image_surface_get_data (surface); switch (type) { case PPM: { unsigned char *buffer = g_malloc (width * height * 3); unsigned char *buffer_p = buffer; fprintf (file, "P6\n%d %d\n255\n", width, height); for (i = 0; i < width * height; ++i, data += 4, buffer_p += 3) ARGB_TO_RGB (buffer_p, data); fwrite (buffer, 1, width * height * 3, file); g_free (buffer); success = 1; } break; case PNG: { png_infop info_ptr = NULL; png_structp png_ptr = NULL; unsigned char *row = NULL; png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) goto finalize; info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) goto finalize; if (setjmp(png_jmpbuf(png_ptr))) goto finalize; png_init_io (png_ptr, file); png_set_compression_level (png_ptr, 1); png_set_IHDR (png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_DEFAULT); png_set_filter (png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE); png_write_info (png_ptr, info_ptr); row = g_malloc (3 * width); for (i = 0; i < height; ++i) { unsigned char *row_p = row; for (j = 0; j < width; ++j, data += 4, row_p += 3) { ARGB_TO_RGB (row_p, data); } png_write_row (png_ptr, row); } png_write_end (png_ptr, NULL); success = 1; finalize: if (png_ptr) png_destroy_write_struct (&png_ptr, &info_ptr); if (row) g_free (row); if (! success) fprintf (stderr, "Error writing png data\n"); } break; default: internal_error ("switch fell through"); } fclose (file); return success; } static void image_write_print_response(cairo_surface_t *surface, enum image_type type) { char *filename = mktempfile (); perror_if_not (filename, "Unable to create temporary file"); if (image_write (surface, filename, type)) { OK_BEGIN (); print_response_string (filename, NEWLINE); OK_END (); } else { printf_error_response ("Unable to write image"); } free (filename); error: return; } static void region_print (cairo_region_t *region, double width, double height) { int i; for (i = 0; i < cairo_region_num_rectangles (region); ++i) { cairo_rectangle_int_t r; cairo_region_get_rectangle (region, i, &r); printf ("%f %f %f %f", r.x / width, r.y / height, (r.x + r.width) / width, (r.y + r.height) / height); if (i < cairo_region_num_rectangles (region) - 1) putchar (':'); } if (0 == cairo_region_num_rectangles (region)) printf ("0.0 0.0 0.0 0.0"); } /** * Return a string representation of a PopplerActionType. * * @param type The PopplerActionType. * * @return Its string representation. */ static const char * xpoppler_action_type_string(PopplerActionType type) { switch (type) { case POPPLER_ACTION_UNKNOWN: return "unknown"; case POPPLER_ACTION_NONE: return "none"; case POPPLER_ACTION_GOTO_DEST: return "goto-dest"; case POPPLER_ACTION_GOTO_REMOTE: return "goto-remote"; case POPPLER_ACTION_LAUNCH: return "launch"; case POPPLER_ACTION_URI: return "uri"; case POPPLER_ACTION_NAMED: return "goto-dest"; /* actually "named" */ case POPPLER_ACTION_MOVIE: return "movie"; case POPPLER_ACTION_RENDITION: return "rendition"; case POPPLER_ACTION_OCG_STATE: return "ocg-state"; case POPPLER_ACTION_JAVASCRIPT: return "javascript"; default: return "invalid"; } } /** * Return a string representation of a PopplerAnnotType. * * @param type The PopplerAnnotType. * * @return Its string representation. */ static const char * xpoppler_annot_type_string (PopplerAnnotType type) { switch (type) { case POPPLER_ANNOT_UNKNOWN: return "unknown"; case POPPLER_ANNOT_TEXT: return "text"; case POPPLER_ANNOT_LINK: return "link"; case POPPLER_ANNOT_FREE_TEXT: return "free-text"; case POPPLER_ANNOT_LINE: return "line"; case POPPLER_ANNOT_SQUARE: return "square"; case POPPLER_ANNOT_CIRCLE: return "circle"; case POPPLER_ANNOT_POLYGON: return "polygon"; case POPPLER_ANNOT_POLY_LINE: return "poly-line"; case POPPLER_ANNOT_HIGHLIGHT: return "highlight"; case POPPLER_ANNOT_UNDERLINE: return "underline"; case POPPLER_ANNOT_SQUIGGLY: return "squiggly"; case POPPLER_ANNOT_STRIKE_OUT: return "strike-out"; case POPPLER_ANNOT_STAMP: return "stamp"; case POPPLER_ANNOT_CARET: return "caret"; case POPPLER_ANNOT_INK: return "ink"; case POPPLER_ANNOT_POPUP: return "popup"; case POPPLER_ANNOT_FILE_ATTACHMENT: return "file"; case POPPLER_ANNOT_SOUND: return "sound"; case POPPLER_ANNOT_MOVIE: return "movie"; case POPPLER_ANNOT_WIDGET: return "widget"; case POPPLER_ANNOT_SCREEN: return "screen"; case POPPLER_ANNOT_PRINTER_MARK: return "printer-mark"; case POPPLER_ANNOT_TRAP_NET: return "trap-net"; case POPPLER_ANNOT_WATERMARK: return "watermark"; case POPPLER_ANNOT_3D: return "3d"; default: return "invalid"; } } /** * Return a string representation of a PopplerAnnotTextState. * * @param type The PopplerAnnotTextState. * * @return Its string representation. */ static const char * xpoppler_annot_text_state_string (PopplerAnnotTextState state) { switch (state) { case POPPLER_ANNOT_TEXT_STATE_MARKED: return "marked"; case POPPLER_ANNOT_TEXT_STATE_UNMARKED: return "unmarked"; case POPPLER_ANNOT_TEXT_STATE_ACCEPTED: return "accepted"; case POPPLER_ANNOT_TEXT_STATE_REJECTED: return "rejected"; case POPPLER_ANNOT_TEXT_STATE_CANCELLED: return "cancelled"; case POPPLER_ANNOT_TEXT_STATE_COMPLETED: return "completed"; case POPPLER_ANNOT_TEXT_STATE_NONE: return "none"; case POPPLER_ANNOT_TEXT_STATE_UNKNOWN: default: return "unknown"; } }; static document_t* document_open (const epdfinfo_t *ctx, const char *filename, const char *passwd, GError **gerror) { char *uri; document_t *doc = g_hash_table_lookup (ctx->documents, filename); if (NULL != doc) return doc; doc = g_malloc0(sizeof (document_t)); uri = g_filename_to_uri (filename, NULL, gerror); if (uri != NULL) doc->pdf = poppler_document_new_from_file(uri, passwd, gerror); if (NULL == doc->pdf) { g_free (doc); doc = NULL; } else { doc->filename = g_strdup (filename); doc->passwd = g_strdup (passwd); g_hash_table_insert (ctx->documents, doc->filename, doc); } g_free (uri); return doc; } /** * Split command args into a list of strings. * * @param args The colon separated list of arguments. * @param nargs[out] The number of returned arguments. * * @return The list of arguments, which should be freed by the caller. */ static char ** command_arg_split (const char *args, int *nargs) { char **list = g_malloc (sizeof (char*) * 16); int i = 0; size_t allocated = 16; char *buffer = NULL; gboolean last = FALSE; if (! args) goto theend; buffer = g_malloc (strlen (args) + 1); while (*args || last) { gboolean esc = FALSE; char *buffer_p = buffer; while (*args && (*args != ':' || esc)) { if (esc) { if (*args == 'n') { ++args; *buffer_p++ = '\n'; } else { *buffer_p++ = *args++; } esc = FALSE; } else if (*args == '\\') { ++args; esc = TRUE; } else { *buffer_p++ = *args++; } } *buffer_p = '\0'; if (i >= allocated) { allocated = 2 * allocated + 1; list = g_realloc (list, sizeof (char*) * allocated); } list[i++] = g_strdup (buffer); last = FALSE; if (*args) { ++args; if (! *args) last = TRUE; } } theend: g_free (buffer); *nargs = i; return list; } static gboolean command_arg_parse_arg (const epdfinfo_t *ctx, const char *arg, command_arg_t *cmd_arg, command_arg_type_t type, gchar **error_msg) { GError *gerror = NULL; if (! arg || !cmd_arg) return FALSE; switch (type) { case ARG_DOC: { document_t *doc = document_open (ctx, arg, NULL, &gerror); cerror_if_not (doc, error_msg, "Error opening %s:%s", arg, gerror ? gerror->message : "Unknown reason"); cmd_arg->value.doc = doc; break; } case ARG_BOOL: cerror_if_not (! strcmp (arg, "0") || ! strcmp (arg, "1"), error_msg, "Expected 0 or 1:%s", arg); cmd_arg->value.flag = *arg == '1'; break; case ARG_NONEMPTY_STRING: cerror_if_not (*arg, error_msg, "Non-empty string expected"); /* fall through */ case ARG_STRING: cmd_arg->value.string = arg; break; case ARG_NATNUM: { char *endptr; long n = strtol (arg, &endptr, 0); cerror_if_not (! (*endptr || (n < 0)), error_msg, "Expected natural number:%s", arg); cmd_arg->value.natnum = n; } break; case ARG_EDGES_OR_POSITION: { PopplerRectangle *r = &cmd_arg->value.rectangle; cerror_if_not (parse_edges_or_position (arg, r), error_msg, "Expected a relative position or rectangle: %s", arg); } break; case ARG_EDGES: { PopplerRectangle *r = &cmd_arg->value.rectangle; cerror_if_not (parse_edges (arg, r), error_msg, "Expected a relative rectangle: %s", arg); } break; case ARG_EDGE_OR_NEGATIVE: case ARG_EDGE: { char *endptr; double n = strtod (arg, &endptr); cerror_if_not (! (*endptr || (type != ARG_EDGE_OR_NEGATIVE && n < 0.0) || n > 1.0), error_msg, "Expected a relative edge: %s", arg); cmd_arg->value.edge = n; } break; case ARG_COLOR: { guint r,g,b; cerror_if_not ((strlen (arg) == 7 && 3 == sscanf (arg, "#%2x%2x%2x", &r, &g, &b)), error_msg, "Invalid color: %s", arg); cmd_arg->value.color.red = r << 8; cmd_arg->value.color.green = g << 8; cmd_arg->value.color.blue = b << 8; } break; case ARG_INVALID: default: internal_error ("switch fell through"); } cmd_arg->type = type; return TRUE; error: if (gerror) { g_error_free (gerror); gerror = NULL; } return FALSE; } /** * Parse arguments for a command. * * @param ctx The epdfinfo context. * @param args A string holding the arguments. This is either empty * or the suffix of the command starting at the first * colon after the command name. * @param len The length of args. * @param cmd The command for which the arguments should be parsed. * * @return */ static command_arg_t* command_arg_parse(epdfinfo_t *ctx, char **args, int nargs, const command_t *cmd, gchar **error_msg) { command_arg_t *cmd_args = g_malloc0 (cmd->nargs * sizeof (command_arg_t)); int i; if (nargs < cmd->nargs - 1 || (nargs == cmd->nargs - 1 && cmd->args_spec[cmd->nargs - 1] != ARG_REST) || (nargs > cmd->nargs && (cmd->nargs == 0 || cmd->args_spec[cmd->nargs - 1] != ARG_REST))) { if (error_msg) { *error_msg = g_strdup_printf ("Command `%s' expects %d argument(s), %d given", cmd->name, cmd->nargs, nargs); } goto failure; } for (i = 0; i < cmd->nargs; ++i) { if (i == cmd->nargs - 1 && cmd->args_spec[i] == ARG_REST) { cmd_args[i].value.rest.args = args + i; cmd_args[i].value.rest.nargs = nargs - i; cmd_args[i].type = ARG_REST; } else if (i >= nargs || ! command_arg_parse_arg (ctx, args[i], cmd_args + i, cmd->args_spec[i], error_msg)) { goto failure; } } return cmd_args; failure: free_command_args (cmd_args, cmd->nargs); return NULL; } static void command_arg_print(const command_arg_t *arg) { switch (arg->type) { case ARG_INVALID: printf ("[invalid]"); break; case ARG_DOC: print_response_string (arg->value.doc->filename, NONE); break; case ARG_BOOL: printf ("%d", arg->value.flag ? 1 : 0); break; case ARG_NONEMPTY_STRING: /* fall */ case ARG_STRING: print_response_string (arg->value.string, NONE); break; case ARG_NATNUM: printf ("%ld", arg->value.natnum); break; case ARG_EDGE_OR_NEGATIVE: /* fall */ case ARG_EDGE: printf ("%f", arg->value.edge); break; case ARG_EDGES_OR_POSITION: /* fall */ case ARG_EDGES: { const PopplerRectangle *r = &arg->value.rectangle; if (r->x2 < 0 && r->y2 < 0) printf ("%f %f", r->x1, r->y1); else printf ("%f %f %f %f", r->x1, r->y1, r->x2, r->y2); break; } case ARG_COLOR: { const PopplerColor *c = &arg->value.color; printf ("#%.2x%.2x%.2x", c->red >> 8, c->green >> 8, c->blue >> 8); break; } case ARG_REST: { int i; for (i = 0; i < arg->value.rest.nargs; ++i) print_response_string (arg->value.rest.args[i], COLON); if (arg->value.rest.nargs > 0) print_response_string (arg->value.rest.args[i], NONE); break; } default: internal_error ("switch fell through"); } } static size_t command_arg_type_size(command_arg_type_t type) { command_arg_t arg; switch (type) { case ARG_INVALID: return 0; case ARG_DOC: return sizeof (arg.value.doc); case ARG_BOOL: return sizeof (arg.value.flag); case ARG_NONEMPTY_STRING: /* fall */ case ARG_STRING: return sizeof (arg.value.string); case ARG_NATNUM: return sizeof (arg.value.natnum); case ARG_EDGE_OR_NEGATIVE: /* fall */ case ARG_EDGE: return sizeof (arg.value.edge); case ARG_EDGES_OR_POSITION: /* fall */ case ARG_EDGES: return sizeof (arg.value.rectangle); case ARG_COLOR: return sizeof (arg.value.color); case ARG_REST: return sizeof (arg.value.rest); default: internal_error ("switch fell through"); return 0; } } /* ------------------------------------------------------------------ * * PDF Actions * ------------------------------------------------------------------ */ static gboolean action_is_handled (PopplerAction *action) { if (! action) return FALSE; switch (action->any.type) { case POPPLER_ACTION_GOTO_REMOTE: case POPPLER_ACTION_GOTO_DEST: case POPPLER_ACTION_NAMED: /* case POPPLER_ACTION_LAUNCH: */ case POPPLER_ACTION_URI: return TRUE; default: ; } return FALSE; } static void action_print_destination (PopplerDocument *doc, PopplerAction *action) { PopplerDest *dest = NULL; gboolean free_dest = FALSE; double width, height, top; PopplerPage *page; int saved_stdin; if (action->any.type == POPPLER_ACTION_GOTO_DEST && action->goto_dest.dest->type == POPPLER_DEST_NAMED) { DISCARD_STDOUT (saved_stdin); /* poppler_document_find_dest reports errors to stdout, so discard them. */ dest = poppler_document_find_dest (doc, action->goto_dest.dest->named_dest); UNDISCARD_STDOUT (saved_stdin); free_dest = TRUE; } else if (action->any.type == POPPLER_ACTION_NAMED) { DISCARD_STDOUT (saved_stdin); dest = poppler_document_find_dest (doc, action->named.named_dest); UNDISCARD_STDOUT (saved_stdin); free_dest = TRUE; } else if (action->any.type == POPPLER_ACTION_GOTO_REMOTE) { print_response_string (action->goto_remote.file_name, COLON); dest = action->goto_remote.dest; } else if (action->any.type == POPPLER_ACTION_GOTO_DEST) dest = action->goto_dest.dest; if (!dest || dest->type == POPPLER_DEST_UNKNOWN || dest->page_num < 1 || dest->page_num > poppler_document_get_n_pages (doc)) { printf (":"); goto theend; } printf ("%d:", dest->page_num); if (action->type == POPPLER_ACTION_GOTO_REMOTE || NULL == (page = poppler_document_get_page (doc, dest->page_num - 1))) { goto theend; } poppler_page_get_size (page, &width, &height); g_object_unref (page); top = (height - dest->top) / height; /* adapted from xpdf */ switch (dest->type) { case POPPLER_DEST_XYZ: if (dest->change_top) printf ("%f", top); break; case POPPLER_DEST_FIT: case POPPLER_DEST_FITB: case POPPLER_DEST_FITH: case POPPLER_DEST_FITBH: putchar ('0'); break; case POPPLER_DEST_FITV: case POPPLER_DEST_FITBV: case POPPLER_DEST_FITR: printf ("%f", top); break; default: ; } theend: if (free_dest) poppler_dest_free (dest); } static void action_print (PopplerDocument *doc, PopplerAction *action) { if (! action_is_handled (action)) return; print_response_string (xpoppler_action_type_string (action->any.type), COLON); print_response_string (action->any.title, COLON); switch (action->any.type) { case POPPLER_ACTION_GOTO_REMOTE: case POPPLER_ACTION_GOTO_DEST: case POPPLER_ACTION_NAMED: action_print_destination (doc, action); putchar ('\n'); break; case POPPLER_ACTION_LAUNCH: print_response_string (action->launch.file_name, COLON); print_response_string (action->launch.params, NEWLINE); break; case POPPLER_ACTION_URI: print_response_string (action->uri.uri, NEWLINE); break; default: ; } } /* ------------------------------------------------------------------ * * PDF Annotations and Attachments * ------------------------------------------------------------------ */ /* static gint * annotation_cmp_edges (const annotation_t *a1, const annotation_t *a2) * { * PopplerRectangle *e1 = &a1->amap->area; * PopplerRectangle *e2 = &a2->amap->area; * * return (e1->y1 > e2->y1 ? -1 * : e1->y1 < e2->y1 ? 1 * : e1->x1 < e2->x1 ? -1 * : e1->x1 != e2->x1); * } */ static GList* annoation_get_for_page (document_t *doc, gint pn) { GList *annot_list, *item; PopplerPage *page; gint i = 0; gint npages = poppler_document_get_n_pages (doc->pdf); if (pn < 1 || pn > npages) return NULL; if (! doc->annotations.pages) doc->annotations.pages = g_malloc0 (npages * sizeof(GList*)); if (doc->annotations.pages[pn - 1]) return doc->annotations.pages[pn - 1]; if (! doc->annotations.keys) doc->annotations.keys = g_hash_table_new (g_str_hash, g_str_equal); page = poppler_document_get_page (doc->pdf, pn - 1); if (NULL == page) return NULL; annot_list = poppler_page_get_annot_mapping (page); for (item = annot_list; item; item = item->next) { PopplerAnnotMapping *map = (PopplerAnnotMapping *)item->data; gchar *key = g_strdup_printf ("annot-%d-%d", pn, i); annotation_t *a = g_malloc (sizeof (annotation_t)); a->amap = map; a->key = key; doc->annotations.pages[pn - 1] = g_list_prepend (doc->annotations.pages[pn - 1], a); assert (NULL == g_hash_table_lookup (doc->annotations.keys, key)); g_hash_table_insert (doc->annotations.keys, key, a); ++i; } g_list_free (annot_list); g_object_unref (page); return doc->annotations.pages[pn - 1]; } static annotation_t* annotation_get_by_key (document_t *doc, const gchar *key) { if (! doc->annotations.keys) return NULL; return g_hash_table_lookup (doc->annotations.keys, key); } #ifdef HAVE_POPPLER_ANNOT_MARKUP void annotation_translate_quadrilateral (PopplerPage *page, PopplerQuadrilateral *q, gboolean inverse) { PopplerRectangle cbox; gdouble xs, ys; poppler_page_get_crop_box (page, &cbox); xs = MIN (cbox.x1, cbox.x2); ys = MIN (cbox.y1, cbox.y2); if (inverse) { xs = -xs; ys = -ys; } q->p1.x -= xs, q->p2.x -= xs; q->p3.x -= xs; q->p4.x -= xs; q->p1.y -= ys, q->p2.y -= ys; q->p3.y -= ys; q->p4.y -= ys; } static cairo_region_t* annotation_markup_get_text_regions (PopplerPage *page, PopplerAnnotTextMarkup *a) { GArray *quads = poppler_annot_text_markup_get_quadrilaterals (a); int i; cairo_region_t *region = cairo_region_create (); gdouble height; poppler_page_get_size (page, NULL, &height); for (i = 0; i < quads->len; ++i) { PopplerQuadrilateral *q = &g_array_index (quads, PopplerQuadrilateral, i); cairo_rectangle_int_t r; annotation_translate_quadrilateral (page, q, FALSE); q->p1.y = height - q->p1.y; q->p2.y = height - q->p2.y; q->p3.y = height - q->p3.y; q->p4.y = height - q->p4.y; r.x = (int) (MIN (q->p1.x, MIN (q->p2.x, MIN (q->p3.x, q->p4.x))) + 0.5); r.y = (int) (MIN (q->p1.y, MIN (q->p2.y, MIN (q->p3.y, q->p4.y))) + 0.5); r.width = (int) (MAX (q->p1.x, MAX (q->p2.x, MAX (q->p3.x, q->p4.x))) + 0.5) - r.x; r.height = (int) (MAX (q->p1.y, MAX (q->p2.y, MAX (q->p3.y, q->p4.y))) + 0.5) - r.y; cairo_region_union_rectangle (region, &r); } g_array_unref (quads); return region; } /** * Append quadrilaterals equivalent to region to an array. * * @param page The page of the annotation. This is used to get the * text regions and pagesize. * @param region The region to add. * @param garray[in,out] An array of PopplerQuadrilateral, where the * new quadrilaterals will be appended. */ static void annotation_markup_append_text_region (PopplerPage *page, PopplerRectangle *region, GArray *garray) { gdouble height; /* poppler_page_get_selection_region is deprecated w/o a replacement. (poppler_page_get_selected_region returns a union of rectangles.) */ GList *regions = poppler_page_get_selection_region (page, 1.0, POPPLER_SELECTION_GLYPH, region); GList *item; poppler_page_get_size (page, NULL, &height); for (item = regions; item; item = item->next) { PopplerRectangle *r = item->data; PopplerQuadrilateral q; q.p1.x = r->x1; q.p1.y = height - r->y1; q.p2.x = r->x2; q.p2.y = height - r->y1; q.p4.x = r->x2; q.p4.y = height - r->y2; q.p3.x = r->x1; q.p3.y = height - r->y2; annotation_translate_quadrilateral (page, &q, TRUE); g_array_append_val (garray, q); } g_list_free (regions); } #endif /** * Create a new annotation. * * @param doc The document for which to create it. * @param type The type of the annotation. * @param r The rectangle where annotation will end up on the page. * * @return The new annotation, or NULL, if the annotation type is * not available. */ static PopplerAnnot* annotation_new (const epdfinfo_t *ctx, document_t *doc, PopplerPage *page, const char *type, PopplerRectangle *r, const command_arg_t *rest, char **error_msg) { PopplerAnnot *a = NULL; int nargs = rest->value.rest.nargs; #ifdef HAVE_POPPLER_ANNOT_MARKUP char * const *args = rest->value.rest.args; int i; GArray *garray = NULL; command_arg_t carg; double width, height; cairo_region_t *region = NULL; #endif if (! strcmp (type, "text")) { cerror_if_not (nargs == 0, error_msg, "%s", "Too many arguments"); return poppler_annot_text_new (doc->pdf, r); } #ifdef HAVE_POPPLER_ANNOT_MARKUP garray = g_array_new (FALSE, FALSE, sizeof (PopplerQuadrilateral)); poppler_page_get_size (page, &width, &height); for (i = 0; i < nargs; ++i) { PopplerRectangle *rr = &carg.value.rectangle; error_if_not (command_arg_parse_arg (ctx, args[i], &carg, ARG_EDGES, error_msg)); rr->x1 *= width; rr->x2 *= width; rr->y1 *= height; rr->y2 *= height; annotation_markup_append_text_region (page, rr, garray); } cerror_if_not (garray->len > 0, error_msg, "%s", "Unable to create empty markup annotation"); if (! strcmp (type, "highlight")) a = poppler_annot_text_markup_new_highlight (doc->pdf, r, garray); else if (! strcmp (type, "squiggly")) a = poppler_annot_text_markup_new_squiggly (doc->pdf, r, garray); else if (! strcmp (type, "strike-out")) a = poppler_annot_text_markup_new_strikeout (doc->pdf, r, garray); else if (! strcmp (type, "underline")) a = poppler_annot_text_markup_new_underline (doc->pdf, r, garray); else cerror_if_not (0, error_msg, "Unknown annotation type: %s", type); #endif error: #ifdef HAVE_POPPLER_ANNOT_MARKUP if (garray) g_array_unref (garray); if (region) cairo_region_destroy (region); #endif return a; } static gboolean annotation_edit_validate (const epdfinfo_t *ctx, const command_arg_t *rest, PopplerAnnot *annotation, char **error_msg) { int nargs = rest->value.rest.nargs; char * const *args = rest->value.rest.args; int i = 0; command_arg_t carg; const char* error_fmt = "Can modify `%s' property only for %s annotations"; while (i < nargs) { command_arg_type_t atype = ARG_INVALID; const char *key = args[i++]; cerror_if_not (i < nargs, error_msg, "Missing a value argument"); if (! strcmp (key, "flags")) atype = ARG_NATNUM; else if (! strcmp (key, "color")) atype = ARG_COLOR; else if (! strcmp (key, "contents")) atype = ARG_STRING; else if (! strcmp (key, "edges")) atype = ARG_EDGES_OR_POSITION; else if (! strcmp (key, "label")) { cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, error_fmt, key, "markup"); atype = ARG_STRING; } else if (! strcmp (key, "opacity")) { cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, error_fmt, key, "markup"); atype = ARG_EDGE; } else if (! strcmp (key, "popup")) { cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, error_fmt, key, "markup"); atype = ARG_EDGES; } else if (! strcmp (key, "popup-is-open")) { cerror_if_not (POPPLER_IS_ANNOT_MARKUP (annotation), error_msg, error_fmt, key, "markup"); atype = ARG_BOOL; } else if (! strcmp (key, "icon")) { cerror_if_not (POPPLER_IS_ANNOT_TEXT (annotation), error_msg, error_fmt, key, "text"); atype = ARG_STRING; } else if (! strcmp (key, "is-open")) { cerror_if_not (POPPLER_IS_ANNOT_TEXT (annotation), error_msg, error_fmt, key, "text"); atype = ARG_BOOL; } else { cerror_if_not (0, error_msg, "Unable to modify property `%s'", key); } if (! command_arg_parse_arg (ctx, args[i++], &carg, atype, error_msg)) return FALSE; } return TRUE; error: return FALSE; } static void annotation_print (const annotation_t *annot, /* const */ PopplerPage *page) { double width, height; PopplerAnnotMapping *m; const gchar *key; PopplerAnnot *a; PopplerAnnotMarkup *ma; PopplerAnnotText *ta; PopplerRectangle r; PopplerColor *color; gchar *text; gdouble opacity; cairo_region_t *region = NULL; GDate *date; if (! annot || ! page) return; m = annot->amap; key = annot->key; a = m->annot; poppler_page_get_size (page, &width, &height); r.x1 = m->area.x1; r.x2 = m->area.x2; r.y1 = height - m->area.y2; r.y2 = height - m->area.y1; #ifdef HAVE_POPPLER_ANNOT_MARKUP if (POPPLER_IS_ANNOT_TEXT_MARKUP (a)) { region = annotation_markup_get_text_regions (page, POPPLER_ANNOT_TEXT_MARKUP (a)); perror_if_not (region, "%s", "Unable to extract annotation's text regions"); } #endif /* >>> Any Annotation >>> */ /* Page */ printf ("%d:", poppler_page_get_index (page) + 1); /* Area */ printf ("%f %f %f %f:", r.x1 / width, r.y1 / height , r.x2 / width, r.y2 / height); /* Type */ printf ("%s:", xpoppler_annot_type_string (poppler_annot_get_annot_type (a))); /* Internal Key */ print_response_string (key, COLON); /* Flags */ printf ("%d:", poppler_annot_get_flags (a)); /* Color */ color = poppler_annot_get_color (a); if (color) { /* Reduce 2 Byte to 1 Byte color space */ printf ("#%.2x%.2x%.2x", (color->red >> 8) , (color->green >> 8) , (color->blue >> 8)); g_free (color); } putchar (':'); /* Text Contents */ text = poppler_annot_get_contents (a); print_response_string (text, COLON); g_free (text); /* Modified Date */ text = poppler_annot_get_modified (a); print_response_string (text, NONE); g_free (text); /* <<< Any Annotation <<< */ /* >>> Markup Annotation >>> */ if (! POPPLER_IS_ANNOT_MARKUP (a)) { putchar ('\n'); goto theend; } putchar (':'); ma = POPPLER_ANNOT_MARKUP (a); /* Label */ text = poppler_annot_markup_get_label (ma); print_response_string (text, COLON); g_free (text); /* Subject */ text = poppler_annot_markup_get_subject (ma); print_response_string (text, COLON); g_free (text); /* Opacity */ opacity = poppler_annot_markup_get_opacity (ma); printf ("%f:", opacity); /* Popup (Area + isOpen) */ if (poppler_annot_markup_has_popup (ma) && poppler_annot_markup_get_popup_rectangle (ma, &r)) { gdouble tmp = r.y1; r.y1 = height - r.y2; r.y2 = height - tmp; printf ("%f %f %f %f:%d:", r.x1 / width, r.y1 / height , r.x2 / width, r.y2 / height , poppler_annot_markup_get_popup_is_open (ma) ? 1 : 0); } else printf ("::"); /* Creation Date */ date = poppler_annot_markup_get_date (ma); if (date != NULL && g_date_valid(date)) { gchar datebuf[128]; g_date_strftime (datebuf, 127, "%x", date); print_response_string (datebuf, NONE); g_date_free (date); } /* <<< Markup Annotation <<< */ /* >>> Text Annotation >>> */ if (POPPLER_IS_ANNOT_TEXT (a)) { putchar (':'); ta = POPPLER_ANNOT_TEXT (a); /* Text Icon */ text = poppler_annot_text_get_icon (ta); print_response_string (text, COLON); g_free (text); /* Text State */ printf ("%s:%d", xpoppler_annot_text_state_string (poppler_annot_text_get_state (ta)), poppler_annot_text_get_is_open (ta)); } #ifdef HAVE_POPPLER_ANNOT_MARKUP /* <<< Text Annotation <<< */ else if (POPPLER_IS_ANNOT_TEXT_MARKUP (a)) { /* >>> Markup Text Annotation >>> */ putchar (':'); region_print (region, width, height); /* <<< Markup Text Annotation <<< */ } #endif putchar ('\n'); theend: #ifdef HAVE_POPPLER_ANNOT_MARKUP error: #endif if (region) cairo_region_destroy (region); } static void attachment_print (PopplerAttachment *att, const char *id, gboolean do_save) { time_t time; print_response_string (id, COLON); print_response_string (att->name, COLON); print_response_string (att->description, COLON); if (att->size + 1 != 0) printf ("%" G_GSIZE_FORMAT ":", att->size); else printf ("-1:"); time = (time_t) att->mtime; print_response_string (time > 0 ? strchomp (ctime (&time)) : "", COLON); time = (time_t) att->ctime; print_response_string (time > 0 ? strchomp (ctime (&time)) : "", COLON); print_response_string (att->checksum ? att->checksum->str : "" , COLON); if (do_save) { char *filename = mktempfile (); GError *error = NULL; if (filename) { if (! poppler_attachment_save (att, filename, &error)) { fprintf (stderr, "Writing attachment failed: %s" , error ? error->message : "reason unknown"); if (error) g_free (error); } else { print_response_string (filename, NONE); } free (filename); } } putchar ('\n'); } /* ================================================================== * * Server command implementations * ================================================================== */ /* Name: features Args: None Returns: A list of compile-time features. Errors: None */ const command_arg_type_t cmd_features_spec[] = {}; static void cmd_features (const epdfinfo_t *ctx, const command_arg_t *args) { const char *features[] = { #ifdef HAVE_POPPLER_FIND_OPTS "case-sensitive-search", #else "no-case-sensitive-search", #endif #ifdef HAVE_POPPLER_ANNOT_WRITE "writable-annotations", #else "no-writable-annotations", #endif #ifdef HAVE_POPPLER_ANNOT_MARKUP "markup-annotations" #else "no-markup-annotations" #endif }; int i; OK_BEGIN (); for (i = 0; i < G_N_ELEMENTS (features); ++i) { printf ("%s", features[i]); if (i < G_N_ELEMENTS (features) - 1) putchar (':'); } putchar ('\n'); OK_END (); } /* Name: open Args: filename password Returns: Nothing Errors: If file can't be opened or is not a PDF document. */ const command_arg_type_t cmd_open_spec[] = { ARG_NONEMPTY_STRING, /* filename */ ARG_STRING, /* password */ }; static void cmd_open (const epdfinfo_t *ctx, const command_arg_t *args) { const char *filename = args[0].value.string; const char *passwd = args[1].value.string; GError *gerror = NULL; document_t *doc; if (! *passwd) passwd = NULL; doc = document_open(ctx, filename, passwd, &gerror); perror_if_not (doc, "Error opening %s:%s", filename, gerror ? gerror->message : "unknown error"); OK (); error: if (gerror) { g_error_free (gerror); gerror = NULL; } } /* Name: close Args: filename Returns: 1 if file was open, otherwise 0. Errors: None */ const command_arg_type_t cmd_close_spec[] = { ARG_NONEMPTY_STRING /* filename */ }; static void cmd_close (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = g_hash_table_lookup(ctx->documents, args->value.string); g_hash_table_remove (ctx->documents, args->value.string); free_document (doc); OK_BEGIN (); puts (doc ? "1" : "0"); OK_END (); } /* Name: closeall Args: None Returns: Nothing Errors: None */ static void cmd_closeall (const epdfinfo_t *ctx, const command_arg_t *args) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, ctx->documents); while (g_hash_table_iter_next (&iter, &key, &value)) { document_t *doc = (document_t*) value; free_document (doc); g_hash_table_iter_remove (&iter); } OK (); } const command_arg_type_t cmd_search_regexp_spec[] = { ARG_DOC, ARG_NATNUM, /* first page */ ARG_NATNUM, /* last page */ ARG_NONEMPTY_STRING, /* regexp */ ARG_NATNUM, /* compile flags */ ARG_NATNUM /* match flags */ }; static void cmd_search_regexp(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; int first = args[1].value.natnum; int last = args[2].value.natnum; const gchar *regexp = args[3].value.string; GRegexCompileFlags cflags = args[4].value.natnum; GRegexMatchFlags mflags = args[5].value.natnum; double width, height; int pn; GError *gerror = NULL; GRegex *re = NULL; NORMALIZE_PAGE_ARG (doc, &first, &last); re = g_regex_new (regexp, cflags, mflags, &gerror); perror_if_not (NULL == gerror, "Invalid regexp: %s", gerror->message); OK_BEGIN (); for (pn = first; pn <= last; ++pn) { PopplerPage *page = poppler_document_get_page(doc, pn - 1); char *text; PopplerRectangle *rectangles = NULL; guint nrectangles; GMatchInfo *match = NULL; if (! page) continue; text = poppler_page_get_text (page); poppler_page_get_text_layout (page, &rectangles, &nrectangles); poppler_page_get_size (page, &width, &height); g_regex_match (re, text, 0, &match); while (g_match_info_matches (match)) { const double scale = 100.0; gint start, end, ustart, ulen; gchar *string = NULL; gchar *line = NULL; int i; /* Does this ever happen ? */ if (! g_match_info_fetch_pos (match, 0, &start, &end)) continue; string = g_match_info_fetch (match, 0); ustart = g_utf8_strlen (text, start); ulen = g_utf8_strlen (string, -1); cairo_region_t *region = cairo_region_create (); /* Merge matched glyph rectangles. Scale them so we're able to use cairo . */ if (ulen > 0) { assert (ustart < nrectangles && ustart + ulen <= nrectangles); line = poppler_page_get_selected_text (page, POPPLER_SELECTION_LINE, rectangles + ustart); for (i = ustart; i < ustart + ulen; ++i) { PopplerRectangle *r = rectangles + i; cairo_rectangle_int_t c; c.x = (int) (scale * r->x1 + 0.5); c.y = (int) (scale * r->y1 + 0.5); c.width = (int) (scale * (r->x2 - r->x1) + 0.5); c.height = (int) (scale * (r->y2 - r->y1) + 0.5); cairo_region_union_rectangle (region, &c); } } printf ("%d:", pn); print_response_string (string, COLON); print_response_string (strchomp (line), COLON); region_print (region, width * scale, height * scale); putchar ('\n'); cairo_region_destroy (region); g_free (string); g_free (line); g_match_info_next (match, NULL); } g_free (rectangles); g_object_unref (page); g_free (text); g_match_info_free (match); } OK_END (); error: if (re) g_regex_unref (re); if (gerror) g_error_free (gerror); } const command_arg_type_t cmd_regexp_flags_spec[] = { }; static void cmd_regexp_flags (const epdfinfo_t *ctx, const command_arg_t *args) { OK_BEGIN (); printf ("caseless:%d\n", G_REGEX_CASELESS); printf ("multiline:%d\n", G_REGEX_MULTILINE); printf ("dotall:%d\n", G_REGEX_DOTALL); printf ("extended:%d\n", G_REGEX_EXTENDED); printf ("anchored:%d\n", G_REGEX_ANCHORED); printf ("dollar-endonly:%d\n", G_REGEX_DOLLAR_ENDONLY); printf ("ungreedy:%d\n", G_REGEX_UNGREEDY); printf ("raw:%d\n", G_REGEX_RAW); printf ("no-auto-capture:%d\n", G_REGEX_NO_AUTO_CAPTURE); printf ("optimize:%d\n", G_REGEX_OPTIMIZE); printf ("dupnames:%d\n", G_REGEX_DUPNAMES); printf ("newline-cr:%d\n", G_REGEX_NEWLINE_CR); printf ("newline-lf:%d\n", G_REGEX_NEWLINE_LF); printf ("newline-crlf:%d\n", G_REGEX_NEWLINE_CRLF); printf ("match-anchored:%d\n", G_REGEX_MATCH_ANCHORED); printf ("match-notbol:%d\n", G_REGEX_MATCH_NOTBOL); printf ("match-noteol:%d\n", G_REGEX_MATCH_NOTEOL); printf ("match-notempty:%d\n", G_REGEX_MATCH_NOTEMPTY); printf ("match-partial:%d\n", G_REGEX_MATCH_PARTIAL); printf ("match-newline-cr:%d\n", G_REGEX_MATCH_NEWLINE_CR); printf ("match-newline-lf:%d\n", G_REGEX_MATCH_NEWLINE_LF); printf ("match-newline-crlf:%d\n", G_REGEX_MATCH_NEWLINE_CRLF); printf ("match-newline-any:%d\n", G_REGEX_MATCH_NEWLINE_ANY); OK_END (); } const command_arg_type_t cmd_search_string_spec[] = { ARG_DOC, ARG_NATNUM, /* first page */ ARG_NATNUM, /* last page */ ARG_NONEMPTY_STRING, /* search string */ ARG_BOOL, /* ignore-case */ }; static void cmd_search_string(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; int first = args[1].value.natnum; int last = args[2].value.natnum; const char *string = args[3].value.string; gboolean ignore_case = args[4].value.flag; GList *list, *item; double width, height; int pn; #ifdef HAVE_POPPLER_FIND_OPTS PopplerFindFlags flags = ignore_case ? 0 : POPPLER_FIND_CASE_SENSITIVE; #endif NORMALIZE_PAGE_ARG (doc, &first, &last); OK_BEGIN (); for (pn = first; pn <= last; ++pn) { PopplerPage *page = poppler_document_get_page(doc, pn - 1); if (! page) continue; #ifdef HAVE_POPPLER_FIND_OPTS list = poppler_page_find_text_with_options(page, string, flags); #else list = poppler_page_find_text(page, string); #endif poppler_page_get_size (page, &width, &height); for (item = list; item; item = item->next) { gchar *line, *match; PopplerRectangle *r = item->data; gdouble y1 = r->y1; r->y1 = height - r->y2; r->y2 = height - y1; printf ("%d:", pn); line = strchomp (poppler_page_get_selected_text (page, POPPLER_SELECTION_LINE, r)); match = strchomp (poppler_page_get_selected_text (page, POPPLER_SELECTION_GLYPH, r)); print_response_string (match, COLON); print_response_string (line, COLON); printf ("%f %f %f %f\n", r->x1 / width, r->y1 / height, r->x2 / width, r->y2 / height); g_free (line); g_free (match); poppler_rectangle_free (r); } g_list_free (list); g_object_unref (page); } OK_END (); } /* Name: metadata Args: filename Returns: PDF's metadata Errors: None title author subject keywords creator producer pdf-version create-date mod-date Dates are in seconds since the epoche. */ const command_arg_type_t cmd_metadata_spec[] = { ARG_DOC, }; static void cmd_metadata (const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; time_t date; gchar *md[6]; gchar *title; int i; char *time_str; OK_BEGIN (); title = poppler_document_get_title (doc); print_response_string (title, COLON); g_free (title); md[0] = poppler_document_get_author (doc); md[1] = poppler_document_get_subject (doc); md[2] = poppler_document_get_keywords (doc); md[3] = poppler_document_get_creator (doc); md[4] = poppler_document_get_producer (doc); md[5] = poppler_document_get_pdf_version_string (doc); for (i = 0; i < 6; ++i) { print_response_string (md[i], COLON); g_free (md[i]); } date = poppler_document_get_creation_date (doc); time_str = strchomp (ctime (&date)); print_response_string (time_str ? time_str : "", COLON); date = poppler_document_get_modification_date (doc); time_str = strchomp (ctime (&date)); print_response_string (time_str ? time_str : "", NEWLINE); OK_END (); } /* Name: outline Args: filename Returns: The documents outline (or index) as a, possibly empty, list of records: tree-level ACTION See cmd_pagelinks for how ACTION is constructed. Errors: None */ static void cmd_outline_walk (PopplerDocument *doc, PopplerIndexIter *iter, int depth) { do { PopplerIndexIter *child; PopplerAction *action = poppler_index_iter_get_action (iter); if (! action) continue; if (action_is_handled (action)) { printf ("%d:", depth); action_print (doc, action); } child = poppler_index_iter_get_child (iter); if (child) { cmd_outline_walk (doc, child, depth + 1); } poppler_action_free (action); poppler_index_iter_free (child); } while (poppler_index_iter_next (iter)); } const command_arg_type_t cmd_outline_spec[] = { ARG_DOC, }; static void cmd_outline (const epdfinfo_t *ctx, const command_arg_t *args) { PopplerIndexIter *iter = poppler_index_iter_new (args->value.doc->pdf); OK_BEGIN (); if (iter) { cmd_outline_walk (args->value.doc->pdf, iter, 1); poppler_index_iter_free (iter); } OK_END (); } /* Name: quit Args: None Returns: Nothing Errors: None Close all documents and exit. */ const command_arg_type_t cmd_quit_spec[] = {}; static void cmd_quit (const epdfinfo_t *ctx, const command_arg_t *args) { cmd_closeall (ctx, args); exit (EXIT_SUCCESS); } /* Name: number-of-pages Args: filename Returns: The number of pages. Errors: None */ const command_arg_type_t cmd_number_of_pages_spec[] = { ARG_DOC }; static void cmd_number_of_pages (const epdfinfo_t *ctx, const command_arg_t *args) { int npages = poppler_document_get_n_pages (args->value.doc->pdf); OK_BEGIN (); printf ("%d\n", npages); OK_END (); } /* Name: pagelinks Args: filename page Returns: A list of linkmaps: edges ACTION , where ACTION is one of 'goto-dest' title page top 'goto-remote' title filename page top 'uri' title URI 'launch' title program arguments top is desired vertical position, filename is the target PDF of the `goto-remote' link. Errors: None */ const command_arg_type_t cmd_pagelinks_spec[] = { ARG_DOC, ARG_NATNUM /* page number */ }; static void cmd_pagelinks(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; PopplerPage *page = NULL; int pn = args[1].value.natnum; double width, height; GList *link_map = NULL, *item; page = poppler_document_get_page (doc, pn - 1); perror_if_not (page, "No such page %d", pn); poppler_page_get_size (page, &width, &height); link_map = poppler_page_get_link_mapping (page); OK_BEGIN (); for (item = g_list_last (link_map); item; item = item->prev) { PopplerLinkMapping *link = item->data; PopplerRectangle *r = &link->area; gdouble y1 = r->y1; /* LinkMappings have a different gravity. */ r->y1 = height - r->y2; r->y2 = height - y1; if (! action_is_handled (link->action)) continue; printf ("%f %f %f %f:", r->x1 / width, r->y1 / height, r->x2 / width, r->y2 / height); action_print (doc, link->action); } OK_END (); error: if (page) g_object_unref (page); if (link_map) poppler_page_free_link_mapping (link_map); } /* Name: gettext Args: filename page edges selection-style Returns: The selection's text. Errors: If page is out of range. For the selection-style argument see getselection command. */ const command_arg_type_t cmd_gettext_spec[] = { ARG_DOC, ARG_NATNUM, /* page number */ ARG_EDGES, /* selection */ ARG_NATNUM /* selection-style */ }; static void cmd_gettext(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; int pn = args[1].value.natnum; PopplerRectangle r = args[2].value.rectangle; int selection_style = args[3].value.natnum; PopplerPage *page = NULL; double width, height; gchar *text = NULL; switch (selection_style) { case POPPLER_SELECTION_GLYPH: break; case POPPLER_SELECTION_LINE: break; case POPPLER_SELECTION_WORD: break; default: selection_style = POPPLER_SELECTION_GLYPH; } page = poppler_document_get_page (doc, pn - 1); perror_if_not (page, "No such page %d", pn); poppler_page_get_size (page, &width, &height); r.x1 = r.x1 * width; r.x2 = r.x2 * width; r.y1 = r.y1 * height; r.y2 = r.y2 * height; /* printf ("%f %f %f %f , %f %f\n", r.x1, r.y1, r.x2, r.y2, width, height); */ text = poppler_page_get_selected_text (page, selection_style, &r); OK_BEGIN (); print_response_string (text, NEWLINE); OK_END (); error: g_free (text); if (page) g_object_unref (page); } /* Name: getselection Args: filename page edges selection-selection_style Returns: The selection's text. Errors: If page is out of range. selection-selection_style should be as follows. 0 (POPPLER_SELECTION_GLYPH) glyph is the minimum unit for selection 1 (POPPLER_SELECTION_WORD) word is the minimum unit for selection 2 (POPPLER_SELECTION_LINE) line is the minimum unit for selection */ const command_arg_type_t cmd_getselection_spec[] = { ARG_DOC, ARG_NATNUM, /* page number */ ARG_EDGES, /* selection */ ARG_NATNUM /* selection-style */ }; static void cmd_getselection (const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; int pn = args[1].value.natnum; PopplerRectangle r = args[2].value.rectangle; int selection_style = args[3].value.natnum; gdouble width, height; cairo_region_t *region = NULL; PopplerPage *page = NULL; int i; switch (selection_style) { case POPPLER_SELECTION_GLYPH: break; case POPPLER_SELECTION_LINE: break; case POPPLER_SELECTION_WORD: break; default: selection_style = POPPLER_SELECTION_GLYPH; } page = poppler_document_get_page (doc, pn - 1); perror_if_not (page, "No such page %d", pn); poppler_page_get_size (page, &width, &height); r.x1 = r.x1 * width; r.x2 = r.x2 * width; r.y1 = r.y1 * height; r.y2 = r.y2 * height; region = poppler_page_get_selected_region (page, 1.0, selection_style, &r); OK_BEGIN (); for (i = 0; i < cairo_region_num_rectangles (region); ++i) { cairo_rectangle_int_t r; cairo_region_get_rectangle (region, i, &r); printf ("%f %f %f %f\n", r.x / width, r.y / height, (r.x + r.width) / width, (r.y + r.height) / height); } OK_END (); error: if (region) cairo_region_destroy (region); if (page) g_object_unref (page); } /* Name: pagesize Args: filename page Returns: width height Errors: If page is out of range. */ const command_arg_type_t cmd_pagesize_spec[] = { ARG_DOC, ARG_NATNUM /* page number */ }; static void cmd_pagesize(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; int pn = args[1].value.natnum; PopplerPage *page = NULL; double width, height; page = poppler_document_get_page (doc, pn - 1); perror_if_not (page, "No such page %d", pn); poppler_page_get_size (page, &width, &height); OK_BEGIN (); printf ("%f:%f\n", width, height); OK_END (); error: if (page) g_object_unref (page); } /* Annotations */ /* Name: getannots Args: filename firstpage lastpage Returns: The list of annotations of this page. For all annotations page edges type key flags color contents mod-date ,where name is a document-unique name, flags is PopplerAnnotFlag bitmask, color is 3-byte RGB hex number and Then label subject opacity popup-edges popup-is-open create-date if this is a markup annotation and additionally text-icon text-state for markup text annotations. Errors: If page is out of range. */ const command_arg_type_t cmd_getannots_spec[] = { ARG_DOC, ARG_NATNUM, /* first page */ ARG_NATNUM /* last page */ }; static void cmd_getannots(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; gint first = args[1].value.natnum; gint last = args[2].value.natnum; GList *list; gint pn; first = MAX(1, first); if (last <= 0) last = poppler_document_get_n_pages (doc); else last = MIN(last, poppler_document_get_n_pages (doc)); OK_BEGIN (); for (pn = first; pn <= last; ++pn) { GList *annots = annoation_get_for_page (args->value.doc, pn); PopplerPage *page = poppler_document_get_page (doc, pn - 1); if (! page) continue; for (list = annots; list; list = list->next) { annotation_t *annot = (annotation_t *)list->data; annotation_print (annot, page); } g_object_unref (page); } OK_END (); } /* Name: getannot Args: filename name Returns: The annotation for name, see cmd_getannots. Errors: If no annotation named ,name' exists. */ const command_arg_type_t cmd_getannot_spec[] = { ARG_DOC, ARG_NONEMPTY_STRING, /* annotation's key */ }; static void cmd_getannot (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args->value.doc; const gchar *key = args[1].value.string; PopplerPage *page = NULL; annotation_t *a = annotation_get_by_key (doc, key); gint index; perror_if_not (a, "No such annotation: %s", key); index = poppler_annot_get_page_index (a->amap->annot); if (index >= 0) page = poppler_document_get_page (doc->pdf, index); perror_if_not (page, "Unable to get page %d", index + 1); OK_BEGIN (); annotation_print (a, page); OK_END (); error: if (page) g_object_unref (page); } /* Name: getannot_attachment Args: filename name [output-filename] Returns: name description size mtime ctime output-filename Errors: If no annotation named ,name' exists or output-filename is not writable. */ const command_arg_type_t cmd_getattachment_from_annot_spec[] = { ARG_DOC, ARG_NONEMPTY_STRING, /* annotation's name */ ARG_BOOL /* save attachment */ }; static void cmd_getattachment_from_annot (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args->value.doc; const gchar *key = args[1].value.string; gboolean do_save = args[2].value.flag; PopplerAttachment *att = NULL; annotation_t *a = annotation_get_by_key (doc, key); gchar *id = NULL; perror_if_not (a, "No such annotation: %s", key); perror_if_not (POPPLER_IS_ANNOT_FILE_ATTACHMENT (a->amap->annot), "Not a file annotation: %s", key); att = poppler_annot_file_attachment_get_attachment (POPPLER_ANNOT_FILE_ATTACHMENT (a->amap->annot)); perror_if_not (att, "Unable to get attachment: %s", key); id = g_strdup_printf ("attachment-%s", key); OK_BEGIN (); attachment_print (att, id, do_save); OK_END (); error: if (att) g_object_unref (att); if (id) g_free (id); } /* document-level attachments */ const command_arg_type_t cmd_getattachments_spec[] = { ARG_DOC, ARG_BOOL, /* save attachments */ }; static void cmd_getattachments (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args->value.doc; gboolean do_save = args[1].value.flag; GList *item; GList *attmnts = poppler_document_get_attachments (doc->pdf); int i; OK_BEGIN (); for (item = attmnts, i = 0; item; item = item->next, ++i) { PopplerAttachment *att = (PopplerAttachment*) item->data; gchar *id = g_strdup_printf ("attachment-document-%d", i); attachment_print (att, id, do_save); g_object_unref (att); g_free (id); } g_list_free (attmnts); OK_END (); } #ifdef HAVE_POPPLER_ANNOT_WRITE const command_arg_type_t cmd_addannot_spec[] = { ARG_DOC, ARG_NATNUM, /* page number */ ARG_STRING, /* type */ ARG_EDGES_OR_POSITION, /* edges or position (uses default size) */ ARG_REST, /* markup regions */ }; static void cmd_addannot (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args->value.doc; gint pn = args[1].value.natnum; const char *type_string = args[2].value.string; PopplerRectangle r = args[3].value.rectangle; int i; PopplerPage *page = NULL; double width, height; PopplerAnnot *pa; PopplerAnnotMapping *amap; annotation_t *a; gchar *key; GList *annotations; gdouble y2; char *error_msg = NULL; page = poppler_document_get_page (doc->pdf, pn - 1); perror_if_not (page, "Unable to get page %d", pn); poppler_page_get_size (page, &width, &height); r.x1 *= width; r.x2 *= width; r.y1 *= height; r.y2 *= height; if (r.y2 < 0) r.y2 = r.y1 + 24; if (r.x2 < 0) r.x2 = r.x1 + 24; y2 = r.y2; r.y2 = height - r.y1; r.y1 = height - y2; pa = annotation_new (ctx, doc, page, type_string, &r, &args[4], &error_msg); perror_if_not (pa, "Creating annotation failed: %s", error_msg ? error_msg : "Reason unknown"); amap = poppler_annot_mapping_new (); amap->area = r; amap->annot = pa; annotations = annoation_get_for_page (doc, pn); i = g_list_length (annotations); key = g_strdup_printf ("annot-%d-%d", pn, i); while (g_hash_table_lookup (doc->annotations.keys, key)) { g_free (key); key = g_strdup_printf ("annot-%d-%d", pn, ++i); } a = g_malloc (sizeof (annotation_t)); a->amap = amap; a->key = key; doc->annotations.pages[pn - 1] = g_list_prepend (annotations, a); g_hash_table_insert (doc->annotations.keys, key, a); poppler_page_add_annot (page, pa); OK_BEGIN (); annotation_print (a, page); OK_END (); error: if (page) g_object_unref (page); if (error_msg) g_free (error_msg); } const command_arg_type_t cmd_delannot_spec[] = { ARG_DOC, ARG_NONEMPTY_STRING /* Annotation's key */ }; static void cmd_delannot (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args->value.doc; const gchar *key = args[1].value.string; PopplerPage *page = NULL; annotation_t *a = annotation_get_by_key (doc, key); gint pn; perror_if_not (a, "No such annotation: %s", key); pn = poppler_annot_get_page_index (a->amap->annot) + 1; if (pn >= 1) page = poppler_document_get_page (doc->pdf, pn - 1); perror_if_not (page, "Unable to get page %d", pn); poppler_page_remove_annot (page, a->amap->annot); doc->annotations.pages[pn - 1] = g_list_remove (doc->annotations.pages[pn - 1], a); g_hash_table_remove (doc->annotations.keys, a->key); poppler_annot_mapping_free(a->amap); OK (); error: if (a) { g_free (a->key); g_free (a); } if (page) g_object_unref (page); } const command_arg_type_t cmd_editannot_spec[] = { ARG_DOC, ARG_NONEMPTY_STRING, /* annotation key */ ARG_REST /* (KEY VALUE ...) */ }; static void cmd_editannot (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args->value.doc; const char *key = args[1].value.string; int nrest_args = args[2].value.rest.nargs; char * const *rest_args = args[2].value.rest.args; annotation_t *a = annotation_get_by_key (doc, key); PopplerAnnot *pa; PopplerPage *page = NULL; int i = 0; gint index; char *error_msg = NULL; command_arg_t carg; const char *unexpected_parse_error = "Internal error while parsing arg `%s'"; perror_if_not (a, "No such annotation: %s", key); pa = a->amap->annot; perror_if_not (annotation_edit_validate (ctx, &args[2], pa, &error_msg), "%s", error_msg); index = poppler_annot_get_page_index (pa); page = poppler_document_get_page (doc->pdf, index); perror_if_not (page, "Unable to get page %d for annotation", index); for (i = 0; i < nrest_args; ++i) { const char *key = rest_args[i++]; if (! strcmp (key, "flags")) { perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_NATNUM, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_set_flags (pa, carg.value.natnum); } else if (! strcmp (key, "color")) { perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_COLOR, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_set_color (pa, &carg.value.color); } else if (! strcmp (key, "contents")) { perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_STRING, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_set_contents (pa, carg.value.string); } else if (! strcmp (key, "edges")) { PopplerRectangle *area = &a->amap->area; gdouble width, height; PopplerRectangle r; perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_EDGES_OR_POSITION, NULL), unexpected_parse_error, rest_args[i]); r = carg.value.rectangle; poppler_page_get_size (page, &width, &height); /* Translate Gravity and maybe keep the width and height. */ if (r.x2 < 0) area->x2 += (r.x1 * width) - area->x1; else area->x2 = r.x2 * width; if (r.y2 < 0) area->y1 -= (r.y1 * height) - (height - area->y2); else area->y1 = height - (r.y2 * height); area->x1 = r.x1 * width; area->y2 = height - (r.y1 * height); poppler_annot_set_rectangle (pa, area); } else if (! strcmp (key, "label")) { PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_STRING, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_markup_set_label (ma, carg.value.string); } else if (! strcmp (key, "opacity")) { PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_EDGE, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_markup_set_opacity (ma, carg.value.edge); } else if (! strcmp (key, "popup")) { PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_EDGES, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_markup_set_popup (ma, &carg.value.rectangle); } else if (! strcmp (key, "popup-is-open")) { PopplerAnnotMarkup *ma = POPPLER_ANNOT_MARKUP (pa); perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_BOOL, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_markup_set_popup_is_open (ma, carg.value.flag); } else if (! strcmp (key, "icon")) { PopplerAnnotText *ta = POPPLER_ANNOT_TEXT (pa); perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_STRING, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_text_set_icon (ta, carg.value.string); } else if (! strcmp (key, "is-open")) { PopplerAnnotText *ta = POPPLER_ANNOT_TEXT (pa); perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &carg, ARG_BOOL, NULL), unexpected_parse_error, rest_args[i]); poppler_annot_text_set_is_open (ta, carg.value.flag); } else { perror_if_not (0, "internal error: annotation property validation failed"); } } OK_BEGIN (); annotation_print (a, page); OK_END (); error: if (error_msg) g_free (error_msg); if (page) g_object_unref (page); } const command_arg_type_t cmd_save_spec[] = { ARG_DOC, }; static void cmd_save (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args->value.doc; char *filename = mktempfile (); GError *gerror = NULL; gchar *uri; gboolean success = FALSE; if (!filename) { printf_error_response ("Unable to create temporary file"); return; } uri = g_filename_to_uri (filename, NULL, &gerror); if (uri) { success = poppler_document_save (doc->pdf, uri, &gerror); g_free (uri); } if (! success) { printf_error_response ("Error while saving %s:%s" , filename, gerror ? gerror->message : "?"); if (gerror) g_error_free (gerror); return; } OK_BEGIN (); print_response_string (filename, NEWLINE); OK_END (); } #endif /* HAVE_POPPLER_ANNOT_WRITE */ const command_arg_type_t cmd_synctex_forward_search_spec[] = { ARG_DOC, ARG_NONEMPTY_STRING, /* source file */ ARG_NATNUM, /* line number */ ARG_NATNUM /* column number */ }; static void cmd_synctex_forward_search (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args[0].value.doc; const char *source = args[1].value.string; int line = args[2].value.natnum; int column = args[3].value.natnum; synctex_scanner_p scanner = NULL; synctex_node_p node; float x1, y1, x2, y2; PopplerPage *page = NULL; double width, height; int pn; scanner = synctex_scanner_new_with_output_file (doc->filename, NULL, 1); perror_if_not (scanner, "Unable to create synctex scanner,\ did you run latex with `--synctex=1' ?"); perror_if_not (synctex_display_query (scanner, source, line, column, 0) && (node = synctex_scanner_next_result (scanner)), "Destination not found"); pn = synctex_node_page (node); page = poppler_document_get_page(doc->pdf, pn - 1); perror_if_not (page, "Page not found"); x1 = synctex_node_box_visible_h (node); y1 = synctex_node_box_visible_v (node) - synctex_node_box_visible_height (node); x2 = synctex_node_box_visible_width (node) + x1; y2 = synctex_node_box_visible_depth (node) + synctex_node_box_visible_height (node) + y1; poppler_page_get_size (page, &width, &height); x1 /= width; y1 /= height; x2 /= width; y2 /= height; OK_BEGIN (); printf("%d:%f:%f:%f:%f\n", pn, x1, y1, x2, y2); OK_END (); error: if (page) g_object_unref (page); if (scanner) synctex_scanner_free (scanner); } const command_arg_type_t cmd_synctex_backward_search_spec[] = { ARG_DOC, ARG_NATNUM, /* page number */ ARG_EDGE, /* x */ ARG_EDGE /* y */ }; static void cmd_synctex_backward_search (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args[0].value.doc; int pn = args[1].value.natnum; double x = args[2].value.edge; double y = args[3].value.edge; synctex_scanner_p scanner = NULL; const char *filename; PopplerPage *page = NULL; synctex_node_p node; double width, height; int line, column; scanner = synctex_scanner_new_with_output_file (doc->filename, NULL, 1); perror_if_not (scanner, "Unable to create synctex scanner,\ did you run latex with `--synctex=1' ?"); page = poppler_document_get_page(doc->pdf, pn - 1); perror_if_not (page, "Page not found"); poppler_page_get_size (page, &width, &height); x = x * width; y = y * height; if (! synctex_edit_query (scanner, pn, x, y) || ! (node = synctex_scanner_next_result (scanner)) || ! (filename = synctex_scanner_get_name (scanner, synctex_node_tag (node)))) { printf_error_response ("Destination not found"); goto error; } line = synctex_node_line (node); column = synctex_node_column (node); OK_BEGIN (); print_response_string (filename, COLON); printf("%d:%d\n", line, column); OK_END (); error: if (page) g_object_unref (page); if (scanner) synctex_scanner_free (scanner); } const command_arg_type_t cmd_renderpage_spec[] = { ARG_DOC, ARG_NATNUM, /* page number */ ARG_NATNUM, /* width */ ARG_REST, /* commands */ }; static void cmd_renderpage (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args[0].value.doc; int pn = args[1].value.natnum; int width = args[2].value.natnum; int nrest_args = args[3].value.rest.nargs; char * const *rest_args = args[3].value.rest.args; PopplerPage *page = poppler_document_get_page(doc->pdf, pn - 1); cairo_surface_t *surface = NULL; cairo_t *cr = NULL; command_arg_t rest_arg; gchar *error_msg = NULL; double pt_width, pt_height; PopplerColor fg = { 0, 0, 0 }; PopplerColor bg = { 65535, 0, 0 }; double alpha = 1.0; double line_width = 1.5; PopplerRectangle cb = {0.0, 0.0, 1.0, 1.0}; int i = 0; perror_if_not (page, "No such page %d", pn); poppler_page_get_size (page, &pt_width, &pt_height); surface = image_render_page (doc->pdf, page, width, 1, &doc->options.render); perror_if_not (surface, "Failed to render page %d", pn); if (! nrest_args) goto theend; cr = cairo_create (surface); cairo_scale (cr, width / pt_width, width / pt_width); while (i < nrest_args) { const char* keyword; perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, ARG_STRING, &error_msg), "%s", error_msg); keyword = rest_arg.value.string; ++i; perror_if_not (i < nrest_args, "Keyword is `%s' missing an argument", keyword); if (! strcmp (keyword, ":foreground") || ! strcmp (keyword, ":background")) { perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, ARG_COLOR, &error_msg), "%s", error_msg); ++i; if (! strcmp (keyword, ":foreground")) fg = rest_arg.value.color; else bg = rest_arg.value.color; } else if (! strcmp (keyword, ":alpha")) { perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, ARG_EDGE, &error_msg), "%s", error_msg); ++i; alpha = rest_arg.value.edge; } else if (! strcmp (keyword, ":crop-to") || ! strcmp (keyword, ":highlight-region") || ! strcmp (keyword, ":highlight-text") || ! strcmp (keyword, ":highlight-line")) { PopplerRectangle *r; perror_if_not (command_arg_parse_arg (ctx, rest_args[i], &rest_arg, ARG_EDGES, &error_msg), "%s", error_msg); ++i; r = &rest_arg.value.rectangle; if (! strcmp (keyword, ":crop-to")) { gdouble w = (cb.x2 - cb.x1); gdouble h = (cb.y2 - cb.y1); gdouble x1 = cb.x1; gdouble y1 = cb.y1; cb.x1 = r->x1 * w + x1; cb.x2 = r->x2 * w + x1; cb.y1 = r->y1 * h + y1; cb.y2 = r->y2 * h + y1; } else { r->x1 = pt_width * r->x1 * (cb.x2 - cb.x1) + pt_width * cb.x1; r->x2 = pt_width * r->x2 * (cb.x2 - cb.x1) + pt_width * cb.x1; r->y1 = pt_height * r->y1 * (cb.y2 - cb.y1) + pt_height * cb.y1; r->y2 = pt_height * r->y2 * (cb.y2 - cb.y1) + pt_height * cb.y1; if (! strcmp (keyword, ":highlight-region")) { const double deg = M_PI / 180.0; double rad; r->x1 += line_width / 2; r->x2 -= line_width / 2; r->y1 += line_width / 2; r->y2 -= line_width / 2; rad = MIN (5, MIN (r->x2 - r->x1, r->y2 - r->y1) / 2.0); cairo_move_to (cr, r->x1 , r->y1 + rad); cairo_arc (cr, r->x1 + rad, r->y1 + rad, rad, 180 * deg, 270 * deg); cairo_arc (cr, r->x2 - rad, r->y1 + rad, rad, 270 * deg, 360 * deg); cairo_arc (cr, r->x2 - rad, r->y2 - rad, rad, 0 * deg, 90 * deg); cairo_arc (cr, r->x1 + rad, r->y2 - rad, rad, 90 * deg, 180 * deg); cairo_close_path (cr); cairo_set_source_rgba (cr, bg.red / 65535.0, bg.green / 65535.0, bg.blue / 65535.0, alpha); cairo_fill_preserve (cr); cairo_set_source_rgba (cr, fg.red / 65535.0, fg.green / 65535.0, fg.blue / 65535.0, 1.0); cairo_set_line_width (cr, line_width); cairo_stroke (cr); } else { gboolean is_single_line = ! strcmp (keyword, ":highlight-line"); if (is_single_line) { gdouble m = r->y1 + (r->y2 - r->y1) / 2; /* Make the rectangle flat, otherwise poppler frequently renders neighboring lines.*/ r->y1 = m; r->y2 = m; } poppler_page_render_selection (page, cr, r, NULL, POPPLER_SELECTION_GLYPH, &fg, &bg); } } } else perror_if_not (0, "Unknown render command: %s", keyword); } if (cb.x1 != 0 || cb.y1 != 0 || cb.x2 != 1 || cb.y2 != 1) { int height = cairo_image_surface_get_height (surface); cairo_rectangle_int_t r = {(int) (width * cb.x1 + 0.5), (int) (height * cb.y1 + 0.5), (int) (width * (cb.x2 - cb.x1) + 0.5), (int) (height * (cb.y2 - cb.y1) + 0.5)}; cairo_surface_t *nsurface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, r.width, r.height); perror_if_not (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS, "%s", "Failed to create cairo surface"); cairo_destroy (cr); cr = cairo_create (nsurface); perror_if_not (cairo_status (cr) == CAIRO_STATUS_SUCCESS, "%s", "Failed to create cairo context"); cairo_set_source_surface (cr, surface, -r.x, -r.y); cairo_paint (cr); cairo_surface_destroy (surface); surface = nsurface; } theend: image_write_print_response (surface, PNG); error: if (error_msg) g_free (error_msg); if (cr) cairo_destroy (cr); if (surface) cairo_surface_destroy (surface); if (page) g_object_unref (page); } const command_arg_type_t cmd_boundingbox_spec[] = { ARG_DOC, ARG_NATNUM, /* page number */ /* region */ }; static void cmd_boundingbox (const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args[0].value.doc; int pn = args[1].value.natnum; PopplerPage *page = poppler_document_get_page(doc->pdf, pn - 1); cairo_surface_t *surface = NULL; int width, height; double pt_width, pt_height; unsigned char *data, *data_p; PopplerRectangle bbox; int i, j; perror_if_not (page, "No such page %d", pn); poppler_page_get_size (page, &pt_width, &pt_height); surface = image_render_page (doc->pdf, page, (int) pt_width, 1, &doc->options.render); perror_if_not (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS, "Failed to render page"); width = cairo_image_surface_get_width (surface); height = cairo_image_surface_get_height (surface); data = cairo_image_surface_get_data (surface); /* Determine the bbox by comparing each pixel in the 4 corner stripes with the origin. */ for (i = 0; i < width; ++i) { data_p = data + 4 * i; for (j = 0; j < height; ++j, data_p += 4 * width) { if (! ARGB_EQUAL (data, data_p)) break; } if (j < height) break; } bbox.x1 = i; for (i = width - 1; i > -1; --i) { data_p = data + 4 * i; for (j = 0; j < height; ++j, data_p += 4 * width) { if (! ARGB_EQUAL (data, data_p)) break; } if (j < height) break; } bbox.x2 = i + 1; for (i = 0; i < height; ++i) { data_p = data + 4 * i * width; for (j = 0; j < width; ++j, data_p += 4) { if (! ARGB_EQUAL (data, data_p)) break; } if (j < width) break; } bbox.y1 = i; for (i = height - 1; i > -1; --i) { data_p = data + 4 * i * width; for (j = 0; j < width; ++j, data_p += 4) { if (! ARGB_EQUAL (data, data_p)) break; } if (j < width) break; } bbox.y2 = i + 1; OK_BEGIN (); if (bbox.x1 >= bbox.x2 || bbox.y1 >= bbox.y2) { /* empty page */ puts ("0:0:1:1"); } else { printf ("%f:%f:%f:%f\n", bbox.x1 / width, bbox.y1 / height, bbox.x2 / width, bbox.y2 / height); } OK_END (); error: if (surface) cairo_surface_destroy (surface); if (page) g_object_unref (page); } const command_arg_type_t cmd_charlayout_spec[] = { ARG_DOC, ARG_NATNUM, /* page number */ ARG_EDGES_OR_POSITION, /* region or position */ }; static void cmd_charlayout(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; int pn = args[1].value.natnum; PopplerRectangle region = args[2].value.rectangle; double width, height; PopplerPage *page = poppler_document_get_page(doc, pn - 1); char *text = NULL; char *text_p; PopplerRectangle *rectangles = NULL; guint nrectangles; int i; gboolean have_position = region.y2 < 0; perror_if_not (page, "No such page %d", pn); text = poppler_page_get_text (page); text_p = text; poppler_page_get_text_layout (page, &rectangles, &nrectangles); poppler_page_get_size (page, &width, &height); region.x1 *= width; region.x2 *= width; region.y1 *= height; region.y2 *= height; OK_BEGIN (); for (i = 0; i < nrectangles && *text_p; ++i) { PopplerRectangle *r = &rectangles[i]; char *nextc = g_utf8_offset_to_pointer (text_p, 1); if ((have_position && region.x1 >= r->x1 && region.x1 <= r->x2 && region.y1 >= r->y1 && region.y1 <= r->y2) || (! have_position && r->x1 >= region.x1 && r->y1 >= region.y1 && r->x2 <= region.x2 && r->y2 <= region.y2)) { char endc = *nextc; printf ("%f %f %f %f:", r->x1 / width, r->y1 / height, r->x2 / width, r->y2 / height); *nextc = '\0'; print_response_string (text_p, NEWLINE); *nextc = endc; } text_p = nextc; } OK_END (); g_free (rectangles); g_object_unref (page); g_free (text); error: return; } const document_option_t document_options [] = { DEC_DOPT (":render/usecolors", ARG_BOOL, render.usecolors), DEC_DOPT (":render/printed", ARG_BOOL, render.printed), DEC_DOPT (":render/foreground", ARG_COLOR, render.fg), DEC_DOPT (":render/background", ARG_COLOR, render.bg), }; const command_arg_type_t cmd_getoptions_spec[] = { ARG_DOC, }; static void cmd_getoptions(const epdfinfo_t *ctx, const command_arg_t *args) { document_t *doc = args[0].value.doc; int i; OK_BEGIN (); for (i = 0; i < G_N_ELEMENTS (document_options); ++i) { command_arg_t arg; arg.type = document_options[i].type; memcpy (&arg.value, ((char*) &doc->options) + document_options[i].offset, command_arg_type_size (arg.type)); print_response_string (document_options[i].name, COLON); command_arg_print (&arg); puts(""); } OK_END (); } const command_arg_type_t cmd_setoptions_spec[] = { ARG_DOC, ARG_REST /* key value pairs */ }; static void cmd_setoptions(const epdfinfo_t *ctx, const command_arg_t *args) { int i = 0; document_t *doc = args[0].value.doc; int nrest = args[1].value.rest.nargs; char * const *rest = args[1].value.rest.args; gchar *error_msg = NULL; document_options_t opts = doc->options; const size_t nopts = G_N_ELEMENTS (document_options); perror_if_not (nrest % 2 == 0, "Even number of key/value pairs expected"); while (i < nrest) { int j; command_arg_t key, value; perror_if_not (command_arg_parse_arg (ctx, rest[i], &key, ARG_NONEMPTY_STRING, &error_msg), "%s", error_msg); ++i; for (j = 0; j < nopts; ++j) { const document_option_t *dopt = &document_options[j]; if (! strcmp (key.value.string, dopt->name)) { perror_if_not (command_arg_parse_arg (ctx, rest[i], &value, dopt->type, &error_msg), "%s", error_msg); memcpy (((char*) &opts) + dopt->offset, &value.value, command_arg_type_size (value.type)); break; } } perror_if_not (j < nopts, "Unknown option: %s", key.value.string); ++i; } doc->options = opts; cmd_getoptions (ctx, args); error: if (error_msg) g_free (error_msg); } const command_arg_type_t cmd_pagelabels_spec[] = { ARG_DOC, }; static void cmd_pagelabels(const epdfinfo_t *ctx, const command_arg_t *args) { PopplerDocument *doc = args[0].value.doc->pdf; int i; OK_BEGIN (); for (i = 0; i < poppler_document_get_n_pages (doc); ++i) { PopplerPage *page = poppler_document_get_page(doc, i); gchar *label = poppler_page_get_label (page); print_response_string (label ? label : "", NEWLINE); g_object_unref (page); g_free (label); } OK_END (); } const command_arg_type_t cmd_ping_spec[] = { ARG_STRING /* any message */ }; static void cmd_ping (const epdfinfo_t *ctx, const command_arg_t *args) { const gchar *msg = args[0].value.string; OK_BEGIN (); print_response_string (msg, NEWLINE); OK_END (); } /* ================================================================== * * Main * ================================================================== */ static const command_t commands [] = { /* Basic */ DEC_CMD (ping), DEC_CMD (features), DEC_CMD (open), DEC_CMD (close), DEC_CMD (quit), DEC_CMD (getoptions), DEC_CMD (setoptions), /* Searching */ DEC_CMD2 (search_string, "search-string"), DEC_CMD2 (search_regexp, "search-regexp"), DEC_CMD2 (regexp_flags, "regexp-flags"), /* General Information */ DEC_CMD (metadata), DEC_CMD (outline), DEC_CMD2 (number_of_pages, "number-of-pages"), DEC_CMD (pagelinks), DEC_CMD (gettext), DEC_CMD (getselection), DEC_CMD (pagesize), DEC_CMD (boundingbox), DEC_CMD (charlayout), /* General Information */ DEC_CMD (metadata), DEC_CMD (outline), DEC_CMD2 (number_of_pages, "number-of-pages"), DEC_CMD (pagelinks), DEC_CMD (gettext), DEC_CMD (getselection), DEC_CMD (pagesize), DEC_CMD (boundingbox), DEC_CMD (charlayout), DEC_CMD (pagelabels), /* Annotations */ DEC_CMD (getannots), DEC_CMD (getannot), #ifdef HAVE_POPPLER_ANNOT_WRITE DEC_CMD (addannot), DEC_CMD (delannot), DEC_CMD (editannot), DEC_CMD (save), #endif /* Attachments */ DEC_CMD2 (getattachment_from_annot, "getattachment-from-annot"), DEC_CMD (getattachments), /* Synctex */ DEC_CMD2 (synctex_forward_search, "synctex-forward-search"), DEC_CMD2 (synctex_backward_search, "synctex-backward-search"), /* Rendering */ DEC_CMD (renderpage), }; int main(int argc, char **argv) { epdfinfo_t ctx = {0}; char *line = NULL; ssize_t read; size_t line_size; const char *error_log = "/dev/null"; #ifdef __MINGW32__ error_log = "NUL"; _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); #endif if (argc > 2) { fprintf(stderr, "usage: epdfinfo [ERROR-LOGFILE]\n"); exit (EXIT_FAILURE); } if (argc == 2) error_log = argv[1]; if (! freopen (error_log, "a", stderr)) err (2, "Unable to redirect stderr"); #if ! GLIB_CHECK_VERSION(2,36,0) g_type_init (); #endif ctx.documents = g_hash_table_new (g_str_hash, g_str_equal); setvbuf (stdout, NULL, _IOFBF, BUFSIZ); while ((read = getline (&line, &line_size, stdin)) != -1) { int nargs = 0; command_arg_t *cmd_args = NULL; char **args = NULL; gchar *error_msg = NULL; int i; if (read <= 1 || line[read - 1] != '\n') { fprintf (stderr, "Skipped parts of a line: `%s'\n", line); goto next_line; } line[read - 1] = '\0'; args = command_arg_split (line, &nargs); if (nargs == 0) continue; for (i = 0; i < G_N_ELEMENTS (commands); i++) { if (! strcmp (commands[i].name, args[0])) { if (commands[i].nargs == 0 || (cmd_args = command_arg_parse (&ctx, args + 1, nargs - 1, commands + i, &error_msg))) { commands[i].execute (&ctx, cmd_args); if (commands[i].nargs > 0) free_command_args (cmd_args, commands[i].nargs); } else { printf_error_response ("%s", error_msg ? error_msg : "Unknown error (this is a bug)"); } if (error_msg) g_free (error_msg); break; } } if (G_N_ELEMENTS (commands) == i) { printf_error_response ("Unknown command: %s", args[0]); } for (i = 0; i < nargs; ++i) g_free (args[i]); g_free (args); next_line: free (line); line = NULL; } if (ferror (stdin)) err (2, NULL); exit (EXIT_SUCCESS); }