ref: f89c755373cff88631fd789a0be8da586b259007
dir: /post.c/
/* * NEATPOST: NEATROFF'S POSTSCRIPT/PDF POSTPROCESSOR * * Copyright (C) 2013-2020 Ali Gholami Rudi <ali at rudi dot ir> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "post.h" static char *ps_title; /* document title */ static int ps_pagewidth = 2159; /* page width (tenths of a millimetre) */ static int ps_pageheight = 2794;/* page height (tenths of a millimetre) */ static int ps_linewidth = 40; /* drawing line thickness in thousandths of an em */ static int o_pages; /* output pages */ /* bookmarks */ static char (*mark_desc)[256]; /* bookmark description */ static int *mark_page; /* bookmark page */ static int *mark_offset; /* bookmark offset */ static int *mark_level; /* bookmark level */ static int mark_n; /* number of bookmarks */ static int mark_sz; /* allocated size of bookmark arrays */ /* named destinations */ static char (*name_desc)[64]; /* reference name */ static int *name_page; /* reference page */ static int *name_offset; /* reference offset */ static int name_n; /* number of references */ static int name_sz; /* allocated size of name arrays */ static int next(void) { return getc(stdin); } static void back(int c) { ungetc(c, stdin); } static int utf8len(int c) { if (~c & 0xc0) /* ASCII or invalid */ return c > 0; if (~c & 0x20) return 2; if (~c & 0x10) return 3; if (~c & 0x08) return 4; return 1; } static int nextutf8(char *s) { int c = next(); int l = utf8len(c); int i; if (c < 0) return 0; s[0] = c; for (i = 1; i < l; i++) s[i] = next(); s[l] = '\0'; return l; } /* skip blanks */ static void nextskip(void) { int c; do { c = next(); } while (isspace(c)); back(c); } static int nextnum(void) { int c; int n = 0; int neg = 0; nextskip(); while (1) { c = next(); if (!n && (c == '-' || c == '+')) { neg = c == '-'; continue; } if (!isdigit(c)) back(c); if (c < 0 || !isdigit(c)) break; n = n * 10 + c - '0'; } return neg ? -n : n; } static int readnum(int *n) { int c; do { c = next(); } while (c == ' '); back(c); if (c == '-' || c == '+' || (c >= '0' && c <= '9')) { *n = nextnum(); return 0; } return 1; } static int iseol(void) { int c; do { c = next(); } while (c == ' '); back(c); return c == '\n'; } /* skip until the end of line */ static void nexteol(void) { int c; do { c = next(); } while (c >= 0 && c != '\n'); } static void nextword(char *s) { int c; nextskip(); c = next(); while (c >= 0 && !isspace(c)) { *s++ = c; c = next(); } if (c >= 0) back(c); *s = '\0'; } /* read until eol */ static void readln(char *s) { int c; c = next(); while (c > 0 && c != '\n') { *s++ = c; c = next(); } if (c == '\n') back(c); *s = '\0'; } static void postline(void) { int h, v; while (!readnum(&h) && !readnum(&v)) drawl(h, v); } static void postarc(void) { int h1, v1, h2, v2; if (!readnum(&h1) && !readnum(&v1) && !readnum(&h2) && !readnum(&v2)) drawa(h1, v1, h2, v2); } static void postspline(void) { int h2, v2; int h1 = nextnum(); int v1 = nextnum(); if (iseol()) { drawl(h1, v1); return; } while (!readnum(&h2) && !readnum(&v2)) { draws(h1, v1, h2, v2); h1 = h2; v1 = v2; } draws(h1, v1, 0, 0); } static void postpoly(void) { int l = 'l'; int c; while (!iseol() && (l == 'l' || l == '~' || l == 'a')) { do { c = next(); } while (c == ' '); back(c); if (c != '-' && c != '+' && (c < '0' || c > '9')) { l = c; while (c >= 0 && !isspace(c)) c = next(); continue; } if (l == 'l') postline(); if (l == '~') postspline(); if (l == 'a') postarc(); } } static void postdraw(void) { int h1, v1; int c = next(); drawbeg(); switch (tolower(c)) { case 'l': h1 = nextnum(); v1 = nextnum(); drawl(h1, v1); break; case 'c': drawc(nextnum()); break; case 'e': h1 = nextnum(); v1 = nextnum(); drawe(h1, v1); break; case 'a': postarc(); break; case '~': postspline(); break; case 'p': postpoly(); break; } drawend(c == 'p' || c == 'P', c == 'E' || c == 'C' || c == 'P'); nexteol(); } static char *strcut(char *dst, char *src) { while (*src == ' ' || *src == '\n') src++; if (src[0] == '"') { src++; while (*src && (src[0] != '"' || src[1] == '"')) { if (*src == '"') src++; *dst++ = *src++; } if (*src == '"') src++; } else { while (*src && *src != ' ' && *src != '\n') *dst++ = *src++; } *dst = '\0'; return src; } static void postps(void) { char cmd[ILNLEN]; char arg[ILNLEN]; nextword(cmd); readln(arg); if (!strcmp("PS", cmd) || !strcmp("ps", cmd)) out("%s\n", arg); if (!strcmp("rotate", cmd)) outrotate(atoi(arg)); if (!strcmp("eps", cmd) || !strcmp("pdf", cmd)) { char path[1 << 12]; int hwid, vwid, nspec; char *spec = arg; spec = strcut(path, spec); nspec = sscanf(spec, "%d %d", &hwid, &vwid); if (nspec < 1) hwid = 0; if (nspec < 2) vwid = 0; if (path[0] && !strcmp("eps", cmd)) outeps(path, hwid, vwid); if (path[0] && !strcmp("pdf", cmd)) outpdf(path, hwid, vwid); } if (!strcmp("name", cmd)) { char *spec = arg; int nspec; if (name_n == name_sz) { name_sz = name_sz == 0 ? 128 : name_sz * 2; name_desc = mextend(name_desc, name_n, name_sz, sizeof(name_desc[0])); name_page = mextend(name_page, name_n, name_sz, sizeof(name_page[0])); name_offset = mextend(name_offset, name_n, name_sz, sizeof(name_offset[0])); } spec = strcut(name_desc[name_n], spec); nspec = sscanf(spec, "%d %d", &name_page[name_n], &name_offset[name_n]); if (name_desc[name_n][0] && nspec > 0) name_n++; } if (!strcmp("mark", cmd)) { char *spec = arg; int nspec; if (mark_n == mark_sz) { mark_sz = mark_sz == 0 ? 128 : mark_sz * 2; mark_desc = mextend(mark_desc, mark_n, mark_sz, sizeof(mark_desc[0])); mark_page = mextend(mark_page, mark_n, mark_sz, sizeof(mark_page[0])); mark_offset = mextend(mark_offset, mark_n, mark_sz, sizeof(mark_offset[0])); mark_level = mextend(mark_level, mark_n, mark_sz, sizeof(mark_level[0])); } spec = strcut(mark_desc[mark_n], spec); nspec = sscanf(spec, "%d %d %d", &mark_page[mark_n], &mark_offset[mark_n], &mark_level[mark_n]); if (mark_desc[mark_n][0] && nspec > 0) mark_n++; } if (!strcmp("link", cmd)) { char link[1 << 12]; int hwid, vwid, nspec; char *spec = arg; spec = strcut(link, spec); nspec = sscanf(spec, "%d %d", &hwid, &vwid); if (link[0] && nspec == 2) outlink(link, hwid, vwid); } if (!strcmp("info", cmd)) { char *spec = arg; char kwd[128]; int i = 0; while (*spec == ' ') spec++; while (*spec && *spec != ' ') { if (i < sizeof(kwd) - 1) kwd[i++] = *spec; spec++; } kwd[i] = '\0'; while (*spec == ' ') spec++; outinfo(kwd, spec); } if (!strcmp("set", cmd)) { char var[ILNLEN]; char val[ILNLEN]; if (sscanf(arg, "%s %s", var, val) == 2) outset(var, val); } if (!strcmp("BeginObject", cmd)) drawmbeg(arg); if (!strcmp("EndObject", cmd)) drawmend(arg); } static char postdir[PATHLEN] = TROFFFDIR; /* output device directory */ static char postdev[PATHLEN] = "utf"; /* output device name */ static void postx(void) { char cmd[128]; char font[128]; int pos; nextword(cmd); switch (cmd[0]) { case 'f': pos = nextnum(); nextword(font); dev_mnt(pos, font, font); outmnt(pos); break; case 'i': if (dev_open(postdir, postdev)) { fprintf(stderr, "neatpost: cannot open device %s\n", postdev); exit(1); } docheader(ps_title, ps_pagewidth, ps_pageheight, ps_linewidth); break; case 'T': nextword(postdev); break; case 's': break; case 'X': postps(); break; } nexteol(); } static void postcmd(int c) { char cs[GNLEN]; if (isdigit(c)) { outrel((c - '0') * 10 + next() - '0', 0); nextutf8(cs); outc(cs); return; } switch (c) { case 's': outsize(nextnum()); break; case 'f': outfont(nextnum()); break; case 'H': outh(nextnum()); break; case 'V': outv(nextnum()); break; case 'h': outrel(nextnum(), 0); break; case 'v': outrel(0, nextnum()); break; case 'c': nextutf8(cs); outc(cs); break; case 'm': nextword(cs); outcolor(clr_get(cs)); break; case 'N': nextnum(); break; case 'C': nextword(cs); outc(cs); break; case 'p': if (o_pages) docpageend(o_pages); o_pages = nextnum(); docpagebeg(o_pages); outpage(); break; case 'w': break; case 'n': nextnum(); nextnum(); break; case 'D': postdraw(); break; case 'x': postx(); break; case '#': nexteol(); break; default: fprintf(stderr, "neatpost: unknown command %c\n", c); nexteol(); } } static void post(void) { int c; while ((c = next()) >= 0) if (!isspace(c)) postcmd(c); if (o_pages) docpageend(o_pages); if (name_n) outname(name_n, name_desc, name_page, name_offset); if (mark_n) outmark(mark_n, mark_desc, mark_page, mark_offset, mark_level); } static struct paper { char *name; int w, h; } papers[] = { {"letter", 2159, 2794}, {"legal", 2159, 3556}, {"ledger", 4318, 2794}, {"tabloid", 2794, 4318}, }; static void setpagesize(char *s) { int d1, d2, n, i; /* predefined paper sizes */ for (i = 0; i < LEN(papers); i++) { if (!strcmp(papers[i].name, s)) { ps_pagewidth = papers[i].w; ps_pageheight = papers[i].h; return; } } /* custom paper size in mm, like 2100x2970 for a4 */ if (isdigit(s[0]) && strchr(s, 'x')) { ps_pagewidth = atoi(s); ps_pageheight = atoi(strchr(s, 'x') + 1); return; } /* ISO paper sizes */ if (!strchr("abcABC", s[0]) || !isdigit(s[1])) return; if (tolower(s[0]) == 'a') { d1 = 8410; d2 = 11890; } if (tolower(s[0]) == 'b') { d1 = 10000; d2 = 14140; } if (tolower(s[0]) == 'c') { d1 = 9170; d2 = 12970; } n = s[1] - '0'; ps_pagewidth = ((n & 1) ? d2 : d1) >> ((n + 1) >> 1); ps_pageheight = ((n & 1) ? d1 : d2) >> (n >> 1); ps_pagewidth -= ps_pagewidth % 10; ps_pageheight -= ps_pageheight % 10; } void *mextend(void *old, long oldsz, long newsz, int memsz) { void *new = malloc(newsz * memsz); memcpy(new, old, oldsz * memsz); memset(new + oldsz * memsz, 0, (newsz - oldsz) * memsz); free(old); return new; } /* the unicode codepoint of the given utf-8 character */ static int utf8code(char *s) { int c = (unsigned char) s[0]; if (~c & 0xc0) /* ASCII or invalid */ return c; if (~c & 0x20) return ((c & 0x1f) << 6) | (s[1] & 0x3f); if (~c & 0x10) return ((c & 0x0f) << 12) | ((s[1] & 0x3f) << 6) | (s[2] & 0x3f); if (~c & 0x08) return ((c & 0x07) << 18) | ((s[1] & 0x3f) << 12) | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f); return c; } static int pdftext_ascii(char *s) { for (; *s; s++) if (((unsigned char) *s) & 0x80 || *s == '(' || *s == ')') return 0; return 1; } /* encode s as pdf text string */ static char *pdftext(char *s) { struct sbuf *sb = sbuf_make(); if (pdftext_ascii(s)) { sbuf_chr(sb, '('); sbuf_str(sb, s); sbuf_chr(sb, ')'); return sbuf_done(sb); } /* read utf-8 and write utf-16 */ sbuf_str(sb, "<FEFF"); /* unicode byte order marker */ while (*s) { int c = utf8code(s); if ((c >= 0 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { sbuf_printf(sb, "%02X%02X", c >> 8, c & 0xff); } if (c >= 0x010000 && c <= 0x10ffff) { int c1 = 0xd800 + ((c - 0x10000) >> 10); int c2 = 0xdc00 + ((c - 0x10000) & 0x3ff); sbuf_printf(sb, "%02X%02X", c1 >> 8, c1 & 0xff); sbuf_printf(sb, "%02X%02X", c2 >> 8, c2 & 0xff); } s += utf8len((unsigned char) *s); } sbuf_chr(sb, '>'); return sbuf_done(sb); } /* encode s as pdf text string; returns a static buffer */ char *pdftext_static(char *s) { static char buf[1024]; char *r = pdftext(s); snprintf(buf, sizeof(buf), "%s", r); free(r); return buf; } static char *usage = "Usage: neatpost [options] <input >output\n" "Options:\n" " -F dir \tset font directory (" TROFFFDIR ")\n" " -p size \tset paper size (letter); e.g., a4, 2100x2970\n" " -t title\tspecify document title\n" " -w lwid \tdrawing line thickness in thousandths of an em (40)\n" " -n \talways draw glyphs by name (ps glyphshow)\n"; int main(int argc, char *argv[]) { int i; for (i = 1; i < argc; i++) { if (argv[i][0] == '-' && argv[i][1] == 'F') { strcpy(postdir, argv[i][2] ? argv[i] + 2 : argv[++i]); } else if (argv[i][0] == '-' && argv[i][1] == 'p') { setpagesize(argv[i][2] ? argv[i] + 2 : argv[++i]); } else if (argv[i][0] == '-' && argv[i][1] == 'w') { ps_linewidth = atoi(argv[i][2] ? argv[i] + 2 : argv[++i]); } else if (argv[i][0] == '-' && argv[i][1] == 'n') { outgname(1); } else if (argv[i][0] == '-' && argv[i][1] == 't') { ps_title = argv[i][2] ? argv[i] + 2 : argv[++i]; } else { printf("%s", usage); return 0; } } post(); doctrailer(o_pages); dev_close(); free(mark_desc); free(mark_page); free(mark_offset); free(mark_level); free(name_desc); free(name_page); free(name_offset); return 0; }