diff options
Diffstat (limited to 'options.c')
-rw-r--r-- | options.c | 2073 |
1 files changed, 2073 insertions, 0 deletions
diff --git a/options.c b/options.c new file mode 100644 index 0000000..e39bf1e --- /dev/null +++ b/options.c @@ -0,0 +1,2073 @@ + +#include "foomaticrip.h" +#include "options.h" +#include "util.h" +#include <stdlib.h> +#include <ctype.h> +#include <assert.h> +#include <regex.h> +#include <string.h> +#include <math.h> + + +/* Values from foomatic keywords in the ppd file */ +char printer_model [256]; +char printer_id [128]; +char driver [128]; +char cmd [1024]; +char cmd_pdf [1024]; +dstr_t *postpipe = NULL; /* command into which the output of this + filter should be piped */ +int ps_accounting = 1; +char cupsfilter [256]; + +/* JCL prefix to put before the JCL options + (Can be modified by a "*JCLBegin:" keyword in the ppd file): */ +char jclbegin[256] = "\033%-12345X@PJL\n"; + +/* JCL command to switch the printer to the PostScript interpreter + (Can be modified by a "*JCLToPSInterpreter:" keyword in the PPD file): */ +char jcltointerpreter[256] = ""; + +/* JCL command to close a print job + (Can be modified by a "*JCLEnd:" keyword in the PPD file): */ +char jclend[256] = "\033%-12345X@PJL RESET\n"; + +/* Prefix for starting every JCL command + (Can be modified by "*FoomaticJCLPrefix:" keyword in the PPD file): */ +char jclprefix[256] = "@PJL "; +int jclprefixset = 0; + +dstr_t *prologprepend; +dstr_t *setupprepend; +dstr_t *pagesetupprepend; + + + +option_t *optionlist = NULL; +option_t *optionlist_sorted_by_order = NULL; + +int optionset_alloc, optionset_count; +char **optionsets; + + + + +const char * type_name(int type) +{ + switch (type) { + case TYPE_NONE: + return "none"; + case TYPE_ENUM: + return "enum"; + case TYPE_PICKMANY: + return "pickmany"; + case TYPE_BOOL: + return "bool"; + case TYPE_INT: + return "int"; + case TYPE_FLOAT: + return "float"; + case TYPE_STRING: + return "string"; + }; + _log("type '%d' does not exist\n", type); + return NULL; +} + +int type_from_string(const char *typestr) +{ + int type = TYPE_NONE; + + /* Official PPD options */ + if (!strcmp(typestr, "PickOne")) + type = TYPE_ENUM; + else if (!strcmp(typestr, "PickMany")) + type = TYPE_PICKMANY; + else if (!strcmp(typestr, "Boolean")) + type = TYPE_BOOL; + + /* FoomaticRIPOption */ + else if (strcasecmp(typestr, "enum") == 0) + type = TYPE_ENUM; + else if (strcasecmp(typestr, "pickmany") == 0) + type = TYPE_PICKMANY; + else if (strcasecmp(typestr, "bool") == 0) + type = TYPE_BOOL; + else if (strcasecmp(typestr, "int") == 0) + type = TYPE_INT; + else if (strcasecmp(typestr, "float") == 0) + type = TYPE_FLOAT; + else if (strcasecmp(typestr, "string") == 0) + type = TYPE_STRING; + else if (strcasecmp(typestr, "password") == 0) + type = TYPE_PASSWORD; + + return type; +} + +char style_from_string(const char *style) +{ + char r = '\0'; + if (strcmp(style, "PS") == 0) + r = 'G'; + else if (strcmp(style, "CmdLine") == 0) + r = 'C'; + else if (strcmp(style, "JCL") == 0) + r = 'J'; + else if (strcmp(style, "Composite") == 0) + r = 'X'; + return r; +} + +int section_from_string(const char *value) +{ + if (!strcasecmp(value, "AnySetup")) + return SECTION_ANYSETUP; + else if (!strcasecmp(value, "PageSetup")) + return SECTION_PAGESETUP; + else if (!strcasecmp(value, "Prolog")) + return SECTION_PROLOG; + else if (!strcasecmp(value, "DocumentSetup")) + return SECTION_DOCUMENTSETUP; + else if (!strcasecmp(value, "JCLSetup")) + return SECTION_JCLSETUP; + + _log("Unknown section: \"%s\"\n", value); + return 0; +} + +void options_init() +{ + optionset_alloc = 8; + optionset_count = 0; + optionsets = calloc(optionset_alloc, sizeof(char *)); + + prologprepend = create_dstr(); + setupprepend = create_dstr(); + pagesetupprepend = create_dstr(); +} + +static void free_param(param_t *param) +{ + if (param->allowedchars) { + regfree(param->allowedchars); + free(param->allowedchars); + } + + if (param->allowedregexp) { + regfree(param->allowedregexp); + free(param->allowedregexp); + } + + free(param); +} + +/* + * Values + */ + +static void free_value(value_t *val) +{ + if (val->value) + free(val->value); + free(val); +} + + +/* + * Options + */ +static void free_option(option_t *opt) +{ + choice_t *choice; + param_t *param; + value_t *value; + + free(opt->custom_command); + free(opt->proto); + + while (opt->valuelist) { + value = opt->valuelist; + opt->valuelist = opt->valuelist->next; + free_value(value); + } + while (opt->choicelist) { + choice = opt->choicelist; + opt->choicelist = opt->choicelist->next; + free(choice); + } + while (opt->paramlist) { + param = opt->paramlist; + opt->paramlist = opt->paramlist->next; + free_param(param); + } + if (opt->foomatic_param) + free_param(opt->foomatic_param); + + free(opt); +} + +void options_free() +{ + option_t *opt; + int i; + + for (i = 0; i < optionset_count; i++) + free(optionsets[i]); + free(optionsets); + optionsets = NULL; + optionset_alloc = 0; + optionset_count = 0; + + while (optionlist) { + opt = optionlist; + optionlist = optionlist->next; + free_option(opt); + } + + if (postpipe) + free_dstr(postpipe); + + free_dstr(prologprepend); + free_dstr(setupprepend); + free_dstr(pagesetupprepend); +} + +size_t option_count() +{ + option_t *opt; + size_t cnt = 0; + + for (opt = optionlist; opt; opt = opt->next) + cnt++; + return cnt; +} + +option_t * find_option(const char *name) +{ + option_t *opt; + + /* PageRegion and PageSize are the same options, just store one of them */ + if (!strcasecmp(name, "PageRegion")) + return find_option("PageSize"); + + for (opt = optionlist; opt; opt = opt->next) { + if (!strcasecmp(opt->name, name)) + return opt; + } + return NULL; +} + +option_t * assure_option(const char *name) +{ + option_t *opt, *last; + + if ((opt = find_option(name))) + return opt; + + opt = calloc(1, sizeof(option_t)); + + /* PageRegion and PageSize are the same options, just store one of them */ + if (!strcmp(name, "PageRegion")) + strlcpy(opt->name, "PageSize", 128); + else + strlcpy(opt->name, name, 128); + + /* set varname */ + strcpy(opt->varname, opt->name); + strrepl(opt->varname, "-/.", '_'); + + /* Default execution style is 'G' (PostScript) since all arguments for + which we don't find "*Foomatic..." keywords are usual PostScript options */ + opt->style = 'G'; + + opt->type = TYPE_NONE; + + /* append opt to optionlist */ + if (optionlist) { + for (last = optionlist; last->next; last = last->next); + last->next = opt; + } + else + optionlist = opt; + + /* prepend opt to optionlist_sorted_by_order + (0 is always at the beginning) */ + if (optionlist_sorted_by_order) { + opt->next_by_order = optionlist_sorted_by_order; + optionlist_sorted_by_order = opt; + } + else { + optionlist_sorted_by_order = opt; + } + + _log("Added option %s\n", opt->name); + return opt; +} + +/* This functions checks if "opt" is named "name", or if it has any + alternative names "name" (e.g. PageSize / PageRegion) */ +int option_has_name(option_t *opt, const char *name) +{ + if (!strcmp(opt->name, name)) + return 1; + + if (!strcmp(opt->name, "PageSize") && !strcmp(name, "PageRegion")) + return 1; + + return 0; +} + +int option_is_composite(option_t *opt) +{ + return opt ? (opt->style == 'X') : 0; +} + +int option_is_ps_command(option_t *opt) +{ + return opt->style == 'G'; +} + +int option_is_jcl_arg(option_t *opt) +{ + return opt->style == 'J'; +} + +int option_is_commandline_arg(option_t *opt) +{ + return opt->style == 'C'; +} + +int option_get_section(option_t *opt) +{ + return opt->section; +} + +static value_t * option_find_value(option_t *opt, int optionset) +{ + value_t *val; + + if (!opt) + return NULL; + + for (val = opt->valuelist; val; val = val->next) { + if (val->optionset == optionset) + return val; + } + return NULL; +} + +static value_t * option_assure_value(option_t *opt, int optionset) +{ + value_t *val, *last; + val = option_find_value(opt, optionset); + if (!val) { + val = calloc(1, sizeof(value_t)); + val->optionset = optionset; + + /* append to opt->valuelist */ + if (opt->valuelist) { + for (last = opt->valuelist; last->next; last = last->next); + last->next = val; + } + else + opt->valuelist = val; + } + return val; +} + +static param_t * option_find_param_index(option_t *opt, const char *name, int *idx) +{ + param_t *param; + int i; + for (param = opt->paramlist, i = 0; param; param = param->next, i += 1) { + if (!strcasecmp(param->name, name)) { + if (idx) + *idx = i; + return param; + } + } + if (idx) + *idx = -1; + return 0; +} + +static choice_t * option_find_choice(option_t *opt, const char *name) +{ + choice_t *choice; + assert(opt && name); + for (choice = opt->choicelist; choice; choice = choice->next) { + if (!strcasecmp(choice->value, name)) + return choice; + } + return NULL; +} + +void free_paramvalues(option_t *opt, char **paramvalues) +{ + int i; + if (!paramvalues) + return; + for (i = 0; i < opt->param_count; i++) + free(paramvalues[i]); + free(paramvalues); +} + +char * get_valid_param_string(option_t *opt, param_t *param, const char *str) +{ + char *result; + int i, imin, imax; + float f, fmin, fmax; + size_t len; + + switch (param->type) { + case TYPE_INT: + i = atoi(str); + imin = !isempty(param->min) ? atoi(param->min) : -999999; + imax = !isempty(param->max) ? atoi(param->max) : 1000000; + if (i < imin) { + _log("Value \"%s\" for option \"%s.%s\" is smaller than the minimum value \"%d\"\n", + str, opt->name, param->name, imin); + return NULL; + } + else if (i > imax) { + _log("Value \"%s\" for option \"%s.%s\" is larger than the maximum value \"%d\"\n", + str, opt->name, param->name, imax); + return NULL; + } + result = malloc(32); + snprintf(result, 32, "%d", i); + return result; + + case TYPE_FLOAT: + case TYPE_CURVE: + case TYPE_INVCURVE: + case TYPE_POINTS: + f = atof(str); + fmin = !isempty(param->min) ? atof(param->min) : -999999.0; + fmax = !isempty(param->max) ? atof(param->max) : 1000000.0; + if (f < fmin) { + _log("Value \"%s\" for option \"%s.%s\" is smaller than the minimum value \"%d\"\n", + str, opt->name, param->name, fmin); + return NULL; + } + else if (f > fmax) { + _log("Value \"%s\" for option \"%s.%s\" is larger than the maximum value \"%d\"\n", + str, opt->name, param->name, fmax); + return NULL; + } + result = malloc(32); + snprintf(result, 32, "%f", f); + return result; + + case TYPE_STRING: + case TYPE_PASSWORD: + case TYPE_PASSCODE: + if (param->allowedchars && + regexec(param->allowedchars, str, 0, NULL, 0) != 0) { + _log("Custom string \"%s\" for \"%s.%s\" contains illegal characters.\n", + str, opt->name, param->name); + return NULL; + } + if (param->allowedregexp && + regexec(param->allowedregexp, str, 0, NULL, 0) != 0) { + _log("Custom string \"%s\" for \"%s.%s\" does not match the allowed regexp.\n", + str, opt->name, param->name); + return NULL; + } + len = strlen(str); + if (!isempty(param->min) && len < atoi(param->min)) { + _log("Custom value \"%s\" is too short for \"%s.%s\".\n", + str, opt->name, param->name); + return NULL; + } + if (!isempty(param->max) && len > atoi(param->max)) { + _log("Custom value \"%s\" is too long for \"%s.%s\".\n", + str, opt->name, param->name); + return NULL; + } + return strdup(str); + } + return NULL; +} + +char * get_valid_param_string_int(option_t *opt, param_t *param, int value) +{ + char str[20]; + snprintf(str, 20, "%d", value); + return get_valid_param_string(opt, param, str); +} + +char * get_valid_param_string_float(option_t *opt, param_t *param, float value) +{ + char str[20]; + snprintf(str, 20, "%f", value); + return get_valid_param_string(opt, param, str); +} + +float convert_to_points(float f, const char *unit) +{ + if (!strcasecmp(unit, "pt")) + return roundf(f); + if (!strcasecmp(unit, "in")) + return roundf(f * 72.0); + if (!strcasecmp(unit, "cm")) + return roundf(f * 72.0 / 2.54); + if (!strcasecmp(unit, "mm")) + return roundf(f * 72.0 / 25.4); + + _log("Unknown unit: \"%s\"\n", unit); + return roundf(f); +} + +static char ** paramvalues_from_string(option_t *opt, const char *str) +{ + char ** paramvalues; + int n, i; + param_t *param; + char *copy, *cur, *p; + float width, height; + char unit[3]; + + if (!strcmp(opt->name, "PageSize")) + { + if (startswith(str, "Custom.")) + str = &str[7]; + /* 'unit' is optional, if it is not given, 'pt' is assumed */ + n = sscanf(str, "%fx%f%2s", &width, &height, unit); + if (n > 1) { + if (n == 3) { + width = convert_to_points(width, unit); + height = convert_to_points(height, unit); + } + paramvalues = calloc(opt->param_count, sizeof(char*)); + for (param = opt->paramlist, i = 0; param; param = param->next, i++) { + if (!strcasecmp(param->name, "width")) + paramvalues[i] = get_valid_param_string_int(opt, param, (int)width); + else if (!strcasecmp(param->name, "height")) + paramvalues[i] = get_valid_param_string_int(opt, param, (int)height); + else + paramvalues[i] = get_valid_param_string(opt, param, "0"); + if (!paramvalues[i]) { + free_paramvalues(opt, paramvalues); + return NULL; + } + } + return paramvalues; + } + } + + if (opt->param_count == 1) { + paramvalues = malloc(sizeof(char*)); + paramvalues[0] = get_valid_param_string(opt, opt->paramlist, + startswith(str, "Custom.") ? &str[7] : str); + if (!paramvalues[0]) { + free(paramvalues); + return NULL; + } + } + else { + if (!(p = strchr(str, '{'))) + return NULL; + paramvalues = calloc(opt->param_count, sizeof(char*)); + copy = strdup(p +1); + for (cur = strtok(copy, " \t}"); cur; cur = strtok(NULL, " \t}")) { + p = strchr(cur, '='); + if (!p) + continue; + *p++ = '\0'; + if ((param = option_find_param_index(opt, cur, &i))) + paramvalues[i] = get_valid_param_string(opt, param, p); + else + _log("Could not find param \"%s\" for option \"%s\"\n", + cur, opt->name); + } + free(copy); + + /* check if all params have been set */ + for (i = 0; i < opt->param_count; i++) { + if (!paramvalues[i]) { + free_paramvalues(opt, paramvalues); + return NULL; + } + } + } + return paramvalues; +} + +char * paramvalues_to_string(option_t *opt, char **paramvalues) +{ + int i; + param_t *param; + dstr_t *res = create_dstr(); + char *data; + + if (opt->param_count < 1) + return NULL; + + if (opt->param_count == 1) { + param = opt->paramlist; + dstrcpyf(res, "Custom.%s", paramvalues[0]); + } + else { + dstrcpyf(res, "{%s=%s", opt->paramlist->name, paramvalues[0]); + param = opt->paramlist->next; + i = 1; + while (param) { + dstrcatf(res, " %s=%s", param->name, paramvalues[i]); + i++; + param = param->next; + } + dstrcat(res, "}"); + } + /* only free dstr struct, NOT the string data */ + data = res->data; + free(res); + return data; +} + +char * get_valid_value_string(option_t *opt, const char *value) +{ + char *res; + choice_t *choice; + char **paramvalues; + + if (!value) + return NULL; + + if (startswith(value, "From") && option_is_composite(find_option(&value[4]))) + return strdup(value); + + if (opt->type == TYPE_BOOL) { + if (is_true_string(value)) + return strdup("1"); + else if (is_false_string(value)) + return strdup("0"); + else { + _log("Could not interpret \"%s\" as boolean value for option \"%s\".\n", value, opt->name); + return NULL; + } + } + + /* Check if "value" is a predefined choice (except for "Custom", which is + * not really a predefined choice, but an error if used without further + * parameters) */ + if (strcmp(value, "Custom") != 0 && (choice = option_find_choice(opt, value))) + return strdup(choice->value); + + if (opt->type == TYPE_ENUM) { + if (!strcasecmp(value, "none")) + return strdup("None"); + + /* + * CUPS assumes that options with the choices "Yes", "No", "On", "Off", + * "True", or "False" are boolean options and maps "-o Option=On" to + * "-o Option" and "-o Option=Off" to "-o noOption", which foomatic-rip + * maps to "0" and "1". So when "0" or "1" is unavailable in the + * option, we try "Yes", "No", "On", "Off", "True", and "False". + */ + if (is_true_string(value)) { + for (choice = opt->choicelist; choice; choice = choice->next) { + if (is_true_string(choice->value)) + return strdup(choice->value); + } + } + else if (is_false_string(value)) { + for (choice = opt->choicelist; choice; choice = choice->next) { + if (is_false_string(choice->value)) + return strdup(choice->value); + } + } + } + + /* Custom value */ + if (opt->paramlist) { + paramvalues = paramvalues_from_string(opt, value); + if (paramvalues) { + res = paramvalues_to_string(opt, paramvalues); + free(paramvalues); + return (startswith(res, "Custom.") ? strdup(&res[7]) : strdup(res)); + } + } + else if (opt->foomatic_param) + return get_valid_param_string(opt, opt->foomatic_param, + startswith(value, "Custom.") ? &value[7] : value); + + /* Return the default value */ + return NULL; +} + +/* Returns the current value for 'opt' in 'optionset'. */ +const char * option_get_value(option_t *opt, int optionset) +{ + value_t *val = option_find_value(opt, optionset); + return val ? val->value : NULL; +} + +/* Returns non-zero if the foomatic prototype should be used for that + * optionset, otherwise the custom_command will be used */ +int option_use_foomatic_prototype(option_t *opt) +{ + /* Only PostScript and JCL options can be CUPS custom options */ + if (!option_is_ps_command(opt) && !option_is_jcl_arg(opt)) + return 1; + + /* if only one of them exists, take that one */ + if (opt->custom_command && !opt->proto) + return 0; + if (!opt->custom_command && opt->proto) + return 1; + return 0; +} + +void build_foomatic_custom_command(dstr_t *cmd, option_t *opt, const char *values) +{ + if (!opt->proto && !strcmp(opt->name, "PageSize")) + { + choice_t *choice = option_find_choice(opt, "Custom"); + char ** paramvalues = paramvalues_from_string(opt, values); + char width[30], height[20]; + int pos; + + assert(choice); + + /* Get rid of the trailing ".00000", it confuses ghostscript */ + snprintf(width, 20, "%d", atoi(paramvalues[0])); + snprintf(height, 20, "%d", atoi(paramvalues[1])); + + dstrcpy(cmd, choice->command); + + if ((pos = dstrreplace(cmd, "%0", width, 0)) < 0) + pos = dstrreplace(cmd, "0", width, 0); + + if ((pos = dstrreplace(cmd, "%1", height, pos) < 0)) + dstrreplace(cmd, "0", height, pos); + + free_paramvalues(opt, paramvalues); + } + else + { + dstrcpy(cmd, opt->proto); + /* use replace instead of printf-style because opt->proto could contain + other format strings */ + dstrreplace(cmd, "%s", values, 0); + } +} + +void build_cups_custom_ps_command(dstr_t *cmd, option_t *opt, const char *values) +{ + param_t *param; + int i; + char **paramvalues = paramvalues_from_string(opt, values); + + dstrclear(cmd); + for (param = opt->paramlist, i = 0; param; param = param->next, i++) + dstrcatf(cmd, "%s ", paramvalues[i]); + dstrcat(cmd, opt->custom_command); + free_paramvalues(opt, paramvalues); +} + +void build_cups_custom_jcl_command(dstr_t *cmd, option_t *opt, const char *values) +{ + param_t *param; + int i; + char orderstr[8]; + char **paramvalues = paramvalues_from_string(opt, values); + + dstrcpy(cmd, opt->custom_command); + for (param = opt->paramlist, i = 0; param; param = param->next, i++) { + snprintf(orderstr, 8, "\\%d", param->order); + dstrreplace(cmd, orderstr, paramvalues[i], 0); + } + free_paramvalues(opt, paramvalues); +} + +int composite_get_command(dstr_t *cmd, option_t *opt, int optionset, int section) +{ + char *copy, *cur, *p; + option_t *dep; + const char * valstr; + dstr_t *depcmd; + + dstrclear(cmd); + if (!option_is_composite(opt)) + return 0; + + if (!(valstr = option_get_value(opt, optionset))) + return 0; + + depcmd = create_dstr(); + copy = strdup(valstr); + /* Dependent options have been set to the right value in composite_set_values, + so just find out which options depend on this composite and get their commands + for "optionset" with option_get_command() */ + for (cur = strtok(copy, " \t"); cur; cur = strtok(NULL, " \t")) { + dstrclear(depcmd); + if ((p = strchr(cur, '='))) { + *p++ = '\0'; + if ((dep = find_option(cur))) + option_get_command(depcmd, dep, optionset, section); + } + else if (startswith(cur, "no") || startswith(cur, "No")) { + if ((dep = find_option(&cur[2]))) + option_get_command(depcmd, dep, optionset, section); + } + else { + if ((dep = find_option(cur))) + option_get_command(depcmd, dep, optionset, section); + } + if (depcmd->len) + dstrcatf(cmd, "%s\n", depcmd->data); + } + free(copy); + free_dstr(depcmd); + return cmd->len != 0; +} + +int option_is_in_section(option_t *opt, int section) +{ + if (opt->section == section) + return 1; + if (opt->section == SECTION_ANYSETUP && (section == SECTION_PAGESETUP || section == SECTION_DOCUMENTSETUP)) + return 1; + return 0; +} + +int option_is_custom_value(option_t *opt, const char *value) +{ + if (opt->type == TYPE_BOOL || opt->type == TYPE_ENUM) + return 0; + + return !option_has_choice(opt, value); +} + +int option_get_command(dstr_t *cmd, option_t *opt, int optionset, int section) +{ + const char *valstr; + choice_t *choice = NULL; + + dstrclear(cmd); + + if (option_is_composite(opt)) + return composite_get_command(cmd, opt, optionset, section); + + if (section >= 0 && !option_is_in_section(opt, section)) + return 1; /* empty command for this section */ + + valstr = option_get_value(opt, optionset); + if (!valstr) + return 0; + + /* If the value is set to a predefined choice */ + choice = option_find_choice(opt, valstr); + if (choice) { + dstrcpy(cmd, choice->command); + return 1; + } + + /* Consider "None" as the empty string for string and password options */ + if ((opt->type == TYPE_STRING || opt->type == TYPE_PASSWORD) && + !strcasecmp(valstr, "None")) + valstr = ""; + + /* Custom value */ + if (option_use_foomatic_prototype(opt)) + build_foomatic_custom_command(cmd, opt, valstr); + else { + dstrcpy(cmd, opt->custom_command); + if ((option_get_section(opt) == SECTION_JCLSETUP) || + (opt->style == 'J')) + build_cups_custom_jcl_command(cmd, opt, valstr); + else + build_cups_custom_ps_command(cmd, opt, valstr); + } + + return cmd->len != 0; +} + +void composite_set_values(option_t *opt, int optionset, const char *values) +{ + char *copy, *cur, *p; + option_t *dep; + value_t *val; + + copy = strdup(values); + for (cur = strtok(copy, " \t"); cur; cur = strtok(NULL, " \t")) { + if ((p = strchr(cur, '='))) { + *p++ = '\0'; + if ((dep = find_option(cur))) { + val = option_assure_value(dep, optionset); + val->fromoption = opt; + val->value = get_valid_value_string(dep, p); + } + else + _log("Could not find option \"%s\" (set from composite \"%s\")", cur, opt->name); + } + else if (startswith(cur, "no") || startswith(cur, "No")) { + if ((dep = find_option(&cur[2]))) { + val = option_assure_value(dep, optionset); + val->fromoption = opt; + val->value = get_valid_value_string(dep, "0"); + } + } + else { + if ((dep = find_option(cur))) { + val = option_assure_value(dep, optionset); + val->fromoption = opt; + val->value = get_valid_value_string(dep, "1"); + } + } + } + free(copy); +} + +int option_set_value(option_t *opt, int optionset, const char *value) +{ + value_t *val = option_assure_value(opt, optionset); + char *newvalue; + choice_t *choice; + option_t *fromopt; + + newvalue = get_valid_value_string(opt, value); + if (!newvalue) + return 0; + + free(val->value); + val->value = NULL; + + if (startswith(newvalue, "From") && (fromopt = find_option(&newvalue[4])) && + option_is_composite(fromopt)) { + /* TODO only set the changed option, not all of them */ + choice = option_find_choice(fromopt, + option_get_value(fromopt, optionset)); + + composite_set_values(fromopt, optionset, choice->command); + } + else { + val->value = newvalue; + } + + if (option_is_composite(opt)) { + /* set dependent values */ + choice = option_find_choice(opt, value); + if (choice && !isempty(choice->command)) + composite_set_values(opt, optionset, choice->command); + } + return 1; +} + +int option_accepts_value(option_t *opt, const char *value) +{ + char *val = get_valid_value_string(opt, value); + if (!val) + return 0; + free(val); + return 1; +} + +int option_has_choice(option_t *opt, const char *choice) +{ + return option_find_choice(opt, choice) != NULL; +} + +const char * option_text(option_t *opt) +{ + if (isempty(opt->text)) + return opt->text; + return opt->text; +} + +int option_type(option_t *opt) +{ + return opt->type; +} + +void option_set_order(option_t *opt, double order) +{ + option_t *prev; + + /* remove opt from old position */ + if (opt == optionlist_sorted_by_order) + optionlist_sorted_by_order = opt->next_by_order; + else { + for (prev = optionlist_sorted_by_order; + prev && prev->next_by_order != opt; + prev = prev->next_by_order); + prev->next_by_order = opt->next_by_order; + } + + opt->order = order; + + /* insert into new position */ + if (!optionlist_sorted_by_order) + optionlist_sorted_by_order = opt; + else if (optionlist_sorted_by_order->order > opt->order) { + opt->next_by_order = optionlist_sorted_by_order; + optionlist_sorted_by_order = opt; + } + else { + for (prev = optionlist_sorted_by_order; + prev->next_by_order && prev->next_by_order->order < opt->order; + prev = prev->next_by_order); + opt->next_by_order = prev->next_by_order; + prev->next_by_order = opt; + } +} + +/* Set option from *FoomaticRIPOption keyword */ +void option_set_from_string(option_t *opt, const char *str) +{ + char type[32], style[32]; + double order; + int matches; + + matches = sscanf(str, "%31s %31s %c %lf", type, style, &opt->spot, &order); + if (matches < 3) { + _log("Can't read the value of *FoomaticRIPOption for \"%s\"", opt->name); + return; + } + opt->type = type_from_string(type); + opt->style = style_from_string(style); + + if (matches == 4) + option_set_order(opt, order); +} + +static choice_t * option_assure_choice(option_t *opt, const char *name) +{ + choice_t *choice, *last = NULL; + + for (choice = opt->choicelist; choice; choice = choice->next) { + if (!strcasecmp(choice->value, name)) + return choice; + last = choice; + } + if (!choice) { + choice = calloc(1, sizeof(choice_t)); + if (last) + last->next = choice; + else + opt->choicelist = choice; + strlcpy(choice->value, name, 128); + } + return choice; +} + +static void unhtmlify(char *dest, size_t size, const char *src) +{ + jobparams_t *job = get_current_job(); + char *pdest = dest; + const char *psrc = src; + const char *repl; + struct tm *t = localtime(&job->time); + char tmpstr[10]; + + while (*psrc && pdest - dest < size) { + + if (*psrc == '&') { + psrc++; + repl = NULL; + + /* Replace HTML/XML entities by the original characters */ + if (!prefixcmp(psrc, "apos;")) + repl = "\'"; + else if (!prefixcmp(psrc, "quot;")) + repl = "\""; + else if (!prefixcmp(psrc, "gt;")) + repl = ">"; + else if (!prefixcmp(psrc, "lt;")) + repl = "<"; + else if (!prefixcmp(psrc, "amp;")) + repl = "&"; + + /* Replace special entities by job->data */ + else if (!prefixcmp(psrc, "job;")) + repl = job->id; + else if (!prefixcmp(psrc, "user;")) + repl = job->user; + else if (!prefixcmp(psrc, "host;")) + repl = job->host; + else if (!prefixcmp(psrc, "title;")) + repl = job->title; + else if (!prefixcmp(psrc, "copies;")) + repl = job->copies; + else if (!prefixcmp(psrc, "rbinumcopies;")) { + if (job->rbinumcopies > 0) { + snprintf(tmpstr, 10, "%d", job->rbinumcopies); + repl = tmpstr; + } + else + repl = job->copies; + } + else if (!prefixcmp(psrc, "options;")) + repl = job->optstr->data; + else if (!prefixcmp(psrc, "year;")) { + sprintf(tmpstr, "%04d", t->tm_year + 1900); + repl = tmpstr; + } + else if (!prefixcmp(psrc, "month;")) { + sprintf(tmpstr, "%02d", t->tm_mon + 1); + repl = tmpstr; + } + else if (!prefixcmp(psrc, "date;")) { + sprintf(tmpstr, "%02d", t->tm_mday); + repl = tmpstr; + } + else if (!prefixcmp(psrc, "hour;")) { + sprintf(tmpstr, "%02d", t->tm_hour); + repl = tmpstr; + } + else if (!prefixcmp(psrc, "min;")) { + sprintf(tmpstr, "%02d", t->tm_min); + repl = tmpstr; + } + else if (!prefixcmp(psrc, "sec;")) { + sprintf(tmpstr, "%02d", t->tm_sec); + repl = tmpstr; + } + + if (repl) { + strncpy(pdest, repl, size - (pdest - dest)); + pdest += strlen(repl); + psrc = strchr(psrc, ';') +1; + } + else { + *pdest = '&'; + pdest++; + } + } + else { + *pdest = *psrc; + pdest++; + psrc++; + } + } + *pdest = '\0'; +} + +/* + * Checks whether 'code' contains active PostScript, i.e. not only comments + */ +static int contains_active_postscript(const char *code) +{ + char **line, **lines; + int contains_ps = 0; + + if (!(lines = argv_split(code, "\n", NULL))) + return 0; + + for (line = lines; *line && !contains_ps; line++) + contains_ps = !isempty(*line) && + !startswith(skip_whitespace(*line), "%"); + + argv_free(lines); + return contains_ps; +} + +void option_set_choice(option_t *opt, const char *name, const char *text, + const char *code) +{ + choice_t *choice; + + if (opt->type == TYPE_BOOL) { + if (is_true_string(name)) + choice = option_assure_choice(opt, "1"); + else + choice = option_assure_choice(opt, "0"); + } + else + choice = option_assure_choice(opt, name); + + if (text) + strlcpy(choice->text, text, 128); + + if (!code) + { + _log("Warning: No code for choice \"%s\" of option \"%s\"\n", + choice->text, opt->name); + return; + } + + if (!startswith(code, "%% FoomaticRIPOptionSetting")) + unhtmlify(choice->command, 1024, code); +} + +/* + * Parameters + */ + +int param_set_allowed_chars(param_t *param, const char *value) +{ + char rxstr[128], tmp[128]; + + param->allowedchars = malloc(sizeof(regex_t)); + unhtmlify(tmp, 128, value); + snprintf(rxstr, 128, "^[%s]*$", tmp); + if (regcomp(param->allowedchars, rxstr, 0) != 0) { + regfree(param->allowedchars); + param->allowedchars = NULL; + return 0; + } + return 1; +} + +int param_set_allowed_regexp(param_t *param, const char *value) +{ + char tmp[128]; + + param->allowedregexp = malloc(sizeof(regex_t)); + unhtmlify(tmp, 128, value); + if (regcomp(param->allowedregexp, tmp, 0) != 0) { + regfree(param->allowedregexp); + param->allowedregexp = NULL; + return 0; + } + return 1; +} + +void option_set_custom_command(option_t *opt, const char *cmd) +{ + size_t len = strlen(cmd) + 50; + free(opt->custom_command); + opt->custom_command = malloc(len); + unhtmlify(opt->custom_command, len, cmd); +} + +param_t * option_add_custom_param_from_string(option_t *opt, + const char *name, const char *text, const char *str) +{ + param_t *param = calloc(1, sizeof(param_t)); + param_t *p; + char typestr[33]; + int n; + + strlcpy(param->name, name, 128); + strlcpy(param->text, text, 128); + + n = sscanf(str, "%d%15s%19s%19s", + ¶m->order, typestr, param->min, param->max); + + if (n != 4) { + _log("Could not parse custom parameter for '%s'!\n", opt->name); + free(param); + return NULL; + } + + if (!strcmp(typestr, "curve")) + param->type = TYPE_CURVE; + else if (!strcmp(typestr, "invcurve")) + param->type = TYPE_INVCURVE; + else if (!strcmp(typestr, "int")) + param->type = TYPE_INT; + else if (!strcmp(typestr, "real")) + param->type = TYPE_FLOAT; + else if (!strcmp(typestr, "passcode")) + param->type = TYPE_PASSCODE; + else if (!strcmp(typestr, "password")) + param->type = TYPE_PASSWORD; + else if (!strcmp(typestr, "points")) + param->type = TYPE_POINTS; + else if (!strcmp(typestr, "string")) + param->type = TYPE_STRING; + else { + _log("Unknown custom parameter type for param '%s' for option '%s'\n", param->name, opt->name); + free(param); + return NULL; + } + + param->next = NULL; + + /* Insert param into opt->paramlist, sorted by order */ + if (!opt->paramlist) + opt->paramlist = param; + else if (opt->paramlist->order > param->order) { + param->next = opt->paramlist; + opt->paramlist = param; + } + else { + for (p = opt->paramlist; + p->next && p->next->order < param->order; + p = p->next); + param->next = p->next; + p->next = param; + } + + opt->param_count++; + return param; +} + +param_t * option_assure_foomatic_param(option_t *opt) +{ + param_t *param; + + if (opt->foomatic_param) + return opt->foomatic_param; + + param = calloc(1, sizeof(param_t)); + strcpy(param->name, "foomatic-param"); + param->order = 0; + param->type = opt->type; + + opt->foomatic_param = param; + return param; +} + + +/* + * Optionsets + */ + +const char * optionset_name(int idx) +{ + if (idx < 0 || idx >= optionset_count) { + _log("Optionset with index %d does not exist\n", idx); + return NULL; + } + return optionsets[idx]; +} + +int optionset(const char * name) +{ + int i; + + for (i = 0; i < optionset_count; i++) { + if (!strcmp(optionsets[i], name)) + return i; + } + + if (optionset_count == optionset_alloc) { + optionset_alloc *= 2; + optionsets = realloc(optionsets, optionset_alloc * sizeof(char *)); + for (i = optionset_count; i < optionset_alloc; i++) + optionsets[i] = NULL; + } + + optionsets[optionset_count] = strdup(name); + optionset_count++; + return optionset_count -1; +} + +void optionset_copy_values(int src_optset, int dest_optset) +{ + option_t *opt; + value_t *val; + + for (opt = optionlist; opt; opt = opt->next) { + for (val = opt->valuelist; val; val = val->next) { + if (val->optionset == src_optset) { + option_set_value(opt, dest_optset, val->value); + break; + } + } + } +} + +void optionset_delete_values(int optionset) +{ + option_t *opt; + value_t *val, *prev_val; + + for (opt = optionlist; opt; opt = opt->next) { + val = opt->valuelist; + prev_val = NULL; + while (val) { + if (val->optionset == optionset) { + if (prev_val) + prev_val->next = val->next; + else + opt->valuelist = val->next; + free_value(val); + val = prev_val ? prev_val->next : opt->valuelist; + break; + } else { + prev_val = val; + val = val->next; + } + } + } +} + +int optionset_equal(int optset1, int optset2, int exceptPS) +{ + option_t *opt; + const char *val1, *val2; + + for (opt = optionlist; opt; opt = opt->next) { + if (exceptPS && opt->style == 'G') + continue; + + val1 = option_get_value(opt, optset1); + val2 = option_get_value(opt, optset2); + + if (val1 && val2) { /* both entries exist */ + if (strcmp(val1, val2) != 0) + return 0; /* but aren't equal */ + } + else if (val1 || val2) /* one entry exists --> can't be equal */ + return 0; + /* If no extry exists, the non-existing entries + * are considered as equal */ + } + return 1; +} + +/* + * read_ppd_file() + */ +void read_ppd_file(const char *filename) +{ + FILE *fh; + char line [256]; /* PPD line length is max 255 (excl. \0) */ + char *p; + char key[128], name[64], text[64]; + dstr_t *value = create_dstr(); /* value can span multiple lines */ + double order; + value_t *val; + option_t *opt, *current_opt = NULL; + param_t *param; + + fh = fopen(filename, "r"); + if (!fh) { + _log("error opening %s\n", filename); + exit(EXIT_PRNERR_NORETRY_BAD_SETTINGS); + } + _log("Parsing PPD file ...\n"); + + dstrassure(value, 256); + + while (!feof(fh)) { + fgets(line, 256, fh); + + if (line[0] != '*' || startswith(line, "*%")) + continue; + + /* get the key */ + if (!(p = strchr(line, ':'))) + continue; + *p = '\0'; + + key[0] = name[0] = text[0] = '\0'; + sscanf(line, "*%127s%*[ \t]%63[^ \t/=)]%*1[/=]%63[^\n]", key, name, text); + + /* get the value */ + dstrclear(value); + sscanf(p +1, " %255[^\r\n]", value->data); + value->len = strlen(value->data); + if (!value->len) + _log("PPD: Missing value for key \"%s\"\n", line); + + while (1) { + /* "&&" is the continue-on-next-line marker */ + if (dstrendswith(value, "&&")) { + value->len -= 2; + value->data[value->len] = '\0'; + } + /* quoted but quotes are not yet closed */ + else if (value->data[0] == '\"' && !strchr(value->data +1, '\"')) + dstrcat(value, "\n"); /* keep newlines in quoted string*/ + /* not quoted, or quotes already closed */ + else + break; + + fgets(line, 256, fh); + dstrcat(value, line); + dstrremovenewline(value); + } + + /* remove quotes */ + if (value->data[0] == '\"') { + memmove(value->data, value->data +1, value->len +1); + p = strrchr(value->data, '\"'); + if (!p) { + _log("Invalid line: \"%s: ...\"\n", key); + continue; + } + *p = '\0'; + } + /* remove last newline */ + dstrremovenewline(value); + + /* process key/value pairs */ + if (strcmp(key, "NickName") == 0) { + unhtmlify(printer_model, 256, value->data); + } + else if (strcmp(key, "FoomaticIDs") == 0) { + /* *FoomaticIDs: <printer ID> <driver ID> */ + sscanf(value->data, "%*[ \t]%127[^ \t]%*[ \t]%127[^ \t\n]", + printer_id, driver); + } + else if (strcmp(key, "FoomaticRIPPostPipe") == 0) { + if (!postpipe) + postpipe = create_dstr(); + dstrassure(postpipe, value->len +128); + unhtmlify(postpipe->data, postpipe->alloc, value->data); + } + else if (strcmp(key, "FoomaticRIPCommandLine") == 0) { + unhtmlify(cmd, 1024, value->data); + } + else if (strcmp(key, "FoomaticRIPCommandLinePDF") == 0) { + unhtmlify(cmd_pdf, 1024, value->data); + } + else if (strcmp(key, "FoomaticNoPageAccounting") == 0) { + /* Boolean value */ + if (strcasecmp(value->data, "true") != 0) { + /* Driver is not compatible with page accounting according to the + Foomatic database, so turn it off for this driver */ + ps_accounting = 0; + _log("CUPS page accounting disabled by driver.\n"); + } + } + else if (!strcmp(key, "cupsFilter")) { + /* cupsFilter: <code> */ + /* only save the filter for "application/vnd.cups-raster" */ + if (prefixcmp(value->data, "application/vnd.cups-raster") == 0) { + p = strrchr(value->data, ' '); + if (p) + unhtmlify(cupsfilter, 256, p +1); + } + } + else if (startswith(key, "Custom") && !strcasecmp(name, "true")) { + /* Cups custom option: *CustomFoo True: "command" */ + if (startswith(&key[6], "JCL")) { + opt = assure_option(&key[9]); + opt->style = 'J'; + } + else + opt = assure_option(&key[6]); + option_set_custom_command(opt, value->data); + if (!strcmp(key, "CustomPageSize")) + option_set_custom_command(assure_option("PageRegion"), value->data); + } + else if (startswith(key, "ParamCustom")) { + /* Cups custom parameter: + *ParamCustomFoo Name/Text: order type minimum maximum */ + if (startswith(&key[11], "JCL")) + opt = assure_option(&key[14]); + else + opt = assure_option(&key[11]); + option_add_custom_param_from_string(opt, name, text, value->data); + } + else if (!strcmp(key, "OpenUI") || !strcmp(key, "JCLOpenUI")) { + /* "*[JCL]OpenUI *<option>[/<translation>]: <type>" */ + current_opt = assure_option(&name[1]); + if (!isempty(text)) + strlcpy(current_opt->text, text, 128); + if (startswith(key, "JCL")) + current_opt->style = 'J'; + /* Set the argument type only if not defined yet, + a definition in "*FoomaticRIPOption" has priority */ + if (current_opt->type == TYPE_NONE) + current_opt->type = type_from_string(value->data); + } + else if (!strcmp(key, "CloseUI") || !strcmp(key, "JCLCloseUI")) { + /* *[JCL]CloseUI: *<option> */ + if (!current_opt || !option_has_name(current_opt, value->data +1)) + _log("CloseUI found without corresponding OpenUI (%s).\n", value->data +1); + current_opt = NULL; + } + else if (!strcmp(key, "FoomaticRIPOption")) { + /* "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]" + <order> only used for 1-choice enum options */ + option_set_from_string(assure_option(name), value->data); + } + else if (!strcmp(key, "FoomaticRIPOptionPrototype")) { + /* "*FoomaticRIPOptionPrototype <option>: <code>" + Used for numerical and string options only */ + opt = assure_option(name); + opt->proto = malloc(128); + unhtmlify(opt->proto, 128, value->data); + } + else if (!strcmp(key, "FoomaticRIPOptionRange")) { + /* *FoomaticRIPOptionRange <option>: <min> <max> + Used for numerical options only */ + param = option_assure_foomatic_param(assure_option(name)); + sscanf(value->data, "%19s %19s", param->min, param->max); + } + else if (!strcmp(key, "FoomaticRIPOptionMaxLength")) { + /* "*FoomaticRIPOptionMaxLength <option>: <length>" + Used for string options only */ + param = option_assure_foomatic_param(assure_option(name)); + sscanf(value->data, "%19s", param->max); + } + else if (!strcmp(key, "FoomaticRIPOptionAllowedChars")) { + /* *FoomaticRIPOptionAllowedChars <option>: <code> + Used for string options only */ + param = option_assure_foomatic_param(assure_option(name)); + param_set_allowed_chars(param, value->data); + } + else if (!strcmp(key, "FoomaticRIPOptionAllowedRegExp")) { + /* "*FoomaticRIPOptionAllowedRegExp <option>: <code>" + Used for string options only */ + param = option_assure_foomatic_param(assure_option(name)); + param_set_allowed_regexp(param, value->data); + } + else if (!strcmp(key, "OrderDependency")) { + /* OrderDependency: <order> <section> *<option> */ + /* use 'text' to read <section> */ + sscanf(value->data, "%lf %63s *%63s", &order, text, name); + opt = assure_option(name); + opt->section = section_from_string(text); + option_set_order(opt, order); + } + + /* Default options are not yet validated (not all options/choices + have been read yet) */ + else if (!prefixcmp(key, "Default")) { + /* Default<option>: <value> */ + + /* TODO *DefaultColorSpace is a keyword and doesn't need to be extraced, does it? */ + if (!strcmp(key, "DefaultColorSpace")) + continue; + + opt = assure_option(&key[7]); + val = option_assure_value(opt, optionset("default")); + free(val->value); + val->value = strdup(value->data); + } + else if (!prefixcmp(key, "FoomaticRIPDefault")) { + /* FoomaticRIPDefault<option>: <value> + Used for numerical options only */ + opt = assure_option(&key[18]); + val = option_assure_value(opt, optionset("default")); + free(val->value); + val->value = strdup(value->data); + } + + /* Current argument */ + else if (current_opt && !strcmp(key, current_opt->name)) { + /* *<option> <choice>[/translation]: <code> */ + option_set_choice(current_opt, name, text, value->data); + } + else if (!strcmp(key, "FoomaticRIPOptionSetting")) { + /* "*FoomaticRIPOptionSetting <option>[=<choice>]: <code> + For boolean options <choice> is not given */ + option_set_choice(assure_option(name), + isempty(text) ? "true" : text, NULL, value->data); + } + + /* "*(Foomatic|)JCL(Begin|ToPSInterpreter|End|Prefix): <code>" + The printer supports PJL/JCL when there is such a line */ + else if (!prefixcmp(key, "JCLBegin") || + !prefixcmp(key, "FoomaticJCLBegin")) { + unhexify(jclbegin, 256, value->data); + if (!jclprefixset && strstr(jclbegin, "PJL") == NULL) + jclprefix[0] = '\0'; + } + else if (!prefixcmp(key, "JCLToPSInterpreter") || + !prefixcmp(key, "FoomaticJCLToPSInterpreter")) { + unhexify(jcltointerpreter, 256, value->data); + } + else if (!prefixcmp(key, "JCLEnd") || + !prefixcmp(key, "FoomaticJCLEnd")) { + unhexify(jclend, 256, value->data); + } + else if (!prefixcmp(key, "JCLPrefix") || + !prefixcmp(key, "FoomaticJCLPrefix")) { + unhexify(jclprefix, 256, value->data); + jclprefixset = 1; + } + else if (!prefixcmp(key, "% COMDATA #")) { + /* old foomtic 2.0.x PPD file */ + _log("You are using an old Foomatic 2.0 PPD file, which is no " + "longer supported by Foomatic >4.0. Exiting.\n"); + exit(1); /* TODO exit more gracefully */ + } + } + + fclose(fh); + free_dstr(value); + + /* Validate default options by resetting them with option_set_value() */ + for (opt = optionlist; opt; opt = opt->next) { + val = option_find_value(opt, optionset("default")); + if (val) { + /* if fromopt is set, this value has already been validated */ + if (!val->fromoption) + option_set_value(opt, optionset("default"), val->value); + } + else + /* Make sure that this option has a default choice, even if none is + defined in the PPD file */ + option_set_value(opt, optionset("default"), opt->choicelist->value); + } +} + +int ppd_supports_pdf() +{ + option_t *opt; + + /* If at least one option inserts PostScript code, we cannot support PDF */ + for (opt = optionlist; opt; opt = opt->next) + { + choice_t *choice; + + if (!option_is_ps_command(opt)) + continue; + + for (choice = opt->choicelist; choice; choice = choice->next) + if (contains_active_postscript(choice->command)) + return 0; + } + + if (!isempty(cmd_pdf)) + return 1; + + /* Ghostscript also accepts PDF, use that if it is in the normal command + * line */ + if (startswith(cmd, "gs")) + { + strncpy(cmd_pdf, cmd, 1024); + return 1; + } + + return 0; +} + +/* build a renderer command line, based on the given option set */ +int build_commandline(int optset, dstr_t *cmdline, int pdfcmdline) +{ + option_t *opt; + const char *userval; + char *s, *p; + dstr_t *cmdvar = create_dstr(); + dstr_t *open = create_dstr(); + dstr_t *close = create_dstr(); + char letters[] = "%A %B %C %D %E %F %G %H %I %J %K %L %M %W %X %Y %Z"; + int jcl = 0; + + dstr_t *local_jclprepend = create_dstr(); + + dstrclear(prologprepend); + dstrclear(setupprepend); + dstrclear(pagesetupprepend); + + if (cmdline) + dstrcpy(cmdline, pdfcmdline ? cmd_pdf : cmd); + + for (opt = optionlist_sorted_by_order; opt; opt = opt->next_by_order) { + /* composite options have no direct influence, and all their dependents + have already been set */ + if (option_is_composite(opt)) + continue; + + userval = option_get_value(opt, optset); + option_get_command(cmdvar, opt, optset, -1); + + /* Insert the built snippet at the correct place */ + if (option_is_ps_command(opt)) { + /* Place this Postscript command onto the prepend queue + for the appropriate section. */ + if (cmdvar->len) { + dstrcpyf(open, "[{\n%%%%BeginFeature: *%s ", opt->name); + if (opt->type == TYPE_BOOL) + dstrcatf(open, is_true_string(userval) ? "True\n" : "False\n"); + else + dstrcatf(open, "%s\n", userval); + dstrcpyf(close, "\n%%%%EndFeature\n} stopped cleartomark\n"); + + switch (option_get_section(opt)) { + case SECTION_PROLOG: + dstrcatf(prologprepend, "%s%s%s", open->data, cmdvar->data, close->data); + break; + + case SECTION_ANYSETUP: + if (optset != optionset("currentpage")) + dstrcatf(setupprepend, "%s%s%s", open->data, cmdvar->data, close->data); + else if (strcmp(option_get_value(opt, optionset("header")), userval) != 0) + dstrcatf(pagesetupprepend, "%s%s%s", open->data, cmdvar->data, close->data); + break; + + case SECTION_DOCUMENTSETUP: + dstrcatf(setupprepend, "%s%s%s", open->data, cmdvar->data, close->data); + break; + + case SECTION_PAGESETUP: + dstrcatf(pagesetupprepend, "%s%s%s", open->data, cmdvar->data, close->data); + break; + + case SECTION_JCLSETUP: /* PCL/JCL argument */ + s = malloc(cmdvar->len +1); + unhexify(s, cmdvar->len +1, cmdvar->data); + dstrcatf(local_jclprepend, "%s", s); + free(s); + break; + + default: + dstrcatf(setupprepend, "%s%s%s", open->data, cmdvar->data, close->data); + } + } + } + else if (option_is_jcl_arg(opt)) { + jcl = 1; + /* Put JCL commands onto JCL stack */ + if (cmdvar->len) { + char *s = malloc(cmdvar->len +1); + unhexify(s, cmdvar->len +1, cmdvar->data); + if (!startswith(cmdvar->data, jclprefix)) + dstrcatf(local_jclprepend, "%s%s\n", jclprefix, s); + else + dstrcat(local_jclprepend, s); + free(s); + } + } + else if (option_is_commandline_arg(opt) && cmdline) { + /* Insert the processed argument in the command line + just before every occurrence of the spot marker. */ + p = malloc(3); + snprintf(p, 3, "%%%c", opt->spot); + s = malloc(cmdvar->len +3); + snprintf(s, cmdvar->len +3, "%s%%%c", cmdvar->data, opt->spot); + dstrreplace(cmdline, p, s, 0); + free(p); + free(s); + } + + /* Insert option into command line of CUPS raster driver */ + if (cmdline && strstr(cmdline->data, "%Y")) { + if (isempty(userval)) + continue; + s = malloc(strlen(opt->name) + strlen(userval) + 20); + sprintf(s, "%s=%s %%Y", opt->name, userval); + dstrreplace(cmdline, "%Y", s, 0); + free(s); + } + } + + /* Tidy up after computing option statements for all of P, J, and C types: */ + + /* C type finishing */ + /* Pluck out all of the %n's from the command line prototype */ + if (cmdline) { + s = strtok(letters, " "); + do { + dstrreplace(cmdline, s, "", 0); + } while ((s = strtok(NULL, " "))); + } + + /* J type finishing */ + /* Compute the proper stuff to say around the job */ + if (jcl && !jobhasjcl) { + /* command to switch to the interpreter */ + dstrcatf(local_jclprepend, "%s", jcltointerpreter); + + /* Arrange for JCL RESET command at the end of job */ + dstrcpy(jclappend, jclend); + + argv_free(jclprepend); + jclprepend = argv_split(local_jclprepend->data, "\r\n", NULL); + } + + free_dstr(cmdvar); + free_dstr(open); + free_dstr(close); + free_dstr(local_jclprepend); + + return !isempty(cmd); +} + +/* if "comments" is set, add "%%BeginProlog...%%EndProlog" */ +void append_prolog_section(dstr_t *str, int optset, int comments) +{ + /* Start comment */ + if (comments) { + _log("\"Prolog\" section is missing, inserting it.\n"); + dstrcat(str, "%%BeginProlog\n"); + } + + /* Generate the option code (not necessary when CUPS is spooler and + PostScript data is not converted from PDF) */ + if ((spooler != SPOOLER_CUPS) || pdfconvertedtops) { + _log("Inserting option code into \"Prolog\" section.\n"); + build_commandline(optset, NULL, 0); + dstrcat(str, prologprepend->data); + } + + /* End comment */ + if (comments) + dstrcat(str, "%%EndProlog\n"); +} + +void append_setup_section(dstr_t *str, int optset, int comments) +{ + /* Start comment */ + if (comments) { + _log("\"Setup\" section is missing, inserting it.\n"); + dstrcat(str, "%%BeginSetup\n"); + } + + /* PostScript code to generate accounting messages for CUPS */ + if (spooler == SPOOLER_CUPS) { + _log("Inserting PostScript code for CUPS' page accounting\n"); + dstrcat(str, accounting_prolog); + } + + /* Generate the option code (not necessary when CUPS is spooler and + PostScript data is not converted from PDF) */ + if ((spooler != SPOOLER_CUPS) || pdfconvertedtops) { + _log("Inserting option code into \"Setup\" section.\n"); + build_commandline(optset, NULL, 0); + dstrcat(str, setupprepend->data); + } + + /* End comment */ + if (comments) + dstrcat(str, "%%EndSetup\n"); +} + +void append_page_setup_section(dstr_t *str, int optset, int comments) +{ + /* Start comment */ + if (comments) { + _log("\"PageSetup\" section is missing, inserting it.\n"); + dstrcat(str, "%%BeginPageSetup\n"); + } + + /* Generate the option code (not necessary when CUPS is spooler) */ + _log("Inserting option code into \"PageSetup\" section.\n"); + build_commandline(optset, NULL, 0); + dstrcat(str, pagesetupprepend->data); + + /* End comment */ + if (comments) + dstrcat(str, "%%EndPageSetup\n"); +} + + +typedef struct page_range { + short even, odd; + unsigned first, last; + struct page_range *next; +} page_range_t; + +static page_range_t * parse_page_ranges(const char *ranges) +{ + page_range_t *head, *tail = NULL; + char *tokens, *tok; + int cnt; + + tokens = strdup(ranges); + for (tok = strtok(tokens, ","); tok; tok = strtok(NULL, ",")) { + page_range_t *pr = calloc(1, sizeof(page_range_t)); + + if (startswith(tok, "even")) + pr->even = 1; + else if (startswith(tok, "odd")) + pr->odd = 1; + else if ((cnt = sscanf(tok, "%d-%d", &pr->first, &pr->last))) { + /* If 'last' has not been read, this could mean only one page (no + * hyphen) or all pages to the end */ + if (cnt == 1 && !endswith(tok, "-")) + pr->last = pr->first; + else if (cnt == 2 && pr->first > pr->last) { + unsigned tmp = pr->first; + pr->first = pr->last; + pr->last = tmp; + } + } + else { + printf("Invalid page range: %s\n", tok); + free(pr); + continue; + } + + if (tail) { + tail->next = pr; + tail = pr; + } + else + tail = head = pr; + } + + free(tokens); + return head; +} + +static void free_page_ranges(page_range_t *ranges) +{ + page_range_t *pr; + while (ranges) { + pr = ranges; + ranges = ranges->next; + free(pr); + } +} + +/* Parse a string containing page ranges and either check whether a + given page is in the ranges or, if the given page number is zero, + determine the score how specific this page range string is.*/ +int get_page_score(const char *pages, int page) +{ + page_range_t *ranges = parse_page_ranges(pages); + page_range_t *pr; + int totalscore = 0; + int pageinside = 0; + + for (pr = ranges; pr; pr = pr->next) { + if (pr->even) { + totalscore += 50000; + if (page % 2 == 0) + pageinside = 1; + } + else if (pr->odd) { + totalscore += 50000; + if (page % 2 == 1) + pageinside = 1; + } + else if (pr->first == pr->last) { /* Single page */ + totalscore += 1; + if (page == pr->first) + pageinside = 1; + } + else if (pr->last == 0) { /* To the end of the document */ + totalscore += 100000; + if (page >= pr->first) + pageinside = 1; + } + else { /* Sequence of pages */ + totalscore += pr->last - pr->first +1; + if (page >= pr->first && page <= pr->last) + pageinside = 1; + } + } + + free_page_ranges(ranges); + + if (page == 0 || pageinside) + return totalscore; + + return 0; +} + +/* Set the options for a given page */ +void set_options_for_page(int optset, int page) +{ + int score, bestscore; + option_t *opt; + value_t *val, *bestvalue; + const char *ranges; + const char *optsetname; + + for (opt = optionlist; opt; opt = opt->next) { + + bestscore = 10000000; + bestvalue = NULL; + for (val = opt->valuelist; val; val = val->next) { + + optsetname = optionset_name(val->optionset); + if (!startswith(optsetname, "pages:")) + continue; + + ranges = &optsetname[6]; /* after "pages:" */ + score = get_page_score(ranges, page); + if (score && score < bestscore) { + bestscore = score; + bestvalue = val; + } + } + + if (bestvalue) + option_set_value(opt, optset, bestvalue->value); + } +} + |