#include "foomaticrip.h"
#include "util.h"
#include "options.h"
#include "process.h"
#include "renderer.h"
#include <stdlib.h>
#include <ctype.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <ghostscript/iapi.h>
#include <ghostscript/ierrors.h>
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
const char *pagecountcode =
"/pdffile (%s) (r) file def\n"
"pdfdict begin\n"
"pdffile pdfopen begin\n"
"(PageCount: ) print\n"
"pdfpagecount == flush\n" /* 'flush' makes sure that gs_stdout is called
before gsapi_run_string returns */
"currentdict pdfclose\n"
"end end\n";
char gsout [256];
static int wait_for_renderer();
static const char * temp_dir()
{
static const char *tmpdir = NULL;
if (!tmpdir)
{
const char *dirs[] = { getenv("TMPDIR"), P_tmpdir, "/tmp", NULL };
const char **dir;
for (dir = dirs; *dir; dir++)
if (access(*dir, W_OK) == 0) {
tmpdir = *dir;
break;
}
if (tmpdir)
{
_log("Storing temporary files in %s\n", tmpdir);
setenv("TMPDIR", tmpdir, 1); /* for child processes */
}
else
rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS,
"Cannot find a writable temp dir.");
}
return tmpdir;
}
int gs_stdout(void *instance, const char *str, int len)
{
int last;
if (isempty(gsout)) {
last = len < 256 ? len : 255;
strncpy(gsout, str, last +1);
gsout[last] = '\0';
}
return len; /* ignore everything after the first few chars */
}
int gs_stderr(void *instance, const char *str, int len)
{
char *buf = malloc(len +1);
strncpy(buf, str, len);
buf[len] = '\0';
_log("Ghostscript: %s", buf);
free(buf);
return len;
}
static int pdf_count_pages(const char *filename)
{
void *minst;
int gsargc = 3;
char *gsargv[] = { "", "-dNODISPLAY", "-q" };
int pagecount;
int exit_code;
char code[2048];
if (gsapi_new_instance(&minst, NULL) < 0) {
_log("Could not create ghostscript instance\n");
return -1;
}
gsapi_set_stdio(minst, NULL, gs_stdout, gs_stderr);
if (gsapi_init_with_args(minst, gsargc, gsargv) < 0) {
_log("Could not init ghostscript\n");
gsapi_exit(minst);
gsapi_delete_instance(minst);
return -1;
}
snprintf(code, 2048, pagecountcode, filename);
if (gsapi_run_string(minst, code, 0, &exit_code) == 0) {
if (sscanf(gsout, "PageCount: %d", &pagecount) < 1)
pagecount = -1;
}
gsapi_exit(minst);
gsapi_delete_instance(minst);
return pagecount;
}
pid_t kid3 = 0;
static int start_renderer(const char *cmd)
{
if (kid3 != 0)
wait_for_renderer();
_log("Starting renderer with command: %s\n", cmd);
kid3 = start_process("kid3", exec_kid3, (void *)cmd, NULL, NULL);
if (kid3 < 0)
return 0;
return 1;
}
static int wait_for_renderer()
{
int status;
waitpid(kid3, &status, 0);
if (!WIFEXITED(status)) {
_log("Kid3 did not finish normally.\n");
exit(EXIT_PRNERR_NORETRY_BAD_SETTINGS);
}
_log("Kid3 exit status: %d\n", WEXITSTATUS(status));
if (WEXITSTATUS(status) != 0)
exit(EXIT_PRNERR_NORETRY_BAD_SETTINGS);
kid3 = 0;
return 1;
}
/*
* Extract pages 'first' through 'last' from the pdf and write them into a
* temporary file.
*/
static int pdf_extract_pages(char filename[PATH_MAX],
const char *pdffilename,
int first,
int last)
{
void *minst;
char filename_arg[PATH_MAX], first_arg[50], last_arg[50];
const char *gs_args[] = { "", "-q", "-dNOPAUSE", "-dBATCH",
"-dPARANOIDSAFER", "-sDEVICE=pdfwrite", filename_arg, first_arg,
last_arg, pdffilename };
_log("Extracting pages %d through %d\n", first, last);
snprintf(filename, PATH_MAX, "%s/foomatic-XXXXXX", temp_dir());
mktemp(filename);
if (!filename[0])
return 0;
if (gsapi_new_instance(&minst, NULL) < 0)
{
_log("Could not create ghostscript instance\n");
return 0;
}
snprintf(filename_arg, PATH_MAX, "-sOutputFile=%s", filename);
snprintf(first_arg, 50, "-dFirstPage=%d", first);
if (last > 0)
snprintf(last_arg, 50, "-dLastPage=%d", last);
else
first_arg[0] = '\0';
gsapi_set_stdio(minst, NULL, NULL, gs_stderr);
if (gsapi_init_with_args(minst, ARRAY_LEN(gs_args), (char **)gs_args) < 0)
{
_log("Could not run ghostscript to extract the pages\n");
gsapi_exit(minst);
gsapi_delete_instance(minst);
return 0;
}
gsapi_exit(minst);
gsapi_delete_instance(minst);
return 1;
}
static int render_pages_with_generic_command(dstr_t *cmd,
const char *filename,
int firstpage,
int lastpage)
{
char tmpfile[PATH_MAX];
int result;
/* TODO it might be a good idea to give pdf command lines the possibility
* to get the file on the command line rather than piped through stdin
* (maybe introduce a &filename; ??) */
if (lastpage < 0) /* i.e. print the whole document */
dstrcatf(cmd, " < %s", filename);
else
{
if (!pdf_extract_pages(tmpfile, filename, firstpage, lastpage))
return 0;
dstrcatf(cmd, " < %s", tmpfile);
}
result = start_renderer(cmd->data);
if (lastpage > 0)
unlink(tmpfile);
return result;
}
static int render_pages_with_ghostscript(dstr_t *cmd,
size_t start_gs_cmd,
size_t end_gs_cmd,
const char *filename,
int firstpage,
int lastpage)
{
char *p;
/* No need to create a temporary file, just give ghostscript the file and
* first/last page on the command line */
/* Some command lines want to read from stdin */
for (p = &cmd->data[end_gs_cmd -1]; isspace(*p); p--)
;
if (*p == '-')
*p = ' ';
dstrinsertf(cmd, end_gs_cmd, " %s ", filename);
if (lastpage > 0)
dstrinsertf(cmd, start_gs_cmd +2,
" -dFirstPage=%d -dLastPage=%d ",
firstpage, lastpage);
else
dstrinsertf(cmd, start_gs_cmd +2,
" -dFirstPage=%d ", firstpage);
return start_renderer(cmd->data);
}
static int render_pages(const char *filename, int firstpage, int lastpage)
{
dstr_t *cmd = create_dstr();
size_t start, end;
int result;
build_commandline(optionset("currentpage"), cmd, 1);
extract_command(&start, &end, cmd->data, "gs");
if (start == end)
/* command is not Ghostscript */
result = render_pages_with_generic_command(cmd,
filename,
firstpage,
lastpage);
else
/* Ghostscript command, tell it which pages we want to render */
result = render_pages_with_ghostscript(cmd,
start,
end,
filename,
firstpage,
lastpage);
free_dstr(cmd);
return result;
}
static int print_pdf_file(const char *filename)
{
int page_count, i;
int firstpage;
page_count = pdf_count_pages(filename);
if (page_count <= 0)
return 0;
_log("File contains %d pages\n", page_count);
optionset_copy_values(optionset("header"), optionset("currentpage"));
optionset_copy_values(optionset("currentpage"), optionset("previouspage"));
firstpage = 1;
for (i = 1; i <= page_count; i++)
{
set_options_for_page(optionset("currentpage"), i);
if (!optionset_equal(optionset("currentpage"), optionset("previouspage"), 1))
{
render_pages(filename, firstpage, i);
firstpage = i;
}
optionset_copy_values(optionset("currentpage"), optionset("previouspage"));
}
if (firstpage == 1)
render_pages(filename, 1, -1); /* Render the whole document */
else
render_pages(filename, firstpage, page_count);
wait_for_renderer();
return 1;
}
int print_pdf(FILE *s,
const char *alreadyread,
size_t len,
const char *filename,
size_t startpos)
{
char tmpfilename[PATH_MAX] = "";
int result;
/* If reading from stdin, write everything into a temporary file */
/* TODO don't do this if there aren't any pagerange-limited options */
if (s == stdin)
{
int fd;
FILE *tmpfile;
snprintf(tmpfilename, PATH_MAX, "%s/foomatic-XXXXXX", temp_dir());
fd = mkstemp(tmpfilename);
if (fd < 0) {
_log("Could not create temporary file: %s\n", strerror(errno));
return EXIT_PRNERR_NORETRY_BAD_SETTINGS;
}
tmpfile = fdopen(fd, "r+");
copy_file(tmpfile, stdin, alreadyread, len);
fclose(tmpfile);
filename = tmpfilename;
}
result = print_pdf_file(filename);
if (!isempty(tmpfilename))
unlink(tmpfilename);
return result;
}