Codebase list foomatic-filters / debian/4.0-20090308-1 pdf.c
debian/4.0-20090308-1

Tree @debian/4.0-20090308-1 (Download .tar.gz)

pdf.c @debian/4.0-20090308-1raw · history · blame

#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;
}