blob: 2aeda011484b5fa7b99bdd78a12dcec9364edefb [file] [log] [blame]
/*
* "$Id: emit.c 5238 2006-03-07 04:41:42Z mike $"
*
* PPD code emission routines for the Common UNIX Printing System (CUPS).
*
* Copyright 1997-2006 by Easy Software Products, all rights reserved.
*
* These coded instructions, statements, and computer programs are the
* property of Easy Software Products and are protected by Federal
* copyright law. Distribution and use rights are outlined in the file
* "LICENSE.txt" which should have been included with this file. If this
* file is missing or damaged please contact Easy Software Products
* at:
*
* Attn: CUPS Licensing Information
* Easy Software Products
* 44141 Airport View Drive, Suite 204
* Hollywood, Maryland 20636 USA
*
* Voice: (301) 373-9600
* EMail: [email protected]
* WWW: http://www.cups.org
*
* PostScript is a trademark of Adobe Systems, Inc.
*
* This file is subject to the Apple OS-Developed Software exception.
*
* Contents:
*
* ppdCollect() - Collect all marked options that reside in the
* specified section.
* ppdCollect2() - Collect all marked options that reside in the
* specified section and minimum order.
* ppdEmit() - Emit code for marked options to a file.
* ppdEmitAfterOrder() - Emit a subset of the code for marked options to a
* file.
* ppdEmitFd() - Emit code for marked options to a file.
* ppdEmitJCL() - Emit code for JCL options to a file.
* ppdEmitJCLEnd() - Emit JCLEnd code to a file.
* ppdEmitString() - Get a string containing the code for marked options.
* ppd_handle_media() - Handle media selection...
* ppd_sort() - Sort options by ordering numbers...
*/
/*
* Include necessary headers...
*/
#include "ppd.h"
#include <stdlib.h>
#include "string.h"
#include <errno.h>
#if defined(WIN32) || defined(__EMX__)
# include <io.h>
#else
# include <unistd.h>
#endif /* WIN32 || __EMX__ */
/*
* Local functions...
*/
static void ppd_handle_media(ppd_file_t *ppd);
static int ppd_sort(ppd_choice_t **c1, ppd_choice_t **c2);
/*
* Local globals...
*/
static const char ppd_custom_code[] =
"pop pop pop\n"
"<</PageSize[5 -2 roll]/ImagingBBox null>>setpagedevice\n";
/*
* 'ppdCollect()' - Collect all marked options that reside in the specified
* section.
*/
int /* O - Number of options marked */
ppdCollect(ppd_file_t *ppd, /* I - PPD file data */
ppd_section_t section, /* I - Section to collect */
ppd_choice_t ***choices) /* O - Pointers to choices */
{
return (ppdCollect2(ppd, section, 0.0, choices));
}
/*
* 'ppdCollect2()' - Collect all marked options that reside in the
* specified section and minimum order.
*
* @since CUPS 1.2@
*/
int /* O - Number of options marked */
ppdCollect2(ppd_file_t *ppd, /* I - PPD file data */
ppd_section_t section, /* I - Section to collect */
float min_order, /* I - Minimum OrderDependency value */
ppd_choice_t ***choices) /* O - Pointers to choices */
{
int i, j, k, m; /* Looping vars */
ppd_group_t *g, /* Current group */
*sg; /* Current sub-group */
ppd_option_t *o; /* Current option */
ppd_choice_t *c; /* Current choice */
int count; /* Number of choices collected */
ppd_choice_t **collect; /* Collected choices */
if (ppd == NULL)
return (0);
/*
* Allocate memory for up to 1000 selected choices...
*/
count = 0;
collect = calloc(sizeof(ppd_choice_t *), 1000);
/*
* Loop through all options and add choices as needed...
*/
for (i = ppd->num_groups, g = ppd->groups; i > 0; i --, g ++)
{
for (j = g->num_options, o = g->options; j > 0; j --, o ++)
if (o->section == section && o->order >= min_order)
for (k = o->num_choices, c = o->choices; k > 0; k --, c ++)
if (c->marked && count < 1000)
{
collect[count] = c;
count ++;
}
for (j = g->num_subgroups, sg = g->subgroups; j > 0; j --, sg ++)
for (k = sg->num_options, o = sg->options; k > 0; k --, o ++)
if (o->section == section && o->order >= min_order)
for (m = o->num_choices, c = o->choices; m > 0; m --, c ++)
if (c->marked && count < 1000)
{
collect[count] = c;
count ++;
}
}
/*
* If we have more than 1 marked choice, sort them...
*/
if (count > 1)
qsort(collect, count, sizeof(ppd_choice_t *),
(int (*)(const void *, const void *))ppd_sort);
/*
* Return the array and number of choices; if 0, free the array since
* it isn't needed.
*/
if (count > 0)
{
*choices = collect;
return (count);
}
else
{
*choices = NULL;
free(collect);
return (0);
}
}
/*
* 'ppdEmit()' - Emit code for marked options to a file.
*/
int /* O - 0 on success, -1 on failure */
ppdEmit(ppd_file_t *ppd, /* I - PPD file record */
FILE *fp, /* I - File to write to */
ppd_section_t section) /* I - Section to write */
{
return (ppdEmitAfterOrder(ppd, fp, section, 0, 0.0));
}
/*
* 'ppdEmitAfterOrder()' - Emit a subset of the code for marked options to a file.
*
* When "limit" is non-zero, this function only emits options whose
* OrderDependency value is greater than or equal to "min_order".
*
* When "limit" is zero, this function is identical to ppdEmit().
*
* @since CUPS 1.2@
*/
int /* O - 0 on success, -1 on failure */
ppdEmitAfterOrder(
ppd_file_t *ppd, /* I - PPD file record */
FILE *fp, /* I - File to write to */
ppd_section_t section, /* I - Section to write */
int limit, /* I - Non-zero to use min_order */
float min_order) /* I - Lowest OrderDependency */
{
char *buffer; /* Option code */
int status; /* Return status */
/*
* Range check input...
*/
if (!ppd || !fp)
return (-1);
/*
* Get the string...
*/
buffer = ppdEmitString(ppd, section, min_order);
/*
* Write it as needed and return...
*/
if (buffer)
{
status = fputs(buffer, fp) < 0 ? -1 : 0;
free(buffer);
}
else
status = 0;
return (status);
}
/*
* 'ppdEmitFd()' - Emit code for marked options to a file.
*/
int /* O - 0 on success, -1 on failure */
ppdEmitFd(ppd_file_t *ppd, /* I - PPD file record */
int fd, /* I - File to write to */
ppd_section_t section) /* I - Section to write */
{
char *buffer, /* Option code */
*bufptr; /* Pointer into code */
size_t buflength; /* Length of option code */
ssize_t bytes; /* Bytes written */
int status; /* Return status */
/*
* Range check input...
*/
if (!ppd || fd < 0)
return (-1);
/*
* Get the string...
*/
buffer = ppdEmitString(ppd, section, 0.0);
/*
* Write it as needed and return...
*/
if (buffer)
{
buflength = strlen(buffer);
bufptr = buffer;
bytes = 0;
while (buflength > 0)
{
if ((bytes = write(fd, bufptr, buflength)) < 0)
{
if (errno == EAGAIN || errno == EINTR)
continue;
break;
}
buflength -= bytes;
bufptr += bytes;
}
status = bytes < 0 ? -1 : 0;
free(buffer);
}
else
status = 0;
return (status);
}
/*
* 'ppdEmitJCL()' - Emit code for JCL options to a file.
*/
int /* O - 0 on success, -1 on failure */
ppdEmitJCL(ppd_file_t *ppd, /* I - PPD file record */
FILE *fp, /* I - File to write to */
int job_id, /* I - Job ID */
const char *user, /* I - Username */
const char *title) /* I - Title */
{
char *ptr; /* Pointer into JCL string */
char temp[81]; /* Local title string */
/*
* Range check the input...
*/
if (!ppd || !ppd->jcl_begin || !ppd->jcl_ps)
return (0);
/*
* See if the printer supports HP PJL...
*/
if (!strncmp(ppd->jcl_begin, "\033%-12345X@", 10))
{
/*
* This printer uses HP PJL commands for output; filter the output
* so that we only have a single "@PJL JOB" command in the header...
*
* To avoid bugs in the PJL implementation of certain vendors' products
* (Xerox in particular), we add a dummy "@PJL" command at the beginning
* of the PJL commands to initialize PJL processing.
*/
fputs("\033%-12345X@PJL\n", fp);
for (ptr = ppd->jcl_begin + 9; *ptr;)
if (!strncmp(ptr, "@PJL JOB", 8))
{
/*
* Skip job command...
*/
for (;*ptr; ptr ++)
if (*ptr == '\n')
break;
if (*ptr)
ptr ++;
}
else
{
/*
* Copy line...
*/
for (;*ptr; ptr ++)
{
putc(*ptr, fp);
if (*ptr == '\n')
break;
}
if (*ptr)
ptr ++;
}
/*
* Eliminate any path info from the job title...
*/
if ((ptr = strrchr(title, '/')) != NULL)
title = ptr + 1;
/*
* Replace double quotes with single quotes so that the title
* does not cause a PJL syntax error.
*/
strlcpy(temp, title, sizeof(temp));
for (ptr = temp; *ptr; ptr ++)
if (*ptr == '\"')
*ptr = '\'';
/*
* Send PJL JOB and PJL RDYMSG commands before we enter PostScript mode...
*/
fprintf(fp, "@PJL JOB NAME = \"%s\" DISPLAY = \"%d %s %s\"\n", temp,
job_id, user, temp);
fprintf(fp, "@PJL RDYMSG DISPLAY = \"%d %s %s\"\n", job_id, user, temp);
}
else
fputs(ppd->jcl_begin, fp);
ppdEmit(ppd, fp, PPD_ORDER_JCL);
fputs(ppd->jcl_ps, fp);
return (0);
}
/*
* 'ppdEmitJCLEnd()' - Emit JCLEnd code to a file.
*
* @since CUPS 1.2@
*/
int /* O - 0 on success, -1 on failure */
ppdEmitJCLEnd(ppd_file_t *ppd, /* I - PPD file record */
FILE *fp) /* I - File to write to */
{
/*
* Range check the input...
*/
if (!ppd)
return (0);
if (!ppd->jcl_end)
{
if (ppd->num_filters == 0)
putc(0x04, fp);
return (0);
}
/*
* See if the printer supports HP PJL...
*/
if (!strncmp(ppd->jcl_end, "\033%-12345X@", 10))
{
/*
* This printer uses HP PJL commands for output; filter the output
* so that we only have a single "@PJL JOB" command in the header...
*
* To avoid bugs in the PJL implementation of certain vendors' products
* (Xerox in particular), we add a dummy "@PJL" command at the beginning
* of the PJL commands to initialize PJL processing.
*/
fputs("\033%-12345X@PJL\n", fp);
fputs("@PJL RDYMSG DISPLAY = \"READY\"\n", fp);
fputs(ppd->jcl_end + 9, fp);
}
else
fputs(ppd->jcl_end, fp);
return (0);
}
/*
* 'ppdEmitString()' - Get a string containing the code for marked options.
*
* When "min_order" is greater than zero, this function only includes options
* whose OrderDependency value is greater than or equal to "min_order".
* Otherwise, all options in the specified section are included in the
* returned string.
*
* The return string is allocated on the heap and should be freed using
* free() when you are done with it.
*
* @since CUPS 1.2@
*/
char * /* O - String containing option code */
ppdEmitString(ppd_file_t *ppd, /* I - PPD file record */
ppd_section_t section, /* I - Section to write */
float min_order) /* I - Lowest OrderDependency */
{
int i, j, /* Looping vars */
count; /* Number of choices */
ppd_choice_t **choices; /* Choices */
ppd_size_t *size; /* Custom page size */
ppd_coption_t *coption; /* Custom option */
ppd_cparam_t *cparam; /* Custom parameter */
size_t bufsize; /* Size of string buffer needed */
char *buffer, /* String buffer */
*bufptr, /* Pointer into buffer */
*bufend; /* End of buffer */
struct lconv *loc; /* Locale data */
/*
* Range check input...
*/
if (!ppd)
return (NULL);
/*
* Use PageSize or PageRegion as required...
*/
ppd_handle_media(ppd);
/*
* Collect the options we need to emit...
*/
if ((count = ppdCollect2(ppd, section, min_order, &choices)) == 0)
return (NULL);
/*
* Count the number of bytes that are required to hold all of the
* option code...
*/
for (i = 0, bufsize = 1; i < count; i ++)
{
if (section != PPD_ORDER_EXIT && section != PPD_ORDER_JCL)
{
bufsize += 3; /* [{\n */
if ((!strcasecmp(choices[i]->option->keyword, "PageSize") ||
!strcasecmp(choices[i]->option->keyword, "PageRegion")) &&
!strcasecmp(choices[i]->choice, "Custom"))
{
bufsize += 37; /* %%BeginFeature: *CustomPageSize True */
bufsize += 50; /* Five 9-digit numbers + newline */
}
else if (!strcasecmp(choices[i]->choice, "Custom") &&
(coption = ppdFindCustomOption(ppd,
choices[i]->option->keyword))
!= NULL)
{
bufsize += 23 + strlen(choices[i]->option->keyword);
/* %%BeginFeature: *keyword True */
for (cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params);
cparam;
cparam = (ppd_cparam_t *)cupsArrayNext(coption->params))
{
switch (cparam->type)
{
case PPD_CUSTOM_CURVE :
case PPD_CUSTOM_INVCURVE :
case PPD_CUSTOM_POINTS :
case PPD_CUSTOM_REAL :
case PPD_CUSTOM_INT :
bufsize += 10;
break;
case PPD_CUSTOM_PASSCODE :
case PPD_CUSTOM_PASSWORD :
case PPD_CUSTOM_STRING :
bufsize += 3 + 4 * strlen(cparam->current.custom_string);
break;
}
}
}
else
bufsize += 19 + strlen(choices[i]->option->keyword) +
strlen(choices[i]->choice);
/* %%BeginFeature: *keyword choice */
bufsize += 13; /* %%EndFeature\n */
bufsize += 22; /* } stopped cleartomark\n */
}
if (choices[i]->code)
bufsize += strlen(choices[i]->code);
else
bufsize += strlen(ppd_custom_code);
}
/*
* Allocate memory...
*/
if ((buffer = calloc(1, bufsize)) == NULL)
{
free(choices);
return (NULL);
}
bufend = buffer + bufsize - 1;
loc = localeconv();
/*
* Copy the option code to the buffer...
*/
for (i = 0, bufptr = buffer; i < count; i ++, bufptr += strlen(bufptr))
if (section != PPD_ORDER_EXIT && section != PPD_ORDER_JCL)
{
/*
* Add wrapper commands to prevent printer errors for unsupported
* options...
*/
strlcpy(bufptr, "[{\n", bufend - bufptr + 1);
bufptr += 3;
/*
* Send DSC comments with option...
*/
if ((!strcasecmp(choices[i]->option->keyword, "PageSize") ||
!strcasecmp(choices[i]->option->keyword, "PageRegion")) &&
!strcasecmp(choices[i]->choice, "Custom"))
{
/*
* Variable size; write out standard size options, using the
* parameter positions defined in the PPD file...
*/
ppd_attr_t *attr; /* PPD attribute */
int pos, /* Position of custom value */
orientation; /* Orientation to use */
float values[5]; /* Values for custom command */
strlcpy(bufptr, "%%BeginFeature: *CustomPageSize True\n",
bufend - bufptr + 1);
bufptr += 37;
size = ppdPageSize(ppd, "Custom");
memset(values, 0, sizeof(values));
if ((attr = ppdFindAttr(ppd, "ParamCustomPageSize", "Width")) != NULL)
{
pos = atoi(attr->value) - 1;
if (pos < 0 || pos > 4)
pos = 0;
}
else
pos = 0;
values[pos] = size->width;
if ((attr = ppdFindAttr(ppd, "ParamCustomPageSize", "Height")) != NULL)
{
pos = atoi(attr->value) - 1;
if (pos < 0 || pos > 4)
pos = 1;
}
else
pos = 1;
values[pos] = size->length;
/*
* According to the Adobe PPD specification, an orientation of 1
* will produce a print that comes out upside-down with the X
* axis perpendicular to the direction of feed, which is exactly
* what we want to be consistent with non-PS printers.
*
* We could also use an orientation of 3 to produce output that
* comes out rightside-up (this is the default for many large format
* printer PPDs), however for consistency we will stick with the
* value 1.
*
* If we wanted to get fancy, we could use orientations of 0 or
* 2 and swap the width and length, however we don't want to get
* fancy, we just want it to work consistently.
*
* The orientation value is range limited by the Orientation
* parameter definition, so certain non-PS printer drivers that
* only support an Orientation of 0 will get the value 0 as
* expected.
*/
orientation = 1;
if ((attr = ppdFindAttr(ppd, "ParamCustomPageSize",
"Orientation")) != NULL)
{
int min_orient, max_orient; /* Minimum and maximum orientations */
if (sscanf(attr->value, "%d%*s%d%d", &pos, &min_orient,
&max_orient) != 3)
pos = 4;
else
{
pos --;
if (pos < 0 || pos > 4)
pos = 4;
if (orientation > max_orient)
orientation = max_orient;
else if (orientation < min_orient)
orientation = min_orient;
}
}
else
pos = 4;
values[pos] = orientation;
for (pos = 0; pos < 5; pos ++)
{
bufptr = _cupsStrFormatd(bufptr, bufend, values[pos], loc);
*bufptr++ = '\n';
}
if (!choices[i]->code)
{
/*
* This can happen with certain buggy PPD files that don't include
* a CustomPageSize command sequence... We just use a generic
* Level 2 command sequence...
*/
strlcpy(bufptr, ppd_custom_code, bufend - bufptr + 1);
bufptr += strlen(bufptr);
}
}
else if (!strcasecmp(choices[i]->choice, "Custom") &&
(coption = ppdFindCustomOption(ppd,
choices[i]->option->keyword))
!= NULL)
{
/*
* Custom option...
*/
const char *s; /* Pointer into string value */
snprintf(bufptr, bufend - bufptr + 1,
"%%%%BeginFeature: *Custom%s True\n", coption->keyword);
bufptr += strlen(bufptr);
for (cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params);
cparam;
cparam = (ppd_cparam_t *)cupsArrayNext(coption->params))
{
switch (cparam->type)
{
case PPD_CUSTOM_CURVE :
case PPD_CUSTOM_INVCURVE :
case PPD_CUSTOM_POINTS :
case PPD_CUSTOM_REAL :
bufptr = _cupsStrFormatd(bufptr, bufend,
cparam->current.custom_real, loc);
*bufptr++ = '\n';
break;
case PPD_CUSTOM_INT :
snprintf(bufptr, bufend - bufptr + 1, "%d\n",
cparam->current.custom_int);
bufptr += strlen(bufptr);
break;
case PPD_CUSTOM_PASSCODE :
case PPD_CUSTOM_PASSWORD :
case PPD_CUSTOM_STRING :
*bufptr++ = '(';
for (s = cparam->current.custom_string; *s; s ++)
if (*s < ' ' || *s == '(' || *s == ')' || *s >= 127)
{
snprintf(bufptr, bufend - bufptr + 1, "\\%03o", *s & 255);
bufptr += strlen(bufptr);
}
else
*bufptr++ = *s;
*bufptr++ = ')';
*bufptr++ = '\n';
break;
}
}
}
else
{
snprintf(bufptr, bufend - bufptr + 1, "%%%%BeginFeature: *%s %s\n",
choices[i]->option->keyword, choices[i]->choice);
bufptr += strlen(bufptr);
}
if (choices[i]->code && choices[i]->code[0])
{
j = strlen(choices[i]->code);
memcpy(bufptr, choices[i]->code, j);
bufptr += j;
if (choices[i]->code[j - 1] != '\n')
*bufptr++ = '\n';
}
strlcpy(bufptr, "%%EndFeature\n"
"} stopped cleartomark\n", bufend - bufptr + 1);
bufptr += strlen(bufptr);
}
else
{
strlcpy(bufptr, choices[i]->code, bufend - bufptr + 1);
bufptr += strlen(bufptr);
}
/*
* Nul-terminate, free, and return...
*/
*bufptr = '\0';
free(choices);
return (buffer);
}
/*
* 'ppd_handle_media()' - Handle media selection...
*/
static void
ppd_handle_media(ppd_file_t *ppd)
{
ppd_choice_t *manual_feed, /* ManualFeed choice, if any */
*input_slot, /* InputSlot choice, if any */
*page; /* PageSize/PageRegion */
ppd_size_t *size; /* Current media size */
ppd_attr_t *rpr; /* RequiresPageRegion value */
/*
* This function determines if the user has selected a media source
* via the InputSlot or ManualFeed options; if so, it marks the
* PageRegion option corresponding to the current media size.
* Otherwise it marks the PageSize option.
*/
if ((size = ppdPageSize(ppd, NULL)) == NULL)
return;
manual_feed = ppdFindMarkedChoice(ppd, "ManualFeed");
input_slot = ppdFindMarkedChoice(ppd, "InputSlot");
if (input_slot != NULL)
rpr = ppdFindAttr(ppd, "RequiresPageRegion", input_slot->choice);
else
rpr = NULL;
if (!rpr)
rpr = ppdFindAttr(ppd, "RequiresPageRegion", "All");
if (!strcasecmp(size->name, "Custom") || (!manual_feed && !input_slot) ||
!((manual_feed && !strcasecmp(manual_feed->choice, "True")) ||
(input_slot && input_slot->code && input_slot->code[0])))
{
/*
* Manual feed was not selected and/or the input slot selection does
* not contain any PostScript code. Use the PageSize option...
*/
ppdMarkOption(ppd, "PageSize", size->name);
}
else
{
/*
* Manual feed was selected and/or the input slot selection contains
* PostScript code. Use the PageRegion option...
*/
ppdMarkOption(ppd, "PageRegion", size->name);
/*
* RequiresPageRegion does not apply to manual feed so we need to
* check that we are not doing manual feed before unmarking PageRegion.
*/
if (!(manual_feed && !strcasecmp(manual_feed->choice, "True")) &&
((rpr && rpr->value && !strcmp(rpr->value, "False")) ||
(!rpr && !ppd->num_filters)))
{
/*
* Either the PPD file specifies no PageRegion code or the PPD file
* not for a CUPS raster driver and thus defaults to no PageRegion
* code... Unmark the PageRegion choice so that we don't output the
* code...
*/
page = ppdFindMarkedChoice(ppd, "PageRegion");
if (page)
page->marked = 0;
}
}
}
/*
* 'ppd_sort()' - Sort options by ordering numbers...
*/
static int /* O - -1 if c1 < c2, 0 if equal, 1 otherwise */
ppd_sort(ppd_choice_t **c1, /* I - First choice */
ppd_choice_t **c2) /* I - Second choice */
{
if ((*c1)->option->order < (*c2)->option->order)
return (-1);
else if ((*c1)->option->order > (*c2)->option->order)
return (1);
else
return (0);
}
/*
* End of "$Id: emit.c 5238 2006-03-07 04:41:42Z mike $".
*/