diff options
Diffstat (limited to 'postscript.c')
-rw-r--r-- | postscript.c | 1288 |
1 files changed, 1288 insertions, 0 deletions
diff --git a/postscript.c b/postscript.c new file mode 100644 index 0000000..b82ba9a --- /dev/null +++ b/postscript.c @@ -0,0 +1,1288 @@ + +#include "foomaticrip.h" +#include "util.h" +#include "options.h" +#include "fileconverter.h" +#include "renderer.h" +#include "process.h" + +#include <errno.h> +#include <unistd.h> +#include <ctype.h> +#include <stdlib.h> + +void get_renderer_handle(const dstr_t *prepend, FILE **fd, pid_t *pid); +int close_renderer_handle(FILE *rendererhandle, pid_t rendererpid); + +#define LT_BEGIN_FEATURE 1 +#define LT_FOOMATIC_RIP_OPTION_SETTING 2 +int line_type(const char *line) +{ + const char *p; + if (startswith(line, "%%BeginFeature:")) + return LT_BEGIN_FEATURE; + p = line; + while (*p && isspace(*p)) p++; + if (!startswith(p, "%%")) + return 0; + p += 2; + while (*p && isspace(*p)) p++; + if (startswith(p, "FoomaticRIPOptionSetting:")) + return LT_FOOMATIC_RIP_OPTION_SETTING; + return 0; +} + + +/* Next, examine the PostScript job for traces of command-line and + JCL options. PPD-aware applications and spoolers stuff option + settings directly into the file, they do not necessarily send + PPD options by the command line. Also stuff in PostScript code + to apply option settings given by the command line and to set + the defaults given in the PPD file. + + Examination strategy: read lines from STDIN until the first + %%Page: comment appears and save them as @psheader. This is the + page-independent header part of the PostScript file. The + PostScript interpreter (renderer) must execute this part once + before rendering any assortment of pages. Then pages can be + printed in any arbitrary selection or order. All option + settings we find here will be collected in the default option + set for the RIP command line. + + Now the pages will be read and sent to the renderer, one after + the other. Every page is read into memory until the + %%EndPageSetup comment appears (or a certain amount of lines was + read). So we can get option settings only valid for this + page. If we have such settings we set them in the modified + command set for this page. + + If the renderer is not running yet (first page) we start it with + the command line built from the current modified command set and + send the first page to it, in the end we leave the renderer + running and keep input and output pipes open, so that it can + accept further pages. If the renderer is still running from + the previous page and the current modified command set is the + same as the one for the previous page, we send the page. If + the command set is different, we close the renderer, re-start + it with the command line built from the new modified command + set, send the header again, and then the page. + + After the last page the trailer (%%Trailer) is sent. + + The output pipe of this program stays open all the time so that + the spooler does not assume that the job has finished when the + renderer is re-started. + + Non DSC-conforming documents will be read until a certain line + number is reached. Command line or JCL options inserted later + will be ignored. + + If options are implemented by PostScript code supposed to be + stuffed into the job's PostScript data we stuff the code for all + these options into our job data, So all default settings made in + the PPD file (the user can have edited the PPD file to change + them) are taken care of and command line options get also + applied. To give priority to settings made by applications we + insert the options's code in the beginnings of their respective + sections, so that sommething, which is already inserted, gets + executed after our code. Missing sections are automatically + created. In non-DSC-conforming files we insert the option code + in the beginning of the file. This is the same policy as used by + the "pstops" filter of CUPS. + + If CUPS is the spooler, the option settings were already + inserted by the "pstops" filter, so we don't insert them + again. The only thing we do is correcting settings of numerical + options when they were set to a value not available as choice in + the PPD file, As "pstops" does not support "real" numerical + options, it sees these settings as an invalid choice and stays + with the default setting. In this case we correct the setting in + the first occurence of the option's code, as this one is the one + added by CUPS, later occurences come from applications and + should not be touched. + + If the input is not PostScript (if there is no "%!" after + $maxlinestopsstart lines) a file conversion filter will + automatically be applied to the incoming data, so that we will + process the resulting PostScript here. This way we have always + PostScript data here and so we can apply the printer/driver + features described in the PPD file. + + Supported file conversion filters are "a2ps", "enscript", + "mpage", and spooler-specific filters. All filters convert + plain text to PostScript, "a2ps" also other formats. The + conversion filter is always used when one prints the + documentation pages, as they are created as plain text, + when CUPS is the spooler "pstops" is executed after the + filter so that the default option settings from the PPD file + and CUPS-specific options as N-up get applied. On regular + printouts one gets always PostScript when CUPS or PPR is + the spooler, so the filter is only used for regular + printouts under LPD, LPRng, GNUlpr or without spooler. +*/ + +/* PostScript sections */ +#define PS_SECTION_JCLSETUP 1 +#define PS_SECTION_PROLOG 2 +#define PS_SECTION_SETUP 3 +#define PS_SECTION_PAGESETUP 4 + +#define MAX_NON_DSC_LINES_IN_HEADER 1000 +#define MAX_LINES_FOR_PAGE_OPTIONS 200 + +typedef struct { + size_t pos; + + FILE *file; + const char *alreadyread; + size_t len; +} stream_t; + +void _print_ps(stream_t *stream); + +int stream_next_line(dstr_t *line, stream_t *s) +{ + int c; + size_t cnt = 0; + + dstrclear(line); + while (s->pos < s->len) { + c = s->alreadyread[s->pos++]; + dstrputc(line, c); + cnt++; + if (c == '\n') + return cnt; + } + + while ((c = fgetc(s->file)) != EOF) { + dstrputc(line, c); + cnt++; + if (c == '\n') + return cnt; + } + return cnt; +} + +int print_ps(FILE *file, const char *alreadyread, size_t len, const char *filename) +{ + stream_t stream; + + if (file != stdin && (dup2(fileno(file), fileno(stdin)) < 0)) { + _log("Could not dup %s to stdin.\n", filename); + return 0; + } + + stream.pos = 0; + stream.file = stdin; + stream.alreadyread = alreadyread; + stream.len = len; + _print_ps(&stream); + return 1; +} + +void _print_ps(stream_t *stream) +{ + char *p; + + int maxlines = 1000; /* Maximum number of lines to be read when the + documenent is not DSC-conforming. + "$maxlines = 0" means that all will be read and + examined. If it is discovered that the input + file is DSC-conforming, this will be set to 0. */ + + int maxlinestopsstart = 200; /* That many lines are allowed until the + "%!" indicating PS comes. These + additional lines in the + beginning are usually JCL + commands. The lines will be + ignored by our parsing but + passed through. */ + + int printprevpage = 0; /* We set this when encountering "%%Page:" and the + previous page is not printed yet. Then it will + be printed and the new page will be prepared in + the next run of the loop (we don't read a new + line and don't increase the $linect then). */ + + int linect = 0; /* how many lines have we examined */ + int nonpslines = 0; /* lines before "%!" found yet. */ + int more_stuff = 1; /* there is more stuff in stdin */ + int saved = 0; /* DSC line not precessed yet */ + int isdscjob = 0; /* is the job dsc conforming */ + int inheader = 1; /* Are we still in the header, before first + "%%Page:" comment= */ + + int optionsalsointoheader = 0; /* 1: We are in a "%%BeginSetup... + %%EndSetup" section after the first + "%%Page:..." line (OpenOffice.org + does this and intends the options here + apply to the whole document and not + only to the current page). We have to + add all lines also to the end of the + @psheader now and we have to set + non-PostScript options also in the + "header" optionset. 0: otherwise. */ + + int insertoptions = 1; /* If we find out that a file with a DSC magic + string ("%!PS-Adobe-") is not really DSC- + conforming, we insert the options directly + after the line with the magic string. We use + this variable to store the number of the line + with the magic string */ + + int prologfound = 0; /* Did we find the + "%%BeginProlog...%%EndProlog" section? */ + int setupfound = 0; /* Did we find the + %%BeginSetup...%%EndSetup" section? */ + int pagesetupfound = 0; /* special page setup handling needed */ + + int inprolog = 0; /* We are between "%%BeginProlog" and "%%EndProlog" */ + int insetup = 0; /* We are between "%%BeginSetup" and "%%EndSetup" */ + int infeature = 0; /* We are between "%%BeginFeature" and "%%EndFeature" */ + + int optionreplaced = 0; /* Will be set to 1 when we are in an + option ("%%BeginFeature... + %%EndFeature") which we have replaced. */ + + int postscriptsection = PS_SECTION_JCLSETUP; /* In which section of the PostScript file + are we currently ? */ + + int nondsclines = 0; /* Number of subsequent lines found which are at a + non-DSC-conforming place, between the sections + of the header.*/ + + int nestinglevel = 0; /* Are we in the main document (0) or in an + embedded document bracketed by "%%BeginDocument" + and "%%EndDocument" (>0) We do not parse the + PostScript in an embedded document. */ + + int inpageheader = 0; /* Are we in the header of a page, + between "%%BeginPageSetup" and + "%%EndPageSetup" (1) or not (0). */ + + int passthru = 0; /* 0: write data into psfifo, + 1: pass data directly to the renderer */ + + int lastpassthru = 0; /* State of 'passthru' in previous line + (to allow debug output when $passthru + switches. */ + + int ignorepageheader = 0; /* Will be set to 1 as soon as active + code (not between "%%BeginPageSetup" + and "%%EndPageSetup") appears after a + "%%Page:" comment. In this case + "%%BeginPageSetup" and + "%%EndPageSetup" is not allowed any + more on this page and will be ignored. + Will be set to 0 when a new "%%Page:" + comment appears. */ + + int optset = optionset("header"); /* Where do the option settings which + we have found go? */ + + /* current line */ + dstr_t *line = create_dstr(); + + dstr_t *onelinebefore = create_dstr(); + dstr_t *twolinesbefore = create_dstr(); + + /* The header of the PostScript file, to be send after each start of the renderer */ + dstr_t *psheader = create_dstr(); + + /* The input FIFO, data which we have pulled from stdin for examination, + but not send to the renderer yet */ + dstr_t *psfifo = create_dstr(); + + FILE *fileconverter_handle = NULL; /* File handle to converter process */ + pid_t fileconverter_pid = 0; /* PID of the fileconverter process */ + + int ignoreline; + + int ooo110 = 0; /* Flag to work around an application bug */ + + int currentpage = 0; /* The page which we are currently printing */ + + option_t *o; + const char *val; + + int linetype; + + dstr_t *linesafterlastbeginfeature = create_dstr(); /* All codelines after the last "%%BeginFeature" */ + + char optionname [128]; + char value [128]; + int fromcomposite = 0; + + dstr_t *pdest; + + double width, height; + + pid_t rendererpid = 0; + FILE *rendererhandle = NULL; + + int retval; + + dstr_t *tmp = create_dstr(); + jobhasjcl = 0; + + /* We do not parse the PostScript to find Foomatic options, we check + only whether we have PostScript. */ + if (dontparse) + maxlines = 1; + + _log("Reading PostScript input ...\n"); + + do { + ignoreline = 0; + + if (printprevpage || saved || stream_next_line(line, stream)) { + saved = 0; + if (linect == nonpslines) { + /* In the beginning should be the postscript leader, + sometimes after some JCL commands */ + if ( !(line->data[0] == '%' && line->data[1] == '!') && + !(line->data[1] == '%' && line->data[2] == '!')) /* There can be a Windows control character before "%!" */ + { + nonpslines++; + if (maxlines == nonpslines) + maxlines ++; + jobhasjcl = 1; + + if (nonpslines > maxlinestopsstart) { + /* This is not a PostScript job, we must convert it */ + _log("Job does not start with \"%%!\", is it Postscript?\n" + "Starting file converter\n"); + + /* Reset all variables but conserve the data which we have already read */ + jobhasjcl = 0; + linect = 0; + nonpslines = 1; /* Take into account that the line of this run of the loop + will be put into @psheader, so the first line read by + the file converter is already the second line */ + maxlines = 1001; + + dstrclear(onelinebefore); + dstrclear(twolinesbefore); + + dstrcpyf(tmp, "%s%s%s", psheader, psfifo, line); + dstrclear(psheader); + dstrclear(psfifo); + dstrclear(line); + + /* Start the file conversion filter */ + if (!fileconverter_pid) + get_fileconverter_handle(tmp->data, &fileconverter_handle, &fileconverter_pid); + else + rip_die(EXIT_JOBERR, "File conversion filter probably crashed\n"); + + /* Read the further data from the file converter and not from STDIN */ + if (close(fileno(stdin)) == -1 && errno != ESPIPE) + rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Couldn't close STDIN\n"); + if (dup2(fileno(stdin), fileno(fileconverter_handle)) == -1) + rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Couldn't dup fileconverter_handle\n"); + } + } + else { + /* Do we have a DSC-conforming document? */ + if ((line->data[0] == '%' && startswith(line->data, "%!PS-Adobe-")) || + (line->data[1] == '%' && startswith(line->data, "%!PS-Adobe-"))) + { + /* Do not stop parsing the document */ + if (!dontparse) { + maxlines = 0; + isdscjob = 1; + insertoptions = linect + 1; + /* We have written into psfifo before, now we continue in + psheader and move over the data which is already in psfifo */ + dstrcat(psheader, psfifo->data); + dstrclear(psfifo); + } + _log("--> This document is DSC-conforming!\n"); + } + else { + /* Job is not DSC-conforming, stick in all PostScript + option settings in the beginning */ + append_prolog_section(line, optset, 1); + append_setup_section(line, optset, 1); + append_page_setup_section(line, optset, 1); + prologfound = 1; + setupfound = 1; + pagesetupfound = 1; + } + } + } + else { + if (startswith(line->data, "%")) { + if (startswith(line->data, "%%BeginDocument")) { + /* Beginning of an embedded document + Note that Adobe Acrobat has a bug and so uses + "%%BeginDocument " instead of "%%BeginDocument:" */ + nestinglevel++; + _log("Embedded document, nesting level now: %d\n", nestinglevel); + } + else if (nestinglevel > 0 && startswith(line->data, "%%EndDocument")) { + /* End of an embedded document */ + nestinglevel--; + _log("End of embedded document, nesting level now: %d\n", nestinglevel); + } + else if (nestinglevel == 0 && startswith(line->data, "%%Creator")) { + /* Here we set flags to treat particular bugs of the + PostScript produced by certain applications */ + p = strstr(line->data, "%%Creator") + 9; + while (*p && (isspace(*p) || *p == ':')) p++; + if (!strcmp(p, "OpenOffice.org")) { + p += 14; + while (*p && isspace(*p)) p++; + if (sscanf(p, "1.1.%d", &ooo110) == 1) { + _log("Document created with OpenOffice.org 1.1.x\n"); + ooo110 = 1; + } + } else if (!strcmp(p, "StarOffice 8")) { + p += 12; + _log("Document created with StarOffice 8\n"); + ooo110 = 1; + } + } + else if (nestinglevel == 0 && startswith(line->data, "%%BeginProlog")) { + /* Note: Below is another place where a "Prolog" section + start will be considered. There we assume start of the + "Prolog" if the job is DSC-Conformimg, but an arbitrary + comment starting with "%%Begin", but not a comment + explicitly treated here, is found. This is done because + many "dvips" (TeX/LaTeX) files miss the "%%BeginProlog" + comment. + Beginning of Prolog */ + _log("\n-----------\nFound: %%%%BeginProlog\n"); + inprolog = 1; + if (inheader) + postscriptsection = PS_SECTION_PROLOG; + nondsclines = 0; + /* Insert options for "Prolog" */ + if (!prologfound) { + append_prolog_section(line, optset, 0); + prologfound = 1; + } + } + else if (nestinglevel == 0 && startswith(line->data, "%%EndProlog")) { + /* End of Prolog */ + _log("Found: %%%%EndProlog\n"); + inprolog = 0; + insertoptions = linect +1; + } + else if (nestinglevel == 0 && startswith(line->data, "%%BeginSetup")) { + /* Beginning of Setup */ + _log("\n-----------\nFound: %%%%BeginSetup\n"); + insetup = 1; + nondsclines = 0; + /* We need to distinguish with the $inheader variable + here whether we are in the header or on a page, as + OpenOffice.org inserts a "%%BeginSetup...%%EndSetup" + section after the first "%%Page:..." line and assumes + this section to be valid for all pages. */ + if (inheader) { + postscriptsection = PS_SECTION_SETUP; + /* If there was no "Prolog" but there are + options for the "Prolog", push a "Prolog" + with these options onto the psfifo here */ + if (!prologfound) { + dstrclear(tmp); + append_prolog_section(tmp, optset, 1); + dstrprepend(line, tmp->data); + prologfound = 1; + } + /* Insert options for "DocumentSetup" or "AnySetup" */ + if (spooler != SPOOLER_CUPS && !setupfound) { + /* For non-CUPS spoolers or no spooler at all, + we leave everythnig as it is */ + append_setup_section(line, optset, 0); + setupfound = 1; + } + } + else { + /* Found option settings must be stuffed into both + the header and the currrent page now. They will + be written into both the "header" and the + "currentpage" optionsets and the PostScript code + lines of this section will not only go into the + output stream, but also added to the end of the + @psheader, so that they get repeated (to preserve + the embedded PostScript option settings) on a + restart of the renderer due to command line + option changes */ + optionsalsointoheader = 1; + _log("\"%%%%BeginSetup\" in page header\n"); + } + } + else if (nestinglevel == 0 && startswith(line->data, "%%EndSetup")) { + /* End of Setup */ + _log("Found: %%%%EndSetup\n"); + insetup = 0; + if (inheader) { + if (spooler == SPOOLER_CUPS) { + /* In case of CUPS, we must insert the + accounting stuff just before the + %%EndSetup comment in order to leave any + EndPage procedures that have been + defined by either the pstops filter or + the PostScript job itself fully + functional. */ + if (!setupfound) { + dstrclear(tmp); + append_setup_section(tmp, optset, 0); + dstrprepend(line, tmp->data); + setupfound = 1; + } + } + insertoptions = linect +1; + } + else { + /* The "%%BeginSetup...%%EndSetup" which + OpenOffice.org has inserted after the first + "%%Page:..." line ends here, so the following + options go only onto the current page again */ + optionsalsointoheader = 0; + } + } + else if (nestinglevel == 0 && startswith(line->data, "%%Page:")) { + if (!lastpassthru && !inheader) { + /* In the last line we were not in passthru mode, + so the last page is not printed. Prepare to do + it now. */ + printprevpage = 1; + passthru = 1; + _log("New page found but previous not printed, print it now.\n"); + } + else { + /* the previous page is printed, so we can prepare + the current one */ + _log("\n-----------\nNew page: %s", line->data); + printprevpage = 0; + currentpage++; + /* We consider the beginning of the page already as + page setup section, as some apps do not use + "%%PageSetup" tags. */ + postscriptsection = PS_SECTION_PAGESETUP; + + /* TODO can this be removed? + Save PostScript state before beginning the page + $line .= "/foomatic-saved-state save def\n"; */ + + /* Here begins a new page */ + if (inheader) { + build_commandline(optset, NULL, 0); + /* Here we add some stuff which still + belongs into the header */ + dstrclear(tmp); + + /* If there was no "Setup" but there are + options for the "Setup", push a "Setup" + with these options onto the @psfifo here */ + if (!setupfound) { + append_setup_section(tmp, optset, 1); + setupfound = 1; + } + /* If there was no "Prolog" but there are + options for the "Prolog", push a "Prolog" + with these options onto the @psfifo here */ + if (!prologfound) { + append_prolog_section(tmp, optset, 1); + prologfound = 1; + } + /* Now we push this into the header */ + dstrcat(psheader, tmp->data); + + /* The first page starts, so header ends */ + inheader = 0; + nondsclines = 0; + /* Option setting should go into the page + specific option set now */ + optset = optionset("currentpage"); + } + else { + /* Restore PostScript state after completing the + previous page: + + foomatic-saved-state restore + %%Page: ... + /foomatic-saved-state save def + + Print this directly, so that if we need to + restart the renderer for this page due to + a command line change this is done under the + old instance of the renderer + rint $rendererhandle + "foomatic-saved-state restore\n"; */ + + /* Save the option settings of the previous page */ + optionset_copy_values(optionset("currentpage"), optionset("previouspage")); + optionset_delete_values(optionset("currentpage")); + } + /* Initialize the option set */ + optionset_copy_values(optionset("header"), optionset("currentpage")); + + /* Set the command line options which apply only + to given pages */ + set_options_for_page(optionset("currentpage"), currentpage); + pagesetupfound = 0; + if (spooler == SPOOLER_CUPS) { + /* Remove the "notfirst" flag from all options + forseen for the "PageSetup" section, because + when these are numerical options for CUPS. + they have to be set to the correct value + for every page */ + for (o = optionlist; o; o = o->next) { + if (option_get_section(o ) == SECTION_PAGESETUP) + o->notfirst = 0; + } + } + /* Now the page header comes, so buffer the data, + because we must perhaps shut down and restart + the renderer */ + passthru = 0; + ignorepageheader = 0; + optionsalsointoheader = 0; + } + } + else if (nestinglevel == 0 && !ignorepageheader && + startswith(line->data, "%%BeginPageSetup")) { + /* Start of the page header, up to %%EndPageSetup + nothing of the page will be drawn, page-specific + option settngs (as letter-head paper for page 1) + go here*/ + _log("\nFound: %%%%BeginPageSetup\n"); + passthru = 0; + inpageheader = 1; + postscriptsection = PS_SECTION_PAGESETUP; + optionsalsointoheader = (ooo110 && currentpage == 1) ? 1 : 0; + /* Insert PostScript option settings + (options for section "PageSetup") */ + if (isdscjob) { + append_page_setup_section(line, optset, 0); + pagesetupfound = 1; + } + } + else if (nestinglevel == 0 && !ignorepageheader && + startswith(line->data, "%%BeginPageSetup")) { + /* End of the page header, the page is ready to be printed */ + _log("Found: %%%%EndPageSetup\n"); + _log("End of page header\n"); + /* We cannot for sure say that the page header ends here + OpenOffice.org puts (due to a bug) a "%%BeginSetup... + %%EndSetup" section after the first "%%Page:...". It + is possible that CUPS inserts a "%%BeginPageSetup... + %%EndPageSetup" before this section, which means that + the options in the "%%BeginSetup...%%EndSetup" + section are after the "%%EndPageSetup", so we + continue for searching options up to the buffer size + limit $maxlinesforpageoptions. */ + passthru = 0; + inpageheader = 0; + optionsalsointoheader = 0; + } + else if (nestinglevel == 0 && !optionreplaced && (!passthru || !isdscjob) && + ((linetype = line_type(line->data)) && + (linetype == LT_BEGIN_FEATURE || linetype == LT_FOOMATIC_RIP_OPTION_SETTING))) { + + /* parse */ + if (linetype == LT_BEGIN_FEATURE) { + dstrcpy(tmp, line->data); + p = strtok(tmp->data, " \t"); /* %%BeginFeature: */ + p = strtok(NULL, " \t="); /* Option */ + if (*p == '*') p++; + strlcpy(optionname, p, 128); + p = strtok(NULL, " \t\r\n"); /* value */ + fromcomposite = 0; + strlcpy(value, p, 128); + } + else { /* LT_FOOMATIC_RIP_OPTION_SETTING */ + dstrcpy(tmp, line->data); + p = strstr(tmp->data, "FoomaticRIPOptionSetting:"); + p = strtok(p, " \t"); /* FoomaticRIPOptionSetting */ + p = strtok(NULL, " \t="); /* Option */ + strlcpy(optionname, p, 128); + p = strtok(NULL, " \t\r\n"); /* value */ + if (*p == '@') { /* fromcomposite */ + p++; + fromcomposite = 1; + } + else + fromcomposite = 0; + strlcpy(value, p, 128); + } + + /* Mark that we are in a "Feature" section */ + if (linetype == LT_BEGIN_FEATURE) { + infeature = 1; + dstrclear(linesafterlastbeginfeature); + } + + /* OK, we have an option. If it's not a + Postscript-style option (ie, it's command-line or + JCL) then we should note that fact, since the + attribute-to-filter option passing in CUPS is kind of + funky, especially wrt boolean options. */ + _log("Found: %s", line->data); + if ((o = find_option(optionname))) { + _log(" Option: %s=%s%s\n", optionname, fromcomposite ? "From" : "", value); + if (spooler == SPOOLER_CUPS && + linetype == LT_BEGIN_FEATURE && + !option_get_value(o, optionset("notfirst")) && + strcmp(option_get_value(o, optset), value) != 0 && + (inheader || option_get_section(o) == SECTION_PAGESETUP)) { + + /* We have the first occurence of an option + setting and the spooler is CUPS, so this + setting is inserted by "pstops" or + "imagetops". The value from the command + line was not inserted by "pstops" or + "imagetops" so it seems to be not under + the choices in the PPD. Possible + reasons: + + - "pstops" and "imagetops" ignore settings + of numerical or string options which are + not one of the choices in the PPD file, + and inserts the default value instead. + + - On the command line an option was applied + only to selected pages: + "-o <page ranges>:<option>=<values> + This is not supported by CUPS, so not + taken care of by "pstops". + + We must fix this here by replacing the + setting inserted by "pstops" or "imagetops" + with the exact setting given on the command + line. */ + + /* $arg->{$optionset} is already + range-checked, so do not check again here + Insert DSC comment */ + pdest = (inheader && isdscjob) ? psheader : psfifo; + if (option_is_ps_command(o)) { + /* PostScript option, insert the code */ + + option_get_command(tmp, o, optset, -1); + if (!(val = option_get_value(o, optset))) + val = ""; + + /* Boolean and enumerated choice options can only be set in the + * PageSetup section */ + if ((inheader && option_is_custom_value(o, val)) || !inheader) + { + if (o->type == TYPE_BOOL) + dstrcatf(pdest, "%%%%BeginFeature: *%s %s\n", o->name, + val && !strcmp(val, "1") ? "True" : "False"); + else + dstrcatf(pdest, "%%%%BeginFeature: *%s %s\n", o->name, val); + + dstrcatf(pdest, "%s\n", tmp->data); + + /* We have replaced this option on the FIFO */ + optionreplaced = 1; + } + } + else { /* Command line or JCL option */ + val = option_get_value(o, optset); + + if (!inheader || option_is_custom_value(o, val)) { + dstrcatf(pdest, "%%%% FoomaticRIPOptionSetting: %s=%s\n", + o->name, val ? val : ""); + optionreplaced = 1; + } + } + + if (optionreplaced) { + val = option_get_value(o, optset); + _log(" --> Correcting numerical/string option to %s=%s (Command line argument)\n", + o->name, val ? val : ""); + } + } + + /* Mark that we have already found this option */ + o->notfirst = 1; + if (!optionreplaced) { + if (o->style != 'G') { + /* Controlled by '<Composite>' setting of + a member option of a composite option */ + if (fromcomposite) { + dstrcpyf(tmp, "From%s", value); + strlcpy(value, tmp->data, 128); + } + + /* Non PostScript option + Check whether it is valid */ + if (option_set_value(o, optset, value)) { + _log("Setting option\n"); + strlcpy(value, option_get_value(o, optset), 128); + if (optionsalsointoheader) + option_set_value(o, optionset("header"), value); + if (o->type == TYPE_ENUM && + (!strcmp(o->name, "PageSize") || !strcmp(o->name, "PageRegion")) && + startswith(value, "Custom") && + linetype == LT_FOOMATIC_RIP_OPTION_SETTING) { + /* Custom Page size */ + width = height = 0.0; + p = linesafterlastbeginfeature->data; + while (*p && isspace(*p)) p++; + width = strtod(p, &p); + while (*p && isspace(*p)) p++; + height = strtod(p, &p); + if (width && height) { + dstrcpyf(tmp, "%s.%fx%f", value, width, height); + strlcpy(value, tmp->data, 128); + option_set_value(o, optset, value); + if (optionsalsointoheader) + option_set_value(o, optionset("header"), value); + } + } + /* For a composite option insert the + code from the member options with + current setting "From<composite>" + The code from the member options + is chosen according to the setting + of the composite option. */ + if (option_is_composite(o) && linetype == LT_FOOMATIC_RIP_OPTION_SETTING) { + build_commandline(optset, NULL, 0); /* TODO can this be removed? */ + + /* TODO merge section and ps_section */ + if (postscriptsection == PS_SECTION_JCLSETUP) + option_get_command(tmp, o, optset, SECTION_JCLSETUP); + else if (postscriptsection == PS_SECTION_PROLOG) + option_get_command(tmp, o, optset, SECTION_PROLOG); + else if (postscriptsection == PS_SECTION_SETUP) + option_get_command(tmp, o, optset, SECTION_DOCUMENTSETUP); + else if (postscriptsection == PS_SECTION_PAGESETUP) + option_get_command(tmp, o, optset, SECTION_PAGESETUP); + dstrcat(line, tmp->data); + } + } + else + _log(" --> Invalid option setting found in job\n"); + } + else if (fromcomposite) { + /* PostScript option, but we have to look up + the PostScript code to be inserted from + the setting of a composite option, as + this option is set to "Controlled by + '<Composite>'". */ + /* Set the option */ + dstrcpyf(tmp, "From%s", value); + strlcpy(value, tmp->data, 128); + if (option_set_value(o, optset, value)) { + _log(" --> Looking up setting in composite option %s\n", value); + if (optionsalsointoheader) + option_set_value(o, optionset("header"), value); + /* update composite options */ + build_commandline(optset, NULL, 0); + /* Substitute PostScript comment by the real code */ + /* TODO what exactly is the next line doing? */ + /* dstrcpy(line, o->compositesubst->data); */ + } + else + _log(" --> Invalid option setting found in job\n"); + } + else + /* it is a PostScript style option with + the code readily inserted, no option + for the renderer command line/JCL to set, + no lookup of a composite option needed, + so nothing to do here... */ + _log(" --> Option will be set by PostScript interpreter\n"); + } + } + else + /* This option is unknown to us, WTF? */ + _log("Unknown option %s=%s found in the job\n", optionname, value); + } + else if (nestinglevel == 0 && startswith(line->data, "%%EndFeature")) { + /* End of feature */ + infeature = 0; + /* If the option setting was replaced, it ends here, + too, and the next option is not necessarily also replaced */ + optionreplaced = 0; + dstrclear(linesafterlastbeginfeature); + } + else if (nestinglevel == 0 && isdscjob && !prologfound && + startswith(line->data, "%%Begin")) { + /* In some PostScript files (especially when generated + by "dvips" of TeX/LaTeX) the "%%BeginProlog" is + missing, so assume that it was before the current + line (the first line starting with "%%Begin". */ + _log("Job claims to be DSC-conforming, but \"%%%%BeginProlog\" " + "was missing before first line with another" + "\"%%%%BeginProlog\" comment (is this a TeX/LaTeX/dvips-generated" + " PostScript file?). Assuming start of \"Prolog\" here.\n"); + /* Beginning of Prolog */ + inprolog = 1; + nondsclines = 0; + /* Insert options for "Prolog" before the current line */ + dstrcpyf(tmp, "%%%%BeginProlog\n"); + append_prolog_section(tmp, optset, 0); + dstrprepend(line, tmp->data); + prologfound = 1; + } + else if (nestinglevel == 0 && ( + startswith(line->data, "%RBINumCopies:") || + startswith(line->data, "%%RBINumCopies:"))) { + p = strchr(line->data, ':') +1; + get_current_job()->rbinumcopies = atoi(p); + _log("Found %RBINumCopies: %d\n", get_current_job()->rbinumcopies); + } + else if (startswith(skip_whitespace(line->data), "%") || + startswith(skip_whitespace(line->data), "$")) + /* This is an unknown PostScript comment or a blank + line, no active code */ + ignoreline = 1; + } + else { + /* This line is active PostScript code */ + if (infeature) + /* Collect coe in a "%%BeginFeature: ... %%EndFeature" + section, to get the values for a custom option + setting */ + dstrcat(linesafterlastbeginfeature, line->data); + + if (inheader) { + if (!inprolog && !insetup) { + /* Outside the "Prolog" and "Setup" section + a correct DSC-conforming document has no + active PostScript code, so consider the + file as non-DSC-conforming when there are + too many of such lines. */ + nondsclines++; + if (nondsclines > MAX_NON_DSC_LINES_IN_HEADER) { + /* Consider document as not DSC-conforming */ + _log("This job seems not to be DSC-conforming, " + "DSC-comment for next section not found, " + "stopping to parse the rest, passing it " + "directly to the renderer.\n"); + /* Stop scanning for further option settings */ + maxlines = 1; + isdscjob = 0; + /* Insert defaults and command line settings in + the beginning of the job or after the last valid + section */ + dstrclear(tmp); + if (prologfound) + append_prolog_section(tmp, optset, 1); + if (setupfound) + append_setup_section(tmp, optset, 1); + if (pagesetupfound) + append_page_setup_section(tmp, optset, 1); + dstrinsert(psheader, line_start(psheader->data, insertoptions), tmp->data); + + prologfound = 1; + setupfound = 1; + pagesetupfound = 1; + } + } + } + else if (!inpageheader) { + /* PostScript code inside a page, but not between + "%%BeginPageSetup" and "%%EndPageSetup", so + we are perhaps already drawing onto a page now */ + if (startswith(onelinebefore->data, "%%Page")) + _log("No page header or page header not DSC-conforming\n"); + /* Stop buffering lines to search for options + placed not DSC-conforming */ + if (line_count(psfifo->data) >= MAX_LINES_FOR_PAGE_OPTIONS) { + _log("Stopping search for page header options\n"); + passthru = 1; + /* If there comes a page header now, ignore it */ + ignorepageheader = 1; + optionsalsointoheader = 0; + } + /* Insert PostScript option settings (options for the + * section "PageSetup" */ + if (isdscjob && !pagesetupfound) { + append_page_setup_section(psfifo, optset, 1); + pagesetupfound = 1; + } + } + } + } + + /* Debug Info */ + if (lastpassthru != passthru) { + if (passthru) + _log("Found: %s --> Output goes directly to the renderer now.\n\n", line->data); + else + _log("Found: %s --> Output goes to the FIFO buffer now.\n\n", line->data); + } + + /* We are in an option which was replaced, do not output the current line */ + if (optionreplaced) + dstrclear(line); + + /* If we are in a "%%BeginSetup...%%EndSetup" section after + the first "%%Page:..." and the current line belongs to + an option setting, we have to copy the line also to the + @psheader. */ + if (optionsalsointoheader && (infeature || startswith(line->data, "%%EndFeature"))) + dstrcat(psheader, line->data); + + /* Store or send the current line */ + if (inheader && isdscjob) { + /* We are still in the PostScript header, collect all lines + in @psheader */ + dstrcat(psheader, line->data); + } + else { + if (passthru && isdscjob) { + if (!lastpassthru) { + /* + * We enter passthru mode with this line, so the + * command line can have changed, check it and close + * the renderer if needed + */ + if (rendererpid && !optionset_equal(optionset("currentpage"), optionset("previouspage"), 0)) { + _log("Command line/JCL options changed, restarting renderer\n"); + retval = close_renderer_handle(rendererhandle, rendererpid); + if (retval != EXIT_PRINTED) + rip_die(retval, "Error closing renderer\n"); + rendererpid = 0; + } + } + + /* Flush psfifo and send line directly to the renderer */ + if (!rendererpid) { + /* No renderer running, start it */ + dstrcpy(tmp, psheader->data); + dstrcat(tmp, psfifo->data); + get_renderer_handle(tmp, &rendererhandle, &rendererpid); + /* psfifo is sent out, flush it */ + dstrclear(psfifo); + } + + if (!isempty(psfifo->data)) { + /* Send psfifo to renderer */ + fwrite(psfifo->data, psfifo->len, 1, rendererhandle); + /* flush psfifo */ + dstrclear(psfifo); + } + + /* Send line to renderer */ + if (!printprevpage) { + fwrite(line->data, line->len, 1, rendererhandle); + + while (stream_next_line(line, stream) > 0) { + if (startswith(line->data, "%%")) { + _log("Found: %s", line->data); + _log(" --> Continue DSC parsing now.\n\n"); + saved = 1; + break; + } + else { + fwrite(line->data, line->len, 1, rendererhandle); + linect++; + } + } + } + } + else { + /* Push the line onto the stack to split up later */ + dstrcat(psfifo, line->data); + } + } + + if (!printprevpage) + linect++; + } + else { + /* EOF! */ + more_stuff = 0; + + /* No PostScript header in the whole file? Then it's not + PostScript, convert it. + We open the file converter here when the file has less + lines than the amount which we search for the PostScript + header ($maxlinestopsstart). */ + if (linect <= nonpslines) { + /* This is not a PostScript job, we must convert it */ + _log("\nJob does not start with \"%%!\", is it PostScript?\n" + "Starting file converter\n"); + + /* Reset all variables but conserve the data which + we already have read */ + jobhasjcl = 0; + linect = 0; + maxlines = 1000; + dstrclear(onelinebefore); + dstrclear(twolinesbefore); + dstrclear(line); + + dstrcpy(tmp, psheader->data); + dstrcat(tmp, psfifo->data); + dstrclear(psfifo); + dstrclear(psheader); + + /* Start the file conversion filter */ + if (!fileconverter_pid) + get_fileconverter_handle(tmp->data, &fileconverter_handle, &fileconverter_pid); + else + rip_die(EXIT_JOBERR, "File conversion filter probably crashed\n"); + + /* Read the further data from the file converter and + not from STDIN */ + if (close(fileno(stdin)) != 0) + rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Couldn't close STDIN\n"); + + if (dup2(fileno(fileconverter_handle), fileno(stdin)) == -1) + rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Couldn't dup fileconverterhandle\n"); + + /* Now we have new (converted) stuff in STDIN, so + continue in the loop */ + more_stuff = 1; + } + } + + lastpassthru = passthru; + + if (!ignoreline && !printprevpage) { + dstrcpy(twolinesbefore, onelinebefore->data); + dstrcpy(onelinebefore, line->data); + } + + } while ((maxlines == 0 || linect < maxlines) && more_stuff != 0); + + /* Some buffer still containing data? Send it out to the renderer */ + if (more_stuff || inheader || !isempty(psfifo->data)) { + /* Flush psfifo and send the remaining data to the renderer, this + only happens with non-DSC-conforming jobs or non-Foomatic PPDs */ + if (more_stuff) + _log("Stopped parsing the PostScript data, " + "sending rest directly to the renderer.\n"); + else + _log("Flushing FIFO.\n"); + + if (inheader) { + build_commandline(optset, NULL, 0); + /* No page initialized yet? Copy the "header" option set into the + "currentpage" option set, so that the renderer will find the + options settings. */ + optionset_copy_values(optionset("header"), optionset("currentpage")); + optset = optionset("currentpage"); + + /* If not done yet, insert defaults and command line settings + in the beginning of the job or after the last valid section */ + dstrclear(tmp); + if (prologfound) + append_prolog_section(tmp, optset, 1); + if (setupfound) + append_setup_section(tmp, optset, 1); + if (pagesetupfound) + append_page_setup_section(tmp, optset, 1); + dstrinsert(psheader, line_start(psheader->data, insertoptions), tmp->data); + + prologfound = 1; + setupfound = 1; + pagesetupfound = 1; + } + + if (rendererpid > 0 && !optionset_equal(optionset("currentpage"), optionset("previouspage"), 0)) { + _log("Command line/JCL options changed, restarting renderer\n"); + retval = close_renderer_handle(rendererhandle, rendererpid); + if (retval != EXIT_PRINTED) + rip_die(retval, "Error closing renderer\n"); + rendererpid = 0; + } + + if (!rendererpid) { + dstrcpy(tmp, psheader->data); + dstrcat(tmp, psfifo->data); + get_renderer_handle(tmp, &rendererhandle, &rendererpid); + /* We have sent psfifo now */ + dstrclear(psfifo); + } + + if (psfifo->len) { + /* Send psfifo to the renderer */ + fwrite(psfifo->data, psfifo->len, 1, rendererhandle); + dstrclear(psfifo); + } + + /* Print the rest of the input data */ + if (more_stuff) { + while (stream_next_line(tmp, stream)) + fwrite(tmp->data, tmp->len, 1, rendererhandle); + } + } + + /* At every "%%Page:..." comment we have saved the PostScript state + and we have increased the page number. So if the page number is + non-zero we had at least one "%%Page:..." comment and so we have + to give a restore the PostScript state. + if ($currentpage > 0) { + print $rendererhandle "foomatic-saved-state restore\n"; + } */ + + /* Close the renderer */ + if (rendererpid) { + retval = close_renderer_handle(rendererhandle, rendererpid); + if (retval != EXIT_PRINTED) + rip_die(retval, "Error closing renderer\n"); + rendererpid = 0; + } + + /* Close the file converter (if it was used) */ + if (fileconverter_pid) { + retval = close_fileconverter_handle(fileconverter_handle, fileconverter_pid); + if (retval != EXIT_PRINTED) + rip_die(retval, "Error closing file converter\n"); + fileconverter_pid = 0; + } + + free_dstr(line); + free_dstr(onelinebefore); + free_dstr(twolinesbefore); + free_dstr(psheader); + free_dstr(psfifo); + free_dstr(tmp); +} + +/* + * Run the renderer command line (and if defined also the postpipe) and returns + * a file handle for stuffing in the PostScript data. + */ +void get_renderer_handle(const dstr_t *prepend, FILE **fd, pid_t *pid) +{ + pid_t kid3; + FILE *kid3in; + dstr_t *cmdline = create_dstr(); + + /* Build the command line and get the JCL commands */ + build_commandline(optionset("currentpage"), cmdline, 0); + massage_gs_commandline(cmdline); + + _log("\nStarting renderer with command: \"%s\"\n", cmdline->data); + kid3 = start_process("kid3", exec_kid3, (void *)cmdline->data, &kid3in, NULL); + if (kid3 < 0) + rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS, "Cannot fork for kid3\n"); + + /* Feed the PostScript header and the FIFO contents */ + if (prepend) + fwrite(prepend->data, prepend->len, 1, kid3in); + + /* We are the parent, return glob to the file handle */ + *fd = kid3in; + *pid = kid3; + + free_dstr(cmdline); +} + +/* Close the renderer process and wait until all kid processes finish */ +int close_renderer_handle(FILE *rendererhandle, pid_t rendererpid) +{ + int status; + + _log("\nClosing renderer\n"); + fclose(rendererhandle); + + status = wait_for_process(rendererpid); + if (WIFEXITED(status)) + return WEXITSTATUS(status); + else + return EXIT_PRNERR_NORETRY_BAD_SETTINGS; +} + |