1 /*
2 * Copyright 2012 Canonical Ltd.
3 * Copyright 2013 ALT Linux, Andrew V. Stepanov <stanv@altlinux.com>
4 * Copyright 2018 Sahil Arora <sahilarora.535@gmail.com>
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 3, as published
8 * by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranties of
12 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13 * PURPOSE. See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdarg.h>
22 #include <math.h>
23
24 #ifndef HAVE_OPEN_MEMSTREAM
25 #include <fcntl.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <errno.h>
29 #endif
30
31 #include <cups/cups.h>
32 #include <cups/ppd.h>
33 #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 6)
34 #define HAVE_CUPS_1_7 1
35 #endif
36 #ifdef HAVE_CUPS_1_7
37 #include <cups/pwg.h>
38 #endif /* HAVE_CUPS_1_7 */
39
40 #include "banner.h"
41 #include "pdf.h"
42
43
get_float_option(const char * name,int noptions,cups_option_t * options,float def)44 static float get_float_option(const char *name,
45 int noptions,
46 cups_option_t *options,
47 float def)
48 {
49 const char *value = cupsGetOption(name, noptions, options);
50 return value ? atof(value) : def;
51 }
52
53
get_int_option(const char * name,int noptions,cups_option_t * options,int def)54 static int get_int_option(const char *name,
55 int noptions,
56 cups_option_t *options,
57 int def)
58 {
59 const char *value = cupsGetOption(name, noptions, options);
60 return value ? atoi(value) : def;
61 }
62
63
get_pagesize(ppd_file_t * ppd,int noptions,cups_option_t * options,float * width,float * length,float media_limits[4])64 static void get_pagesize(ppd_file_t *ppd,
65 int noptions,
66 cups_option_t *options,
67 float *width,
68 float *length,
69 float media_limits[4])
70 {
71 static const ppd_size_t defaultsize = {
72 0, /* marked */
73 "", /* name */
74 612.0, /* width */
75 792.0, /* length */
76 18.0, /* left */
77 36.0, /* bottom */
78 594.0, /* right */
79 756.0, /* top */
80 };
81 const ppd_size_t *pagesize;
82 #ifdef HAVE_CUPS_1_7
83 pwg_media_t *size_found; /* page size found for given name */
84 const char *val; /* Pointer into value */
85 char *ptr1, *ptr2, /* Pointer into string */
86 s[255]; /* Temporary string */
87 #endif /* HAVE_CUPS_1_7 */
88
89 if (!ppd || !(pagesize = ppdPageSize(ppd, NULL)))
90 pagesize = &defaultsize;
91
92 *width = pagesize->width;
93 *length = pagesize->length;
94
95 media_limits[0] = get_float_option("page-left",
96 noptions, options,
97 pagesize->left);
98 media_limits[1] = get_float_option("page-bottom",
99 noptions, options,
100 pagesize->bottom);
101 media_limits[2] = get_float_option("page-right",
102 noptions, options,
103 fabs(pagesize->right));
104 media_limits[3] = get_float_option("page-top",
105 noptions, options,
106 fabs(pagesize->top));
107
108 #ifdef HAVE_CUPS_1_7
109 if (!ppd) {
110 if ((val = cupsGetOption("media-size", noptions, options)) != NULL ||
111 (val = cupsGetOption("MediaSize", noptions, options)) != NULL ||
112 (val = cupsGetOption("page-size", noptions, options)) != NULL ||
113 (val = cupsGetOption("PageSize", noptions, options)) != NULL ||
114 (val = cupsGetOption("media", noptions, options)) != NULL) {
115 for (ptr1 = (char *)val; *ptr1;) {
116 for (ptr2 = s; *ptr1 && *ptr1 != ',' && (ptr2 - s) < (sizeof(s) - 1);)
117 *ptr2++ = *ptr1++;
118 *ptr2++ = '\0';
119 if (*ptr1 == ',')
120 ptr1 ++;
121 size_found = NULL;
122 if ((size_found = pwgMediaForPWG(s)) == NULL)
123 if ((size_found = pwgMediaForPPD(s)) == NULL)
124 size_found = pwgMediaForLegacy(s);
125 if (size_found != NULL) {
126 *width = size_found->width * 72.0 / 2540.0;
127 *length = size_found->length * 72.0 / 2540.0;
128 media_limits[2] += (*width - 612.0);
129 media_limits[3] += (*length - 792.0);
130 }
131 }
132 }
133 if ((val = cupsGetOption("media-left-margin", noptions, options))
134 != NULL)
135 media_limits[0] = atol(val) * 72.0 / 2540.0;
136 if ((val = cupsGetOption("media-bottom-margin", noptions, options))
137 != NULL)
138 media_limits[1] = atol(val) * 72.0 / 2540.0;
139 if ((val = cupsGetOption("media-right-margin", noptions, options))
140 != NULL)
141 media_limits[2] = *width - atol(val) * 72.0 / 2540.0;
142 if ((val = cupsGetOption("media-top-margin", noptions, options))
143 != NULL)
144 media_limits[3] = *length - atol(val) * 72.0 / 2540.0;
145 }
146 #endif /* HAVE_CUPS_1_7 */
147 }
148
149
duplex_marked(ppd_file_t * ppd,int noptions,cups_option_t * options)150 static int duplex_marked(ppd_file_t *ppd,
151 int noptions,
152 cups_option_t *options)
153 {
154 const char *val; /* Pointer into value */
155 return
156 (ppd &&
157 (ppdIsMarked(ppd, "Duplex", "DuplexNoTumble") ||
158 ppdIsMarked(ppd, "Duplex", "DuplexTumble") ||
159 ppdIsMarked(ppd, "JCLDuplex", "DuplexNoTumble") ||
160 ppdIsMarked(ppd, "JCLDuplex", "DuplexTumble") ||
161 ppdIsMarked(ppd, "EFDuplex", "DuplexNoTumble") ||
162 ppdIsMarked(ppd, "EFDuplex", "DuplexTumble") ||
163 ppdIsMarked(ppd, "KD03Duplex", "DuplexNoTumble") ||
164 ppdIsMarked(ppd, "KD03Duplex", "DuplexTumble"))) ||
165 ((val = cupsGetOption("Duplex", noptions, options))
166 != NULL &&
167 (!strcasecmp(val, "DuplexNoTumble") ||
168 !strcasecmp(val, "DuplexTumble"))) ||
169 ((val = cupsGetOption("sides", noptions, options))
170 != NULL &&
171 (!strcasecmp(val, "two-sided-long-edge") ||
172 !strcasecmp(val, "two-sided-short-edge")));
173 }
174
175
info_linef(FILE * s,const char * key,const char * valuefmt,...)176 static void info_linef(FILE *s,
177 const char *key,
178 const char *valuefmt, ...)
179 {
180 va_list ap;
181
182 va_start(ap, valuefmt);
183 fprintf(s, "(%s: ", key);
184 vfprintf(s, valuefmt, ap);
185 fprintf(s, ") Tj T*\n");
186 va_end(ap);
187 }
188
189
info_line(FILE * s,const char * key,const char * value)190 static void info_line(FILE *s,
191 const char *key,
192 const char *value)
193 {
194 info_linef(s, key, "%s", value);
195 }
196
197
info_line_time(FILE * s,const char * key,const char * timestamp)198 static void info_line_time(FILE *s,
199 const char *key,
200 const char *timestamp)
201 {
202 char buf[40];
203 time_t time;
204
205 if (timestamp) {
206 time = (time_t)atoll(timestamp);
207 strftime(buf, sizeof buf, "%c", localtime(&time));
208 info_line(s, key, buf);
209 }
210 else
211 info_line(s, key, "unknown");
212 }
213
human_time(const char * timestamp)214 static const char *human_time(const char *timestamp)
215 {
216 time_t time;
217 int size = sizeof(char) * 40;
218 char *buf = malloc(size);
219 strcpy(buf, "unknown");
220
221 if (timestamp) {
222 time = (time_t)atoll(timestamp);
223 strftime(buf, size, "%c", localtime(&time));
224 }
225
226 return buf;
227 }
228
229 /*
230 * Add new key & value.
231 */
add_opt(opt_t * in_opt,const char * key,const char * val)232 static opt_t* add_opt(opt_t *in_opt, const char *key, const char *val) {
233 if ( ! key || ! val ) {
234 return in_opt;
235 }
236
237 if ( !strlen(key) || !strlen(val) ) {
238 return in_opt;
239 }
240
241 opt_t *entry = malloc(sizeof(opt_t));
242 if ( ! entry ) {
243 return in_opt;
244 }
245
246 entry->key = key;
247 entry->val = val;
248 entry->next = in_opt;
249
250 return entry;
251 }
252
253 /*
254 * Collect all known info about current task.
255 * Bond PDF form field name with collected info.
256 *
257 * Create PDF form's field names according above.
258 */
get_known_opts(ppd_file_t * ppd,const char * jobid,const char * user,const char * jobtitle,int noptions,cups_option_t * options)259 opt_t *get_known_opts(
260 ppd_file_t *ppd,
261 const char *jobid,
262 const char *user,
263 const char *jobtitle,
264 int noptions,
265 cups_option_t *options) {
266
267 ppd_attr_t *attr;
268 opt_t *opt = NULL;
269
270 /* Job ID */
271 opt = add_opt(opt, "job-id", jobid);
272
273 /* Job title */
274 opt = add_opt(opt, "job-title", jobtitle);
275
276 /* Printer by */
277 opt = add_opt(opt, "user", user);
278
279 /* Printer name */
280 opt = add_opt(opt, "printer-name", getenv("PRINTER"));
281
282 /* Printer info */
283 opt = add_opt(opt, "printer-info", getenv("PRINTER_INFO"));
284
285 /* Time at creation */
286 opt = add_opt(opt, "time-at-creation",
287 human_time(cupsGetOption("time-at-creation", noptions, options)));
288
289 /* Processing time */
290 opt = add_opt(opt, "time-at-processing",
291 human_time(cupsGetOption("time-at-processing", noptions, options)));
292
293 /* Billing information */
294 opt = add_opt(opt, "job-billing",
295 cupsGetOption("job-billing", noptions, options));
296
297 /* Source hostname */
298 opt = add_opt(opt, "job-originating-host-name",
299 cupsGetOption("job-originating-host-name", noptions, options));
300
301 /* Banner font */
302 opt = add_opt(opt, "banner-font",
303 cupsGetOption("banner-font", noptions, options));
304
305 /* Banner font size */
306 opt = add_opt(opt, "banner-font-size",
307 cupsGetOption("banner-font-size", noptions, options));
308
309 /* Job UUID */
310 opt = add_opt(opt, "job-uuid",
311 cupsGetOption("job-uuid", noptions, options));
312
313 /* Security context */
314 opt = add_opt(opt, "security-context",
315 cupsGetOption("security-context", noptions, options));
316
317 /* Security context range part */
318 opt = add_opt(opt, "security-context-range",
319 cupsGetOption("security-context-range", noptions, options));
320
321 /* Security context current range part */
322 const char * full_range = cupsGetOption("security-context-range", noptions, options);
323 if ( full_range ) {
324 size_t cur_size = strcspn(full_range, "-");
325 char * cur_range = strndup(full_range, cur_size);
326 opt = add_opt(opt, "security-context-range-cur", cur_range);
327 }
328
329 /* Security context type part */
330 opt = add_opt(opt, "security-context-type",
331 cupsGetOption("security-context-type", noptions, options));
332
333 /* Security context role part */
334 opt = add_opt(opt, "security-context-role",
335 cupsGetOption("security-context-role", noptions, options));
336
337 /* Security context user part */
338 opt = add_opt(opt, "security-context-user",
339 cupsGetOption("security-context-user", noptions, options));
340
341 if (ppd) {
342 /* Driver */
343 opt = add_opt(opt, "driver", ppd->pcfilename);
344
345 /* Driver version */
346 opt = add_opt(opt, "driver-version",
347 (attr = ppdFindAttr(ppd, "FileVersion", NULL)) ?
348 attr->value : "");
349
350 /* Make and model */
351 opt = add_opt(opt, "make-and-model", ppd->nickname);
352 }
353
354 return opt;
355 }
356
generate_banner_pdf(banner_t * banner,ppd_file_t * ppd,const char * jobid,const char * user,const char * jobtitle,int noptions,cups_option_t * options)357 static int generate_banner_pdf(banner_t *banner,
358 ppd_file_t *ppd,
359 const char *jobid,
360 const char *user,
361 const char *jobtitle,
362 int noptions,
363 cups_option_t *options)
364 {
365 char *buf;
366 size_t len;
367 FILE *s;
368 pdf_t *doc;
369 float page_width, page_length;
370 float media_limits[4];
371 float page_scale;
372 ppd_attr_t *attr;
373 unsigned copies;
374 #ifndef HAVE_OPEN_MEMSTREAM
375 struct stat st;
376 #endif
377
378 if (!(doc = pdf_load_template(banner->template_file)))
379 return 1;
380
381 get_pagesize(ppd, noptions, options,
382 &page_width, &page_length, media_limits);
383
384 pdf_resize_page (doc, 1, page_width, page_length, &page_scale);
385
386 pdf_add_type1_font(doc, 1, "Courier");
387
388 #ifdef HAVE_OPEN_MEMSTREAM
389 s = open_memstream(&buf, &len);
390 #else
391 if ((s = tmpfile()) == NULL) {
392 fprintf (stderr, "ERROR: bannertopdf: cannot create temp file: %s\n",
393 strerror (errno));
394 return 1;
395 }
396 #endif
397
398 if (banner->infos & INFO_IMAGEABLE_AREA) {
399 fprintf(s, "q\n");
400 fprintf(s, "0 0 0 RG\n");
401 fprintf(s, "%f %f %f %f re S\n", media_limits[0] + 1.0,
402 media_limits[1] + 1.0,
403 media_limits[2] - media_limits[0] - 2.0,
404 media_limits[3] - media_limits[1] - 2.0);
405 fprintf(s, "Q\n");
406 }
407
408 fprintf(s, "%f 0 0 %f 0 0 cm\n", page_scale, page_scale);
409
410 fprintf(s, "0 0 0 rg\n");
411 fprintf(s, "BT\n");
412 fprintf(s, "/bannertopdf-font 14 Tf\n");
413 fprintf(s, "83.662 335.0 Td\n");
414 fprintf(s, "17 TL\n");
415
416 if (banner->infos & INFO_IMAGEABLE_AREA)
417 info_linef(s, "Media Limits", "%.2f x %.2f to %.2f x %.2f inches",
418 media_limits[0] / 72.0,
419 media_limits[1] / 72.0,
420 media_limits[2] / 72.0,
421 media_limits[3] / 72.0);
422
423 if (banner->infos & INFO_JOB_BILLING)
424 info_line(s, "Billing Information\n",
425 cupsGetOption("job-billing", noptions, options));
426
427 if (banner->infos & INFO_JOB_ID)
428 info_linef(s, "Job ID", "%s-%s", getenv("PRINTER"), jobid);
429
430 if (banner->infos & INFO_JOB_NAME)
431 info_line(s, "Job Title", jobtitle);
432
433 if (banner->infos & INFO_JOB_ORIGINATING_HOST_NAME)
434 info_line(s, "Printed from",
435 cupsGetOption("job-originating-host-name",
436 noptions, options));
437
438 if (banner->infos & INFO_JOB_ORIGINATING_USER_NAME)
439 info_line(s, "Printed by", user);
440
441 if (banner->infos & INFO_JOB_UUID)
442 info_line(s, "Job UUID",
443 cupsGetOption("job-uuid", noptions, options));
444
445 if (ppd && banner->infos & INFO_PRINTER_DRIVER_NAME)
446 info_line(s, "Driver", ppd->pcfilename);
447
448 if (ppd && banner->infos & INFO_PRINTER_DRIVER_VERSION)
449 info_line(s, "Driver Version",
450 (attr = ppdFindAttr(ppd, "FileVersion", NULL)) ? attr->value : "");
451
452 if (banner->infos & INFO_PRINTER_INFO)
453 info_line(s, "Description", getenv("PRINTER_INFO"));
454
455 if (banner->infos & INFO_PRINTER_LOCATION)
456 info_line(s, "Printer Location", getenv("PRINTER_LOCATION"));
457
458 if (ppd && banner->infos & INFO_PRINTER_MAKE_AND_MODEL)
459 info_line(s, "Make and Model", ppd->nickname);
460
461 if (banner->infos & INFO_PRINTER_NAME)
462 info_line(s, "Printer", getenv("PRINTER"));
463
464 if (banner->infos & INFO_TIME_AT_CREATION)
465 info_line_time(s, "Created at",
466 cupsGetOption("time-at-creation", noptions, options));
467
468 if (banner->infos & INFO_TIME_AT_PROCESSING)
469 info_line_time(s, "Printed at",
470 cupsGetOption("time-at-processing", noptions, options));
471
472 fprintf(s, "ET\n");
473 #ifndef HAVE_OPEN_MEMSTREAM
474 fflush (s);
475 if (fstat (fileno (s), &st) < 0) {
476 fprintf (stderr, "ERROR: bannertopdf: cannot fstat(): %s\n", strerror(errno));
477 return 1 ;
478 }
479 fseek (s, 0L, SEEK_SET);
480 if ((buf = malloc(st.st_size + 1)) == NULL) {
481 fprintf (stderr, "ERROR: bannertopdf: cannot malloc(): %s\n", strerror(errno));
482 return 1 ;
483 }
484 size_t nbytes = fread (buf, 1, st.st_size, s);
485 buf[st.st_size] = '\0';
486 len = strlen (buf);
487 #endif /* !HAVE_OPEN_MEMSTREAM */
488 fclose(s);
489
490 opt_t * known_opts = get_known_opts(ppd,
491 jobid,
492 user,
493 jobtitle,
494 noptions,
495 options);
496
497 /*
498 * Try to find a PDF form in PDF template and fill it.
499 */
500 int ret = pdf_fill_form(doc, known_opts);
501
502 /*
503 * Could we fill a PDF form? If no, just add PDF stream.
504 */
505 if ( ! ret ) {
506 pdf_prepend_stream(doc, 1, buf, len);
507 }
508
509 copies = get_int_option("number-up", noptions, options, 1);
510 if (duplex_marked(ppd, noptions, options))
511 copies *= 2;
512
513 if (copies > 1)
514 pdf_duplicate_page(doc, 1, copies - 1);
515
516 pdf_write(doc, stdout);
517
518 opt_t * opt_current = known_opts;
519 opt_t * opt_next = NULL;
520 while (opt_current != NULL)
521 {
522 opt_next = opt_current->next;
523 free(opt_current);
524 opt_current = opt_next;
525 }
526 free(buf);
527 pdf_free(doc);
528 return 0;
529 }
530
531
main(int argc,char * argv[])532 int main(int argc, char *argv[])
533 {
534 banner_t *banner;
535 int noptions;
536 cups_option_t *options;
537 ppd_file_t *ppd;
538 int ret;
539
540 if (argc < 6) {
541 fprintf(stderr,
542 "Usage: %s job-id user job-title nr-copies options [file]\n",
543 argv[0]);
544 return 1;
545 }
546
547 ppd = ppdOpenFile(getenv("PPD"));
548 if (!ppd)
549 fprintf(stderr, "DEBUG: Could not open PPD file '%s'\n", getenv("PPD"));
550
551 noptions = cupsParseOptions(argv[5], 0, &options);
552 if (ppd) {
553 ppdMarkDefaults(ppd);
554 cupsMarkOptions(ppd, noptions, options);
555 }
556
557 banner = banner_new_from_file(argc == 7 ? argv[6] : "-", &noptions, &options);
558 if (!banner) {
559 fprintf(stderr, "Error: could not read banner file\n");
560 return 1;
561 }
562
563 ret = generate_banner_pdf(banner,
564 ppd,
565 argv[1],
566 argv[2],
567 argv[3],
568 noptions,
569 options);
570
571 banner_free(banner);
572 cupsFreeOptions(noptions, options);
573 return ret;
574 }
575