3722 lines
98 KiB
C
3722 lines
98 KiB
C
/* Copyright (C) 2013, 2014 Andreas Politz
|
||
*
|
||
* Author: Andreas Politz <politza@fh-trier.de>
|
||
*
|
||
* 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 <http://www.gnu.org/licenses/>. */
|
||
|
||
#include <config.h>
|
||
|
||
#include <assert.h>
|
||
#ifdef HAVE_ERR_H
|
||
# include <err.h>
|
||
#endif
|
||
#ifdef HAVE_ERROR_H
|
||
# include <error.h>
|
||
#endif
|
||
#include <glib.h>
|
||
#include <poppler.h>
|
||
#include <cairo.h>
|
||
#include <stdarg.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <strings.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#include <fcntl.h>
|
||
#include <errno.h>
|
||
#include <png.h>
|
||
#include <math.h>
|
||
#include <regex.h>
|
||
#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);
|
||
}
|