jpg2pdf/000755 000423 000000 00000000000 11127353223 012647 5ustar00luigiwheel000000 000000 jpg2pdf/jpg2pdf.c000644 000423 000000 00000035645 11127352427 014371 0ustar00luigiwheel000000 000000 /* * Copyright (C) 2008 Luigi Rizzo - Universita' di Pisa * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * * $Id: jpg2pdf.c 197 2009-01-02 08:54:42Z luigi $ * */ #if !defined(STANDALONE) && defined(LIB_ONLY) #include "pdf.h" #else #include #include #include #include #include #endif /* * Simple routine to convert jpg into ps or pdf * * Extract the geometry and resolution from the file, and build a * simple wrapper around the image to embed in the PDF or PS file. * * Components of the code: * - a crude jpg parser, to identify the JFIF and SOF markers and * extract the relevant info from there. * Each marker has a 2-byte header 0xffXX, almost always followed by * a 2-byte length (big-endian format). * We skip non-interesting headers, get the density from JFIF * (though the density is often wrong), and the geometry from SOF. * Once we find the SOF, we are done. * * - encoder for b2a/ascii85 mode. It is used in the PostScript encoding. * * - write routine for PS or PDF. * For PS, prepend a small header to scale/translate and decode the * ASCII85 format. Then encode the image in ASCII85 and add to the file. * For PDF, build the hierarchy of objects that allow the display of * the image in a single page. * * The default is to use an image density of 72dpi, and scale if * the image does not fit. Manually you can specify margins or offsets. */ static int verbose = 0; /* extract length of a jpeg marker */ static int get_len(FILE *fd) { int b2, b1 = fgetc(fd); if (b1 == EOF) return -1; b2 = fgetc(fd); return (b2 == EOF) ? -1 : (b1<<8) + b2; } /* * returns an array of int with the following info: * 0 length * 1 bps (1..8+) bits per component ? * 2 width * 3 height * 4 units (0: pixels; 1:dpi; 2:dpcm) * 5 x-density * 6 y-density * 7 type (0:jpg, 1:gif) * 8 components (1, 3, 4) * * This is used as a library by the pdf parser code. */ int * get_image_info(const char *src) { int c, len, found = 0; FILE *fd = fopen(src, "r"); struct stat sb; int *res = calloc(10, sizeof(int)); /* results, see above */ if (res == NULL || fd == NULL || stat(src, &sb) ) return NULL; res[0] = sb.st_size; res[7] = 0; len = strlen(src); if (len >= 3 && !strcasecmp(src + len - 3, "gif")) { if (res[0] > 10) { unsigned char buf[16]; fread(buf, 14, 1, fd); res[1] = 8; res[2] = buf[6] + 256*buf[7]; res[3] = buf[8] + 256*buf[9]; res[7] = 1; /* gif */ res[8] = 3; /* 3 components */ found = 3; } } while ( found != 3 && EOF != (c = fgetc(fd) ) ) { if (c == 0xff) continue; if (c == 0xd8 || c == 0xd9) /* header only, size 0 */ continue; if (c == 0xdd) { /* fixed-size */ len = 4; } else { /* variable size */ len = get_len(fd); } if (len < 0) /* eof */ break; if (verbose) fprintf(stderr, "block 0xff%02x len %d\n", c, len); if (c == 0xe0) { /* JFIF */ if (len >= 16) { unsigned char buf[16]; fread(buf, 14, 1, fd); len -= 14; res[4] = buf[7]; res[5] = buf[8]*256 + buf[9]; res[6] = buf[10]*256 + buf[11]; if (verbose) fprintf(stderr, "density %dx%d %s\n", res[5], res[6], res[4] == 0 ? "pixels" : (res[4] == 1 ? "dpi" : "dpcm") ); found |= 2; } } else if (c == 0xc0) { if (len >= 6) { unsigned char buf[16]; fread(buf, 6, 1, fd); len -= 6; res[1] = buf[0]; /* bits per sample (each component) */ res[3] = buf[1]*256 + buf[2]; res[2] = buf[3]*256 + buf[4]; res[8] = buf[5]; /* number of components */ if (verbose) fprintf(stderr, "size %dx%d bps %d \n", res[2], res[3], res[1] * res[8]); found |= 1; } } fseek(fd, len - 2, SEEK_CUR); } fclose(fd); if (!found) free(res); return found ? res : NULL; } #if defined(STANDALONE) || !defined(LIB_ONLY) /* full jpg2pdf support */ struct opts { /* options and paper sizes */ const char *name; const char *orientation; /* paper size in 72dpi */ double page_width; double page_height; /* offset in 72dpi */ double x_ofs; double y_ofs; double margin; double scale; int do_ps; }; /* support for ascii85/btoa encoding, used in PS output */ struct e85_state { FILE *fd; int shift; uint32_t val; int pos, width; }; static void * e85_init(FILE *fd, int width) { struct e85_state *s; s = calloc(1, sizeof(*s)); s->fd = fd; s->shift = 24; s->width = width; return s; } static void e85_flush(struct e85_state *s) { int i; char buf[5]; if (s->shift == 0 && s->val == 0) { buf[4] = 'z'; /* special case */ } else for (i = 0; i < 5; i++) { buf[i] = '!' + (s->val % 85); s->val /= 85; } for (i = 4 - s->shift/8; i >= 0; i--) { fputc(buf[i], s->fd); if (s->pos++ >= s->width) { s->pos = 0; fputc('\n', s->fd); } if (buf[4] == 'z') break; } s->shift = 24; s->val = 0; } static void e85_close(struct e85_state *s) { e85_flush(s); //if (s->shift != 0) fputs("~>", s->fd); fputc('\n', s->fd); free(s); } static void e85_encode(unsigned c, void *_s) { struct e85_state *s = _s; s->val |= c << s->shift; if (s->shift != 0) { s->shift -= 8; return; } e85_flush(s); } /* * extract info from the jpg file and compute scaling. * Return 0 on error. */ static double compute_scale(const char *src, struct opts *o, int **info_p) { int *info = get_image_info(src); double scale = o->scale; int width, height; *info_p = info; if (!info) return 0; width = info[2]; height = info[3]; /* scale the image */ if (scale == 0) { /* user did not specify a scale, autocompute */ double len, xscale, yscale; scale = xscale = yscale = 1.0; /* default */ len = o->page_width - o->x_ofs - 2*o->margin; /* useful size */ if (width > len) xscale = len / (double)width; len = o->page_height - o->y_ofs - 2*o->margin; if (height > len) yscale = len / (double)height; if (xscale < 1 || yscale < 1) { scale = xscale; if (yscale < xscale) scale = yscale; } } if (verbose) fprintf(stderr, "scaling to %f\n", scale); return scale; } /* * Create a document with the following objects: * 1: document catalog, points to pagetree(2) * 2: the page tree, with all kids (3 + i*4) * 4 objects per page: * 3: the page, (resource in 4 + i*4, contents in 5 + i*4) * 4: page resources (ref. to the image in 6 + i*4) * 5: page stream * 6: the image stream and dict */ static int write_doc(int argc, char *argv[], struct opts *o) { FILE *out; int i, c, width, height, *info; double scale; int *xref; /* pdf: 2 global objects + 4 per page */ char buf[512]; /* a small buffer used for temp strings */ int objs = 0; /* how many objects so far */ if (argc == 0) { fprintf(stderr, "need at least 1 argument\n"); return 1; } else if (argc == 1) { /* output to stdout */ out = stdout; } else { const char *dst = argv[--argc]; out = !strcmp(dst, "-") ? stdout : fopen(dst, "w+b"); } xref = calloc(4*argc + 3, sizeof(int)); if (xref == NULL || out == NULL) { error: fprintf(stderr, "I/O error, exiting\n"); exit(1); } /* first, write file headers */ if (o->do_ps) { fprintf(out, "%%!PS-Adobe-3.0 EPSF-3.0\n" "%%%%Creator: jpg2df\n" "%%%%Title: %s\n" "%%%%DocumentPaperSizes: %s\n" "%%%%Orientation: %s\n" "%%%%BoundingBox: 0 0 %f %f\n" "%%%%DocumentData: Clean7Bit\n" "%%LanguageLevel: 2\n" "%%%%EndComments\n" "%%%%BeginProlog\n" "%%%%EndProlog\n" "/languagelevel where {pop languagelevel 2 lt}{true} ifelse {\n" " (Need PostScript Level 2) dup print flush\n" " /Helvetica findfont 20 scalefont setfont\n" " 100 100 moveto show showpage stop\n" "} if\n", argv[0], o->name, o->orientation, o->page_width, o->page_height); } else { fprintf(out, "%%PDF-1.7\n%%%c%c%c%c\n%% jpg2pdf by Luigi Rizzo\n\n", 220, 230, 240, 250); xref[++objs] = ftell(out); fprintf(out, "%% document catalog\n" "%d 0 obj << /Type /Catalog /Pages %d 0 R >> endobj\n\n", objs, objs+1); xref[++objs] = ftell(out); fprintf(out, "%d 0 obj %% page root with all children\n" "<< /Type /Pages /Count %d\n /Kids [ ", objs, argc); for (i=0; i < argc; i++) /* each page has 4 objects */ fprintf(out, "%3d 0 R%s", objs + 1 + 4*i, (i % 8 == 7) ? "\n\t " : " "); fprintf(out, "] >>\nendobj %% %d 0\n\n", objs); } /* then add per-file components */ for (i = 0; i < argc; i++) { /* open file and compute scaling */ const char *src = argv[i]; FILE *_in; const char *filter = "/DCTDecode"; if (verbose) fprintf(stderr, "Inserting image %d: %s " "margin %f ofs %f, %f\n", i+1, src, o->margin, o->x_ofs, o->y_ofs); scale = compute_scale(src, o, &info); _in = fopen(src, "r"); if (info == NULL || _in == NULL) goto error; if (info[7]) { filter = "/LZWDecode"; fseek(_in, 13, SEEK_SET); /* skip gif header */ info[0] -= 13; } width = info[2]; height = info[3]; if (o->do_ps) { /* write the encoded file */ void *enc_state = NULL; fprintf(out, "%%%%Page: %d %d\n" "save\n" "/RawData currentfile /ASCII85Decode filter def\n" "/Data RawData << >> %s filter def\n" "%f %f translate %f %f scale\n", i+1, argc, filter, o->x_ofs + o->margin, o->page_height - o->margin - o->y_ofs, scale * (double)width, scale * (double)height ); fprintf(out, "/Device%s setcolorspace\n" "{ << /ImageType 1 /Width %d /Height %d\n" " /ImageMatrix [ %d 0 0 %d 0 %d ]\n" " /DataSource Data /BitsPerComponent %d /Decode [0 1 %s]\n" " >> image Data closefile RawData flushfile\n" " showpage\n restore\n} exec\n", info[8] == 3 ? "RGB" : "Gray", width, height, width, -height, 0 /* height */, info[1], /* bps */ info[8] == 3 ? " 0 1 0 1" : ""); /* do the ascii85 encoding */ enc_state = e85_init(out, 72 /* line width */); while ( (c = fgetc(_in)) != EOF) e85_encode(c, enc_state); e85_close(enc_state); } else { /* PDF - add 4 objects per image */ xref[++objs] = ftell(out); fprintf(out, "%d 0 obj %% image %d\n" "<< /Type /Page /Parent 2 0 R /Resources %d 0 R\n" " /Contents [%d 0 R] /MediaBox [ %d %d %f %f ] >>\nendobj\n\n", objs, i, objs+1, objs+2, 0, 0, o->page_width, o->page_height /* geometry */ ); xref[++objs] = ftell(out); fprintf(out, "%d 0 obj %% resources used in the page\n" "<< /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]\n" " /XObject << /Im_res%d %d 0 R >>\n>>\nendobj\n\n", objs, i, objs+2); xref[++objs] = ftell(out); c = sprintf(buf, "q\n%f 0 0 %f %f %f cm\n/Im_res%d Do\nQ", (double)width * scale, (double)height * scale, (o->x_ofs + o->margin), o->page_height - (o->y_ofs + o->margin) - (double)height * scale, i); fprintf(out, "%d 0 obj %% stream describing image %d\n" "<< /Length %d >>\nstream\n%s" "\nendstream\nendobj %% %d 0\n\n", objs, i, c, buf, objs); xref[++objs] = ftell(out); fprintf(out, "%d 0 obj %% the image stream for %d %s\n" "<< /Type /XObject /Subtype /Image\n" " /Width %d /Height %d /ColorSpace /Device%s\n" " /BitsPerComponent %d /Length %d /Filter %s>>\n" "stream\n", objs, i, src, info[2], info[3], info[8] == 3 ? "RGB" : "Gray", info[1], info[0], filter); /* now dump the data */ while ( (c = fgetc(_in)) != EOF) fputc(c, out); fprintf(out, "\nendstream\nendobj %% %d 0\n\n", objs); } fclose(_in); } if (o->do_ps == 0) { /* PDF needs a trailer */ xref[++objs] = ftell(out); /* startxref */ fprintf(out, "xref\n0 %d\n0000000000 65535 f \n", objs); for (i=1; i < objs; i++) fprintf(out, "%010d 00000 n \n", xref[i]); fprintf(out, "trailer << /Size %d /Root 1 0 R >>\n" "startxref\n%d\n%%%%EOF\n", objs, xref[objs]); } if (out != stdout) fclose(out); return 0; } static struct opts papers[] = { { "A4", "Portrait", 21*72/2.54, 29.7*72/2.54}, { "A4R", "Portrait", 29.7*72/2.54, 21*72/2.54}, { "Letter", "Portrait", 8.5*72, 11*72}, { "A3", "Portrait", 29.7*72/2.54, 42*72/2.54}, { "A5", "Portrait", 14.85*72/2.54, 21*72/2.54}, { NULL} }; int main(int argc, char *argv[]) { int i, n; struct opts base, o = papers[0]; bzero(&base, sizeof(base)); base.do_ps = -1; /* default */ for (i = 1; i+1 < argc; i += 2) { const char *p = argv[i]; const char *v = argv[i+1]; if (!strcmp(p, "-v")) { verbose = 1; i--; } else if (!strcmp(p, "-ps")) { base.do_ps = 1; i--; } else if (!strcmp(p, "-pdf")) { base.do_ps = 0; i--; } else if (!strcmp(p, "-paper")) { struct opts *i; for (i = papers; i->name; i++) { if (!strcasecmp(v, i->name)) { o = *i; break; } } if (!i->name) { fprintf(stderr, "Paper %s not found, using %s\n", v, o.name); } } else if (!strcmp(p, "-margin")) { if ( (n = atoi(v)) ) base.margin = n; } else if (!strcmp(p, "-xofs")) { if ( (n = atoi(v)) ) base.x_ofs = n; } else if (!strcmp(p, "-yofs")) { if ( (n = atoi(v)) ) base.y_ofs = n; } else if (!strcmp(p, "-dpi")) { if ( (n = atoi(v)) ) base.scale = 72.0 / (double)(n); } else { break; } } argc -= i; argv += i; if (base.do_ps == -1) { if (argc > 1) { /* try to guess the type from output file name */ const char *dst = argv[argc-1]; int l = strlen(dst); if (l >= 2 && !strcasecmp(dst + l-2, "ps")) base.do_ps = 1; else if (l >= 3 && !strcasecmp(dst + l-3, "pdf")) base.do_ps = 0; } } o.scale = base.scale; o.x_ofs = base.x_ofs; o.y_ofs = base.y_ofs; o.margin = base.margin; o.do_ps = base.do_ps >= 0 ? base.do_ps : 0; /* if more than one argument, use the last one as output file, * others as image name */ return write_doc(argc, argv, &o); } #endif /* STANDALONE */ jpg2pdf/jpg2pdf.1000644 000423 000000 00000003050 11123551143 014260 0ustar00luigiwheel000000 000000 .\" $Id: jpg2pdf.1 195 2008-12-21 00:49:07Z luigi $ .\" .Dd December 21, 2008 .Dt JPG2PDF 1 .Os .Sh NAME .Nm jpg2pdf .Nd Convert JPG images to PDF or Postscript .Sh SYNOPSIS .Nm .Op options .Ar file .Op Ar file ... .Op destination | - .Sh DESCRIPTION The .Nm program creates a Postscript or PDF file with one JPEG image per page. Input file names must be supplied on the command line, whereas the output file is either a file or the standard output. The image is printed at the top left corner of the page, but it can be moved or centered with options. .Pp The output type (Postscript or PDF) is determined from the suffix of the output file, defaults to PDF if not recognised, and can be manually forced with the .Fl pdf or .Fl ps options. .Pp By default, the image is normally printed at 72dpi and scaled if it does not fit the page size. The scaling factor or density can be manually forced with options. .Pp Options are: .Bl -tag -width indent .It Fl dpi Ar value Considers the image to have density .Ar value dots per inch. The value is used to scale the image. .It Fl paper Ar A4 | A4R | Letter | A5 | A3 Sets the paper type to the type specified .It Fl pdf Forces the output to PDF .It Fl ps Forces the output to Postscript Ignored if .Fl xofs Ar value .Fl yofs Ar value .Fl margin Ar value Set the x or y offset or margin to the .Ar value specified as argument, interpreted as 1/72 inch units. .Fl v Sets verbose mode, printing additional info on stder while processing. .Sh RETURN VALUES The program exits with an error if one of the input files cannot be processed.