Fri, 14 Mar 2003 08:24:37 +0000
finished implementing page labels.
1 /*
2 * tumble: build a PDF file from image files
3 *
4 * Main program
5 * $Id: tumble.c,v 1.34 2003/03/14 00:24:37 eric Exp $
6 * Copyright 2001, 2002, 2003 Eric Smith <eric@brouhaha.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation. Note that permission is
11 * not granted to redistribute this program under the terms of any
12 * other version of the General Public License.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
22 */
25 #include <stdarg.h>
26 #include <stdbool.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
33 #include <tiffio.h>
34 #define TIFF_REVERSE_BITS
36 #include "bitblt.h"
37 #include "semantics.h"
38 #include "parser.tab.h"
39 #include "tumble.h"
40 #include "pdf.h"
43 #define MAX_INPUT_FILES 5000
45 #define POINTS_PER_INCH 72
47 /* page size limited by Acrobat Reader to 45 inches on a side */
48 #define PAGE_MAX_INCHES 45
49 #define PAGE_MAX_POINTS (PAGE_MAX_INCHES * POINTS_PER_INCH)
52 typedef struct output_file_t
53 {
54 struct output_file_t *next;
55 char *name;
56 pdf_file_handle pdf;
57 } output_file_t;
60 int verbose;
63 char *in_filename;
64 TIFF *in;
65 output_file_t *output_files;
66 output_file_t *out;
69 char *progname;
72 bool close_tiff_input_file (void);
73 bool close_pdf_output_files (void);
76 void usage (void)
77 {
78 fprintf (stderr, "\n");
79 fprintf (stderr, "tumble - Copyright 2001-2003 Eric Smith <eric@brouhaha.com>\n");
80 fprintf (stderr, "http://tumble.brouhaha.com/\n");
81 fprintf (stderr, "\n");
82 fprintf (stderr, "usage:\n");
83 fprintf (stderr, " %s [options] -s spec\n", progname);
84 fprintf (stderr, " %s [options] <input.tif>... -o <output.pdf>\n", progname);
85 fprintf (stderr, "options:\n");
86 fprintf (stderr, " -v verbose\n");
87 fprintf (stderr, " -b fmt create bookmarks\n");
88 fprintf (stderr, "bookmark format:\n");
89 fprintf (stderr, " %%F file name (sans suffix)\n");
90 fprintf (stderr, " %%p page number\n");
91 }
94 /* generate fatal error message to stderr, doesn't return */
95 void fatal (int ret, char *format, ...)
96 {
97 va_list ap;
99 fprintf (stderr, "fatal error");
100 if (format)
101 {
102 fprintf (stderr, ": ");
103 va_start (ap, format);
104 vfprintf (stderr, format, ap);
105 va_end (ap);
106 }
107 else
108 fprintf (stderr, "\n");
109 if (ret == 1)
110 usage ();
111 close_tiff_input_file ();
112 close_pdf_output_files ();
113 exit (ret);
114 }
117 bool close_tiff_input_file (void)
118 {
119 if (in)
120 {
121 free (in_filename);
122 TIFFClose (in);
123 }
124 in = NULL;
125 in_filename = NULL;
126 return (1);
127 }
130 bool open_tiff_input_file (char *name)
131 {
132 if (in)
133 {
134 if (strcmp (name, in_filename) == 0)
135 return (1);
136 close_tiff_input_file ();
137 }
138 in_filename = strdup (name);
139 if (! in_filename)
140 {
141 fprintf (stderr, "can't strdup input filename '%s'\n", name);
142 return (0);
143 }
144 in = TIFFOpen (name, "r");
145 if (! in)
146 {
147 fprintf (stderr, "can't open input file '%s'\n", name);
148 free (in_filename);
149 return (0);
150 }
151 return (1);
152 }
155 bool close_pdf_output_files (void)
156 {
157 output_file_t *o, *n;
159 for (o = output_files; o; o = n)
160 {
161 n = o->next;
162 pdf_close (o->pdf);
163 free (o->name);
164 free (o);
165 }
166 out = NULL;
167 output_files = NULL;
168 return (1);
169 }
171 bool open_pdf_output_file (char *name,
172 pdf_file_attributes_t *attributes)
173 {
174 output_file_t *o;
176 if (out && (strcmp (name, out->name) == 0))
177 return (1);
178 for (o = output_files; o; o = o->next)
179 if (strcmp (name, o->name) == 0)
180 {
181 out = o;
182 return (1);
183 }
184 o = calloc (1, sizeof (output_file_t));
185 if (! o)
186 {
187 fprintf (stderr, "can't calloc output file struct for '%s'\n", name);
188 return (0);
189 }
191 o->name = strdup (name);
192 if (! o->name)
193 {
194 fprintf (stderr, "can't strdup output filename '%s'\n", name);
195 free (o);
196 return (0);
197 }
199 o->pdf = pdf_create (name, (attributes->has_bookmarks ?
200 PDF_PAGE_MODE_USE_OUTLINES :
201 PDF_PAGE_MODE_USE_NONE));
202 if (! o->pdf)
203 {
204 fprintf (stderr, "can't open output file '%s'\n", name);
205 free (o->name);
206 free (o);
207 return (0);
208 }
210 if (attributes->author)
211 pdf_set_author (o->pdf, attributes->author);
212 if (attributes->creator)
213 pdf_set_creator (o->pdf, attributes->creator);
214 if (attributes->title)
215 pdf_set_title (o->pdf, attributes->title);
216 if (attributes->subject)
217 pdf_set_subject (o->pdf, attributes->subject);
218 if (attributes->keywords)
219 pdf_set_keywords (o->pdf, attributes->keywords);
221 /* prepend new output file onto list */
222 o->next = output_files;
223 output_files = o;
225 out = o;
226 return (1);
227 }
230 /* frees original! */
231 static Bitmap *resize_bitmap (Bitmap *src,
232 double x_resolution,
233 double y_resolution,
234 input_attributes_t input_attributes)
235 {
236 Rect src_rect;
237 Point dest_min;
238 Bitmap *dest;
240 int width_pixels = input_attributes.page_size.width * x_resolution;
241 int height_pixels = input_attributes.page_size.height * y_resolution;
243 src_rect.min.x = (rect_width (& src->rect) - width_pixels) / 2;
244 src_rect.min.y = (rect_height (& src->rect) - height_pixels) / 2;
245 src_rect.max.x = src_rect.min.x + width_pixels;
246 src_rect.max.y = src_rect.min.y + height_pixels;
248 dest_min.x = 0;
249 dest_min.y = 0;
251 dest = bitblt (src, & src_rect, NULL, & dest_min, TF_SRC, 0);
252 free_bitmap (src);
253 return (dest);
254 }
257 /* "in place" rotation */
258 static void rotate_bitmap (Bitmap *src,
259 input_attributes_t input_attributes)
260 {
261 switch (input_attributes.rotation)
262 {
263 case 0: break;
264 case 90: rot_90 (src); break;
265 case 180: rot_180 (src); break;
266 case 270: rot_270 (src); break;
267 default:
268 fprintf (stderr, "rotation must be 0, 90, 180, or 270\n");
269 }
270 }
273 #define SWAP(type,a,b) do { type temp; temp = a; a = b; b = temp; } while (0)
276 bool last_tiff_page (void)
277 {
278 return (TIFFLastDirectory (in));
279 }
282 static pdf_page_handle process_tiff_page (int image, /* range 1 .. n */
283 input_attributes_t input_attributes)
284 {
285 uint32_t image_length, image_width;
286 uint32_t dest_image_length, dest_image_width;
287 #ifdef CHECK_DEPTH
288 uint32_t image_depth;
289 #endif
291 uint16_t samples_per_pixel;
292 uint16_t bits_per_sample;
293 uint16_t planar_config;
295 uint16_t resolution_unit;
296 float x_resolution, y_resolution;
297 double dest_x_resolution, dest_y_resolution;
299 double width_points, height_points; /* really 1/72 inch units rather than
300 points */
302 Rect rect;
303 Bitmap *bitmap = NULL;
305 int row;
307 pdf_page_handle page = NULL;
309 if (! TIFFSetDirectory (in, image - 1))
310 {
311 fprintf (stderr, "can't find page %d of input file\n", image);
312 goto fail;
313 }
314 if (1 != TIFFGetField (in, TIFFTAG_IMAGELENGTH, & image_length))
315 {
316 fprintf (stderr, "can't get image length\n");
317 goto fail;
318 }
319 if (1 != TIFFGetField (in, TIFFTAG_IMAGEWIDTH, & image_width))
320 {
321 fprintf (stderr, "can't get image width\n");
322 goto fail;
323 }
325 if (1 != TIFFGetField (in, TIFFTAG_SAMPLESPERPIXEL, & samples_per_pixel))
326 {
327 fprintf (stderr, "can't get samples per pixel\n");
328 goto fail;
329 }
331 #ifdef CHECK_DEPTH
332 if (1 != TIFFGetField (in, TIFFTAG_IMAGEDEPTH, & image_depth))
333 {
334 fprintf (stderr, "can't get image depth\n");
335 goto fail;
336 }
337 #endif
339 if (1 != TIFFGetField (in, TIFFTAG_BITSPERSAMPLE, & bits_per_sample))
340 {
341 fprintf (stderr, "can't get bits per sample\n");
342 goto fail;
343 }
345 if (1 != TIFFGetField (in, TIFFTAG_PLANARCONFIG, & planar_config))
346 planar_config = 1;
348 if (1 != TIFFGetField (in, TIFFTAG_RESOLUTIONUNIT, & resolution_unit))
349 resolution_unit = 2;
350 if (1 != TIFFGetField (in, TIFFTAG_XRESOLUTION, & x_resolution))
351 x_resolution = 300;
352 if (1 != TIFFGetField (in, TIFFTAG_YRESOLUTION, & y_resolution))
353 y_resolution = 300;
355 if (samples_per_pixel != 1)
356 {
357 fprintf (stderr, "samples per pixel %u, must be 1\n", samples_per_pixel);
358 goto fail;
359 }
361 #ifdef CHECK_DEPTH
362 if (image_depth != 1)
363 {
364 fprintf (stderr, "image depth %u, must be 1\n", image_depth);
365 goto fail;
366 }
367 #endif
369 if (bits_per_sample != 1)
370 {
371 fprintf (stderr, "bits per sample %u, must be 1\n", bits_per_sample);
372 goto fail;
373 }
375 if (planar_config != 1)
376 {
377 fprintf (stderr, "planar config %u, must be 1\n", planar_config);
378 goto fail;
379 }
381 if (input_attributes.has_resolution)
382 {
383 x_resolution = input_attributes.x_resolution;
384 y_resolution = input_attributes.y_resolution;
385 }
387 if ((input_attributes.rotation == 90) || (input_attributes.rotation == 270))
388 {
389 dest_image_width = image_length;
390 dest_image_length = image_width;
391 dest_x_resolution = y_resolution;
392 dest_y_resolution = x_resolution;
393 SWAP (double, width_points, height_points); /* $$$ not yet set!!! */
394 }
395 else
396 {
397 dest_image_width = image_width;
398 dest_image_length = image_length;
399 dest_x_resolution = x_resolution;
400 dest_y_resolution = y_resolution;
401 }
403 rect.min.x = 0;
404 rect.min.y = 0;
405 rect.max.x = image_width;
406 rect.max.y = image_length;
408 bitmap = create_bitmap (& rect);
410 if (! bitmap)
411 {
412 fprintf (stderr, "can't allocate bitmap\n");
413 goto fail;
414 }
416 for (row = 0; row < image_length; row++)
417 if (1 != TIFFReadScanline (in,
418 bitmap->bits + row * bitmap->row_words,
419 row,
420 0))
421 {
422 fprintf (stderr, "can't read TIFF scanline\n");
423 goto fail;
424 }
426 #ifdef TIFF_REVERSE_BITS
427 reverse_bits ((uint8_t *) bitmap->bits,
428 image_length * bitmap->row_words * sizeof (word_t));
429 #endif /* TIFF_REVERSE_BITS */
431 #if 0
432 if (input_attributes.has_page_size)
433 bitmap = resize_bitmap (bitmap,
434 x_resolution,
435 y_resolution,
436 input_attributes);
437 #endif
439 rotate_bitmap (bitmap,
440 input_attributes);
442 width_points = (rect_width (& bitmap->rect) / dest_x_resolution) * POINTS_PER_INCH;
443 height_points = (rect_height (& bitmap->rect) / dest_y_resolution) * POINTS_PER_INCH;
445 if ((height_points > PAGE_MAX_POINTS) || (width_points > PAGE_MAX_POINTS))
446 {
447 fprintf (stdout, "image too large (max %d inches on a side\n", PAGE_MAX_INCHES);
448 goto fail;
449 }
451 page = pdf_new_page (out->pdf, width_points, height_points);
453 #if 0
454 pdf_write_text (page);
455 #else
456 pdf_write_g4_fax_image (page,
457 0, 0, /* x, y */
458 width_points, height_points,
459 bitmap,
460 0, /* ImageMask */
461 0, 0, 0, /* r, g, b */
462 0); /* BlackIs1 */
463 #endif
465 if (bitmap)
466 free_bitmap (bitmap);
467 return (page);
469 fail:
470 if (bitmap)
471 free_bitmap (bitmap);
473 return (NULL);
474 }
477 #if 0
478 pdf_page_handle process_jpeg_page (int image, /* range 1 .. n */
479 input_attributes_t input_attributes)
480 {
481 FILE *f;
482 pdf_page_handle page;
484 f = fopen (filename, "rb");
485 if (! f)
486 fatal ("error opening input file '%s'\n", filename);
488 page = pdf_new_page (out->pdf, width_points, height_points);
490 pdf_write_jpeg_image (page,
491 0, 0, /* x, y */
492 width_points, height_points,
493 f);
495 return (page);
496 }
497 #endif
500 bool process_page (int image, /* range 1 .. n */
501 input_attributes_t input_attributes,
502 bookmark_t *bookmarks,
503 page_label_t *page_label)
504 {
505 pdf_page_handle page;
507 page = process_tiff_page (image, input_attributes);
509 while (bookmarks)
510 {
511 /* $$$ need to handle level here */
512 pdf_new_bookmark (NULL, bookmarks->name, 0, page);
513 bookmarks = bookmarks->next;
514 }
516 if (page_label)
517 pdf_new_page_label (out->pdf,
518 page_label->page_index,
519 page_label->base,
520 page_label->count,
521 page_label->style,
522 page_label->prefix);
524 return (page != NULL);
525 }
528 #define MAX_BOOKMARK_NAME_LEN 500
531 static int filename_length_without_suffix (char *in_fn)
532 {
533 char *p;
534 int len = strlen (in_fn);
536 p = strrchr (in_fn, '.');
537 if (p && ((strcasecmp (p, ".tif") == 0) ||
538 (strcasecmp (p, ".tiff") == 0)))
539 return (p - in_fn);
540 return (len);
541 }
544 /* $$$ this function should ensure that it doesn't overflow the name string! */
545 static void generate_bookmark_name (char *name,
546 char *bookmark_fmt,
547 char *in_fn,
548 int page)
549 {
550 bool meta = 0;
551 int len;
553 while (*bookmark_fmt)
554 {
555 if (meta)
556 {
557 meta = 0;
558 switch (*bookmark_fmt)
559 {
560 case '%':
561 *(name++) = '%';
562 break;
563 case 'F':
564 len = filename_length_without_suffix (in_fn);
565 strncpy (name, in_fn, len);
566 name += len;
567 break;
568 case 'p':
569 sprintf (name, "%d", page);
570 name += strlen (name);
571 break;
572 default:
573 break;
574 }
575 }
576 else
577 switch (*bookmark_fmt)
578 {
579 case '%':
580 meta = 1;
581 break;
582 default:
583 *(name++) = *bookmark_fmt;
584 }
585 bookmark_fmt++;
586 }
587 *name = '\0';
588 }
591 void main_args (char *out_fn,
592 int inf_count,
593 char **in_fn,
594 char *bookmark_fmt)
595 {
596 int i, ip;
597 input_attributes_t input_attributes;
598 pdf_file_attributes_t output_attributes;
599 bookmark_t bookmark;
600 char bookmark_name [MAX_BOOKMARK_NAME_LEN];
602 bookmark.next = NULL;
603 bookmark.level = 1;
604 bookmark.name = & bookmark_name [0];
606 memset (& input_attributes, 0, sizeof (input_attributes));
607 memset (& output_attributes, 0, sizeof (output_attributes));
609 output_attributes.has_bookmarks = (bookmark_fmt != NULL);
611 if (! open_pdf_output_file (out_fn, & output_attributes))
612 fatal (3, "error opening output file \"%s\"\n", out_fn);
613 for (i = 0; i < inf_count; i++)
614 {
615 if (! open_tiff_input_file (in_fn [i]))
616 fatal (3, "error opening input file \"%s\"\n", in_fn [i]);
617 for (ip = 1;; ip++)
618 {
619 fprintf (stderr, "processing page %d of file \"%s\"\r", ip, in_fn [i]);
620 if (bookmark_fmt)
621 generate_bookmark_name (& bookmark_name [0],
622 bookmark_fmt,
623 in_fn [i],
624 ip);
625 if (! process_page (ip, input_attributes,
626 bookmark_fmt ? & bookmark : NULL,
627 NULL))
628 fatal (3, "error processing page %d of input file \"%s\"\n", ip, in_fn [i]);
629 if (last_tiff_page ())
630 break;
631 }
632 if (verbose)
633 fprintf (stderr, "processed %d pages of input file \"%s\"\n", ip, in_fn [i]);
634 if (! close_tiff_input_file ())
635 fatal (3, "error closing input file \"%s\"\n", in_fn [i]);
636 }
637 if (! close_pdf_output_files ())
638 fatal (3, "error closing output file \"%s\"\n", out_fn);
639 }
642 void main_spec (char *spec_fn)
643 {
644 if (! parse_spec_file (spec_fn))
645 fatal (2, "error parsing spec file\n");
646 if (! process_specs ())
647 fatal (3, "error processing spec file\n");
648 }
651 int main (int argc, char *argv[])
652 {
653 char *spec_fn = NULL;
654 char *out_fn = NULL;
655 char *bookmark_fmt = NULL;
656 int inf_count = 0;
657 char *in_fn [MAX_INPUT_FILES];
659 progname = argv [0];
661 pdf_init ();
663 while (--argc)
664 {
665 if (argv [1][0] == '-')
666 {
667 if (strcmp (argv [1], "-v") == 0)
668 verbose++;
669 else if (strcmp (argv [1], "-o") == 0)
670 {
671 if (argc)
672 {
673 argc--;
674 argv++;
675 out_fn = argv [1];
676 }
677 else
678 fatal (1, "missing filename after \"-o\" option\n");
679 }
680 else if (strcmp (argv [1], "-s") == 0)
681 {
682 if (argc)
683 {
684 argc--;
685 argv++;
686 spec_fn = argv [1];
687 }
688 else
689 fatal (1, "missing filename after \"-s\" option\n");
690 }
691 else if (strcmp (argv [1], "-b") == 0)
692 {
693 if (argc)
694 {
695 argc--;
696 argv++;
697 bookmark_fmt = argv [1];
698 }
699 else
700 fatal (1, "missing format string after \"-b\" option\n");
701 }
702 else
703 fatal (1, "unrecognized option \"%s\"\n", argv [1]);
704 }
705 else if (inf_count < MAX_INPUT_FILES)
706 in_fn [inf_count++] = argv [1];
707 else
708 fatal (1, "exceeded maximum of %d input files\n", MAX_INPUT_FILES);
709 argv++;
710 }
712 if (! ((! out_fn) ^ (! spec_fn)))
713 fatal (1, "either a spec file or an output file (but not both) must be specified\n");
715 if (out_fn && ! inf_count)
716 fatal (1, "no input files specified\n");
718 if (spec_fn && inf_count)
719 fatal (1, "if spec file is provided, input files can't be specified as arguments\n");
721 if (spec_fn)
722 main_spec (spec_fn);
723 else
724 main_args (out_fn, inf_count, in_fn, bookmark_fmt);
726 close_tiff_input_file ();
727 close_pdf_output_files ();
728 exit (0);
729 }