Return to term_ps.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.26, 2010/07/02 10:53:28 | version 1.63, 2014/08/10 23:54:41 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org> | |||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
|
|
||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | */ | ||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | #include "config.h" | ||
#endif | |||
#include <sys/param.h> | #include <sys/types.h> | ||
#include <assert.h> | #include <assert.h> | ||
#include <stdarg.h> | #include <stdarg.h> | ||
|
|
||
#include <time.h> | #include <time.h> | ||
#include <unistd.h> | #include <unistd.h> | ||
#include "mandoc.h" | |||
#include "mandoc_aux.h" | |||
#include "out.h" | #include "out.h" | ||
#include "main.h" | #include "main.h" | ||
#include "term.h" | #include "term.h" | ||
/* These work the buffer used by the header and footer. */ | |||
#define PS_BUFSLOP 128 | |||
/* Convert PostScript point "x" to an AFM unit. */ | /* Convert PostScript point "x" to an AFM unit. */ | ||
#define PNT2AFM(p, x) /* LINTED */ \ | #define PNT2AFM(p, x) \ | ||
(size_t)((double)(x) * (1000.0 / (double)(p)->engine.ps.scale)) | (size_t)((double)(x) * (1000.0 / (double)(p)->ps->scale)) | ||
/* Convert an AFM unit "x" to a PostScript points */ | /* Convert an AFM unit "x" to a PostScript points */ | ||
#define AFM2PNT(p, x) /* LINTED */ \ | #define AFM2PNT(p, x) \ | ||
(size_t)((double)(x) / (1000.0 / (double)(p)->engine.ps.scale)) | ((double)(x) / (1000.0 / (double)(p)->ps->scale)) | ||
struct glyph { | struct glyph { | ||
size_t wx; /* WX in AFM */ | unsigned short wx; /* WX in AFM */ | ||
}; | }; | ||
struct font { | struct font { | ||
|
|
||
struct glyph gly[MAXCHAR]; /* glyph metrics */ | struct glyph gly[MAXCHAR]; /* glyph metrics */ | ||
}; | }; | ||
struct termp_ps { | |||
int flags; | |||
#define PS_INLINE (1 << 0) /* we're in a word */ | |||
#define PS_MARGINS (1 << 1) /* we're in the margins */ | |||
#define PS_NEWPAGE (1 << 2) /* new page, no words yet */ | |||
size_t pscol; /* visible column (AFM units) */ | |||
size_t psrow; /* visible row (AFM units) */ | |||
char *psmarg; /* margin buf */ | |||
size_t psmargsz; /* margin buf size */ | |||
size_t psmargcur; /* cur index in margin buf */ | |||
char last; /* character buffer */ | |||
enum termfont lastf; /* last set font */ | |||
size_t scale; /* font scaling factor */ | |||
size_t pages; /* number of pages shown */ | |||
size_t lineheight; /* line height (AFM units) */ | |||
size_t top; /* body top (AFM units) */ | |||
size_t bottom; /* body bottom (AFM units) */ | |||
size_t height; /* page height (AFM units */ | |||
size_t width; /* page width (AFM units) */ | |||
size_t lastwidth; /* page width before last ll */ | |||
size_t left; /* body left (AFM units) */ | |||
size_t header; /* header pos (AFM units) */ | |||
size_t footer; /* footer pos (AFM units) */ | |||
size_t pdfbytes; /* current output byte */ | |||
size_t pdflastpg; /* byte of last page mark */ | |||
size_t pdfbody; /* start of body object */ | |||
size_t *pdfobjs; /* table of object offsets */ | |||
size_t pdfobjsz; /* size of pdfobjs */ | |||
}; | |||
static double ps_hspan(const struct termp *, | |||
const struct roffsu *); | |||
static size_t ps_width(const struct termp *, int); | |||
static void ps_advance(struct termp *, size_t); | |||
static void ps_begin(struct termp *); | |||
static void ps_closepage(struct termp *); | |||
static void ps_end(struct termp *); | |||
static void ps_endline(struct termp *); | |||
static void ps_fclose(struct termp *); | |||
static void ps_growbuf(struct termp *, size_t); | |||
static void ps_letter(struct termp *, int); | |||
static void ps_pclose(struct termp *); | |||
static void ps_pletter(struct termp *, int); | |||
#if __GNUC__ - 0 >= 4 | |||
__attribute__((__format__ (__printf__, 2, 3))) | |||
#endif | |||
static void ps_printf(struct termp *, const char *, ...); | |||
static void ps_putchar(struct termp *, char); | |||
static void ps_setfont(struct termp *, enum termfont); | |||
static void ps_setwidth(struct termp *, int, size_t); | |||
static struct termp *pspdf_alloc(char *); | |||
static void pdf_obj(struct termp *, size_t); | |||
/* | /* | ||
* We define, for the time being, three fonts: bold, oblique/italic, and | * We define, for the time being, three fonts: bold, oblique/italic, and | ||
* normal (roman). The following table hard-codes the font metrics for | * normal (roman). The following table hard-codes the font metrics for | ||
|
|
||
} }, | } }, | ||
}; | }; | ||
/* These work the buffer used by the header and footer. */ | void * | ||
#define PS_BUFSLOP 128 | pdf_alloc(char *outopts) | ||
#define PS_GROWBUF(p, sz) \ | { | ||
do if ((p)->engine.ps.psmargcur + (sz) > \ | struct termp *p; | ||
(p)->engine.ps.psmargsz) { \ | |||
(p)->engine.ps.psmargsz += /* CONSTCOND */ \ | |||
MAX(PS_BUFSLOP, (sz)); \ | |||
(p)->engine.ps.psmarg = realloc \ | |||
((p)->engine.ps.psmarg, \ | |||
(p)->engine.ps.psmargsz); \ | |||
if (NULL == (p)->engine.ps.psmarg) { \ | |||
perror(NULL); \ | |||
exit(EXIT_FAILURE); \ | |||
} \ | |||
} while (/* CONSTCOND */ 0) | |||
if (NULL != (p = pspdf_alloc(outopts))) | |||
p->type = TERMTYPE_PDF; | |||
static double ps_hspan(const struct termp *, | return(p); | ||
const struct roffsu *); | } | ||
static size_t ps_width(const struct termp *, char); | |||
static void ps_advance(struct termp *, size_t); | |||
static void ps_begin(struct termp *); | |||
static void ps_end(struct termp *); | |||
static void ps_endline(struct termp *); | |||
static void ps_fclose(struct termp *); | |||
static void ps_letter(struct termp *, char); | |||
static void ps_pclose(struct termp *); | |||
static void ps_pletter(struct termp *, int); | |||
static void ps_printf(struct termp *, const char *, ...); | |||
static void ps_putchar(struct termp *, char); | |||
static void ps_setfont(struct termp *, enum termfont); | |||
void * | void * | ||
ps_alloc(char *outopts) | ps_alloc(char *outopts) | ||
{ | { | ||
struct termp *p; | struct termp *p; | ||
size_t pagex, pagey, margin, lineheight; | |||
if (NULL != (p = pspdf_alloc(outopts))) | |||
p->type = TERMTYPE_PS; | |||
return(p); | |||
} | |||
static struct termp * | |||
pspdf_alloc(char *outopts) | |||
{ | |||
struct termp *p; | |||
unsigned int pagex, pagey; | |||
size_t marginx, marginy, lineheight; | |||
const char *toks[2]; | const char *toks[2]; | ||
const char *pp; | const char *pp; | ||
char *v; | char *v; | ||
if (NULL == (p = term_alloc(TERMENC_ASCII))) | p = mandoc_calloc(1, sizeof(struct termp)); | ||
return(NULL); | p->enc = TERMENC_ASCII; | ||
p->ps = mandoc_calloc(1, sizeof(struct termp_ps)); | |||
p->advance = ps_advance; | p->advance = ps_advance; | ||
p->begin = ps_begin; | p->begin = ps_begin; | ||
|
|
||
p->endline = ps_endline; | p->endline = ps_endline; | ||
p->hspan = ps_hspan; | p->hspan = ps_hspan; | ||
p->letter = ps_letter; | p->letter = ps_letter; | ||
p->type = TERMTYPE_PS; | p->setwidth = ps_setwidth; | ||
p->width = ps_width; | p->width = ps_width; | ||
p->engine.ps.scale = 11; | |||
toks[0] = "paper"; | toks[0] = "paper"; | ||
toks[1] = NULL; | toks[1] = NULL; | ||
|
|
||
while (outopts && *outopts) | while (outopts && *outopts) | ||
switch (getsubopt(&outopts, UNCONST(toks), &v)) { | switch (getsubopt(&outopts, UNCONST(toks), &v)) { | ||
case (0): | case 0: | ||
pp = v; | pp = v; | ||
break; | break; | ||
default: | default: | ||
break; | break; | ||
} | } | ||
margin = PNT2AFM(p, 72); | |||
lineheight = PNT2AFM(p, 12); | |||
/* Default to US letter (millimetres). */ | /* Default to US letter (millimetres). */ | ||
pagex = 216; | pagex = 216; | ||
|
|
||
} else if (0 == strcasecmp(pp, "legal")) { | } else if (0 == strcasecmp(pp, "legal")) { | ||
pagex = 216; | pagex = 216; | ||
pagey = 356; | pagey = 356; | ||
} else if (2 != sscanf(pp, "%zux%zu", &pagex, &pagey)) | } else if (2 != sscanf(pp, "%ux%u", &pagex, &pagey)) | ||
fprintf(stderr, "%s: Unknown paper\n", pp); | fprintf(stderr, "%s: Unknown paper\n", pp); | ||
} | } | ||
/* | |||
* This MUST be defined before any PNT2AFM or AFM2PNT | |||
* calculations occur. | |||
*/ | |||
p->ps->scale = 11; | |||
/* Remember millimetres -> AFM units. */ | /* Remember millimetres -> AFM units. */ | ||
pagex = PNT2AFM(p, ((double)pagex * 2.834)); | pagex = PNT2AFM(p, ((double)pagex * 2.834)); | ||
pagey = PNT2AFM(p, ((double)pagey * 2.834)); | pagey = PNT2AFM(p, ((double)pagey * 2.834)); | ||
assert(margin * 2 < pagex); | /* Margins are 1/9 the page x and y. */ | ||
assert(margin * 2 < pagey); | |||
p->engine.ps.width = pagex; | marginx = (size_t)((double)pagex / 9.0); | ||
p->engine.ps.height = pagey; | marginy = (size_t)((double)pagey / 9.0); | ||
p->engine.ps.header = pagey - (margin / 2); | |||
p->engine.ps.top = pagey - margin; | |||
p->engine.ps.footer = (margin / 2); | |||
p->engine.ps.bottom = margin; | |||
p->engine.ps.left = margin; | |||
p->engine.ps.lineheight = lineheight; | |||
p->defrmargin = pagex - (margin * 2); | /* Line-height is 1.4em. */ | ||
lineheight = PNT2AFM(p, ((double)p->ps->scale * 1.4)); | |||
p->ps->width = p->ps->lastwidth = (size_t)pagex; | |||
p->ps->height = (size_t)pagey; | |||
p->ps->header = pagey - (marginy / 2) - (lineheight / 2); | |||
p->ps->top = pagey - marginy; | |||
p->ps->footer = (marginy / 2) - (lineheight / 2); | |||
p->ps->bottom = marginy; | |||
p->ps->left = marginx; | |||
p->ps->lineheight = lineheight; | |||
p->defrmargin = pagex - (marginx * 2); | |||
return(p); | return(p); | ||
} | } | ||
static void | |||
ps_setwidth(struct termp *p, int iop, size_t width) | |||
{ | |||
size_t lastwidth; | |||
lastwidth = p->ps->width; | |||
if (0 < iop) | |||
p->ps->width += width; | |||
else if (0 > iop) | |||
p->ps->width -= width; | |||
else | |||
p->ps->width = width ? width : p->ps->lastwidth; | |||
p->ps->lastwidth = lastwidth; | |||
} | |||
void | void | ||
ps_free(void *arg) | pspdf_free(void *arg) | ||
{ | { | ||
struct termp *p; | struct termp *p; | ||
p = (struct termp *)arg; | p = (struct termp *)arg; | ||
if (p->engine.ps.psmarg) | if (p->ps->psmarg) | ||
free(p->engine.ps.psmarg); | free(p->ps->psmarg); | ||
if (p->ps->pdfobjs) | |||
free(p->ps->pdfobjs); | |||
free(p->ps); | |||
term_free(p); | term_free(p); | ||
} | } | ||
static void | static void | ||
ps_printf(struct termp *p, const char *fmt, ...) | ps_printf(struct termp *p, const char *fmt, ...) | ||
{ | { | ||
va_list ap; | va_list ap; | ||
int pos; | int pos, len; | ||
va_start(ap, fmt); | va_start(ap, fmt); | ||
|
|
||
* into our growable margin buffer. | * into our growable margin buffer. | ||
*/ | */ | ||
if ( ! (PS_MARGINS & p->engine.ps.psstate)) { | if ( ! (PS_MARGINS & p->ps->flags)) { | ||
vprintf(fmt, ap); | len = vprintf(fmt, ap); | ||
va_end(ap); | va_end(ap); | ||
p->ps->pdfbytes += len < 0 ? 0 : (size_t)len; | |||
return; | return; | ||
} | } | ||
/* | /* | ||
* XXX: I assume that the in-margin print won't exceed | * XXX: I assume that the in-margin print won't exceed | ||
* PS_BUFSLOP (128 bytes), which is reasonable but still an | * PS_BUFSLOP (128 bytes), which is reasonable but still an | ||
* assumption that will cause pukeage if it's not the case. | * assumption that will cause pukeage if it's not the case. | ||
*/ | */ | ||
PS_GROWBUF(p, PS_BUFSLOP); | ps_growbuf(p, PS_BUFSLOP); | ||
pos = (int)p->engine.ps.psmargcur; | pos = (int)p->ps->psmargcur; | ||
vsnprintf(&p->engine.ps.psmarg[pos], PS_BUFSLOP, fmt, ap); | vsnprintf(&p->ps->psmarg[pos], PS_BUFSLOP, fmt, ap); | ||
p->engine.ps.psmargcur = strlen(p->engine.ps.psmarg); | |||
va_end(ap); | va_end(ap); | ||
p->ps->psmargcur = strlen(p->ps->psmarg); | |||
} | } | ||
static void | static void | ||
ps_putchar(struct termp *p, char c) | ps_putchar(struct termp *p, char c) | ||
{ | { | ||
|
|
||
/* See ps_printf(). */ | /* See ps_printf(). */ | ||
if ( ! (PS_MARGINS & p->engine.ps.psstate)) { | if ( ! (PS_MARGINS & p->ps->flags)) { | ||
putchar(c); | putchar(c); | ||
p->ps->pdfbytes++; | |||
return; | return; | ||
} | } | ||
PS_GROWBUF(p, 2); | ps_growbuf(p, 2); | ||
pos = (int)p->engine.ps.psmargcur++; | pos = (int)p->ps->psmargcur++; | ||
p->engine.ps.psmarg[pos++] = c; | p->ps->psmarg[pos++] = c; | ||
p->engine.ps.psmarg[pos] = '\0'; | p->ps->psmarg[pos] = '\0'; | ||
} | } | ||
static void | |||
pdf_obj(struct termp *p, size_t obj) | |||
{ | |||
/* ARGSUSED */ | assert(obj > 0); | ||
if ((obj - 1) >= p->ps->pdfobjsz) { | |||
p->ps->pdfobjsz = obj + 128; | |||
p->ps->pdfobjs = mandoc_reallocarray(p->ps->pdfobjs, | |||
p->ps->pdfobjsz, sizeof(size_t)); | |||
} | |||
p->ps->pdfobjs[(int)obj - 1] = p->ps->pdfbytes; | |||
ps_printf(p, "%zu 0 obj\n", obj); | |||
} | |||
static void | static void | ||
ps_closepage(struct termp *p) | |||
{ | |||
int i; | |||
size_t len, base; | |||
/* | |||
* Close out a page that we've already flushed to output. In | |||
* PostScript, we simply note that the page must be showed. In | |||
* PDF, we must now create the Length, Resource, and Page node | |||
* for the page contents. | |||
*/ | |||
assert(p->ps->psmarg && p->ps->psmarg[0]); | |||
ps_printf(p, "%s", p->ps->psmarg); | |||
if (TERMTYPE_PS != p->type) { | |||
ps_printf(p, "ET\n"); | |||
len = p->ps->pdfbytes - p->ps->pdflastpg; | |||
base = p->ps->pages * 4 + p->ps->pdfbody; | |||
ps_printf(p, "endstream\nendobj\n"); | |||
/* Length of content. */ | |||
pdf_obj(p, base + 1); | |||
ps_printf(p, "%zu\nendobj\n", len); | |||
/* Resource for content. */ | |||
pdf_obj(p, base + 2); | |||
ps_printf(p, "<<\n/ProcSet [/PDF /Text]\n"); | |||
ps_printf(p, "/Font <<\n"); | |||
for (i = 0; i < (int)TERMFONT__MAX; i++) | |||
ps_printf(p, "/F%d %d 0 R\n", i, 3 + i); | |||
ps_printf(p, ">>\n>>\n"); | |||
/* Page node. */ | |||
pdf_obj(p, base + 3); | |||
ps_printf(p, "<<\n"); | |||
ps_printf(p, "/Type /Page\n"); | |||
ps_printf(p, "/Parent 2 0 R\n"); | |||
ps_printf(p, "/Resources %zu 0 R\n", base + 2); | |||
ps_printf(p, "/Contents %zu 0 R\n", base); | |||
ps_printf(p, ">>\nendobj\n"); | |||
} else | |||
ps_printf(p, "showpage\n"); | |||
p->ps->pages++; | |||
p->ps->psrow = p->ps->top; | |||
assert( ! (PS_NEWPAGE & p->ps->flags)); | |||
p->ps->flags |= PS_NEWPAGE; | |||
} | |||
static void | |||
ps_end(struct termp *p) | ps_end(struct termp *p) | ||
{ | { | ||
size_t i, xref, base; | |||
/* | /* | ||
* At the end of the file, do one last showpage. This is the | * At the end of the file, do one last showpage. This is the | ||
|
|
||
* well as just one. | * well as just one. | ||
*/ | */ | ||
assert(0 == p->engine.ps.psstate); | if ( ! (PS_NEWPAGE & p->ps->flags)) { | ||
assert('\0' == p->engine.ps.last); | assert(0 == p->ps->flags); | ||
assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]); | assert('\0' == p->ps->last); | ||
printf("%s", p->engine.ps.psmarg); | ps_closepage(p); | ||
p->engine.ps.pages++; | } | ||
printf("showpage\n"); | |||
printf("%%%%Trailer\n"); | if (TERMTYPE_PS == p->type) { | ||
printf("%%%%Pages: %zu\n", p->engine.ps.pages); | ps_printf(p, "%%%%Trailer\n"); | ||
printf("%%%%EOF\n"); | ps_printf(p, "%%%%Pages: %zu\n", p->ps->pages); | ||
} | ps_printf(p, "%%%%EOF\n"); | ||
return; | |||
} | |||
pdf_obj(p, 2); | |||
ps_printf(p, "<<\n/Type /Pages\n"); | |||
ps_printf(p, "/MediaBox [0 0 %zu %zu]\n", | |||
(size_t)AFM2PNT(p, p->ps->width), | |||
(size_t)AFM2PNT(p, p->ps->height)); | |||
ps_printf(p, "/Count %zu\n", p->ps->pages); | |||
ps_printf(p, "/Kids ["); | |||
for (i = 0; i < p->ps->pages; i++) | |||
ps_printf(p, " %zu 0 R", i * 4 + p->ps->pdfbody + 3); | |||
base = (p->ps->pages - 1) * 4 + p->ps->pdfbody + 4; | |||
ps_printf(p, "]\n>>\nendobj\n"); | |||
pdf_obj(p, base); | |||
ps_printf(p, "<<\n"); | |||
ps_printf(p, "/Type /Catalog\n"); | |||
ps_printf(p, "/Pages 2 0 R\n"); | |||
ps_printf(p, ">>\n"); | |||
xref = p->ps->pdfbytes; | |||
ps_printf(p, "xref\n"); | |||
ps_printf(p, "0 %zu\n", base + 1); | |||
ps_printf(p, "0000000000 65535 f \n"); | |||
for (i = 0; i < base; i++) | |||
ps_printf(p, "%.10zu 00000 n \n", | |||
p->ps->pdfobjs[(int)i]); | |||
ps_printf(p, "trailer\n"); | |||
ps_printf(p, "<<\n"); | |||
ps_printf(p, "/Size %zu\n", base + 1); | |||
ps_printf(p, "/Root %zu 0 R\n", base); | |||
ps_printf(p, "/Info 1 0 R\n"); | |||
ps_printf(p, ">>\n"); | |||
ps_printf(p, "startxref\n"); | |||
ps_printf(p, "%zu\n", xref); | |||
ps_printf(p, "%%%%EOF\n"); | |||
} | |||
static void | static void | ||
ps_begin(struct termp *p) | ps_begin(struct termp *p) | ||
{ | { | ||
time_t t; | time_t t; | ||
int i; | int i; | ||
/* | /* | ||
* Print margins into margin buffer. Nothing gets output to the | * Print margins into margin buffer. Nothing gets output to the | ||
* screen yet, so we don't need to initialise the primary state. | * screen yet, so we don't need to initialise the primary state. | ||
*/ | */ | ||
if (p->engine.ps.psmarg) { | if (p->ps->psmarg) { | ||
assert(p->engine.ps.psmargsz); | assert(p->ps->psmargsz); | ||
p->engine.ps.psmarg[0] = '\0'; | p->ps->psmarg[0] = '\0'; | ||
} | } | ||
p->engine.ps.psmargcur = 0; | /*p->ps->pdfbytes = 0;*/ | ||
p->engine.ps.psstate = PS_MARGINS; | p->ps->psmargcur = 0; | ||
p->engine.ps.pscol = p->engine.ps.left; | p->ps->flags = PS_MARGINS; | ||
p->engine.ps.psrow = p->engine.ps.header; | p->ps->pscol = p->ps->left; | ||
p->ps->psrow = p->ps->header; | |||
ps_setfont(p, TERMFONT_NONE); | ps_setfont(p, TERMFONT_NONE); | ||
(*p->headf)(p, p->argf); | (*p->headf)(p, p->argf); | ||
(*p->endline)(p); | (*p->endline)(p); | ||
p->engine.ps.pscol = p->engine.ps.left; | p->ps->pscol = p->ps->left; | ||
p->engine.ps.psrow = p->engine.ps.footer; | p->ps->psrow = p->ps->footer; | ||
(*p->footf)(p, p->argf); | (*p->footf)(p, p->argf); | ||
(*p->endline)(p); | (*p->endline)(p); | ||
p->engine.ps.psstate &= ~PS_MARGINS; | p->ps->flags &= ~PS_MARGINS; | ||
assert(0 == p->engine.ps.psstate); | assert(0 == p->ps->flags); | ||
assert(p->engine.ps.psmarg); | assert(p->ps->psmarg); | ||
assert('\0' != p->engine.ps.psmarg[0]); | assert('\0' != p->ps->psmarg[0]); | ||
/* | /* | ||
* Print header and initialise page state. Following this, | * Print header and initialise page state. Following this, | ||
* stuff gets printed to the screen, so make sure we're sane. | * stuff gets printed to the screen, so make sure we're sane. | ||
*/ | */ | ||
t = time(NULL); | t = time(NULL); | ||
printf("%%!PS-Adobe-3.0\n"); | if (TERMTYPE_PS == p->type) { | ||
printf("%%%%Creator: mandoc-%s\n", VERSION); | ps_printf(p, "%%!PS-Adobe-3.0\n"); | ||
printf("%%%%CreationDate: %s", ctime(&t)); | ps_printf(p, "%%%%CreationDate: %s", ctime(&t)); | ||
printf("%%%%DocumentData: Clean7Bit\n"); | ps_printf(p, "%%%%DocumentData: Clean7Bit\n"); | ||
printf("%%%%Orientation: Portrait\n"); | ps_printf(p, "%%%%Orientation: Portrait\n"); | ||
printf("%%%%Pages: (atend)\n"); | ps_printf(p, "%%%%Pages: (atend)\n"); | ||
printf("%%%%PageOrder: Ascend\n"); | ps_printf(p, "%%%%PageOrder: Ascend\n"); | ||
printf("%%%%DocumentMedia: Default %zu %zu 0 () ()\n", | ps_printf(p, "%%%%DocumentMedia: " | ||
AFM2PNT(p, p->engine.ps.width), | "Default %zu %zu 0 () ()\n", | ||
AFM2PNT(p, p->engine.ps.height)); | (size_t)AFM2PNT(p, p->ps->width), | ||
printf("%%%%DocumentNeededResources: font"); | (size_t)AFM2PNT(p, p->ps->height)); | ||
for (i = 0; i < (int)TERMFONT__MAX; i++) | ps_printf(p, "%%%%DocumentNeededResources: font"); | ||
printf(" %s", fonts[i].name); | |||
printf("\n%%%%EndComments\n"); | |||
printf("%%%%Page: %zu %zu\n", | for (i = 0; i < (int)TERMFONT__MAX; i++) | ||
p->engine.ps.pages + 1, | ps_printf(p, " %s", fonts[i].name); | ||
p->engine.ps.pages + 1); | |||
ps_printf(p, "\n%%%%EndComments\n"); | |||
} else { | |||
ps_printf(p, "%%PDF-1.1\n"); | |||
pdf_obj(p, 1); | |||
ps_printf(p, "<<\n"); | |||
ps_printf(p, ">>\n"); | |||
ps_printf(p, "endobj\n"); | |||
for (i = 0; i < (int)TERMFONT__MAX; i++) { | |||
pdf_obj(p, (size_t)i + 3); | |||
ps_printf(p, "<<\n"); | |||
ps_printf(p, "/Type /Font\n"); | |||
ps_printf(p, "/Subtype /Type1\n"); | |||
ps_printf(p, "/Name /F%d\n", i); | |||
ps_printf(p, "/BaseFont /%s\n", fonts[i].name); | |||
ps_printf(p, ">>\n"); | |||
} | |||
} | |||
p->ps->pdfbody = (size_t)TERMFONT__MAX + 3; | |||
p->ps->pscol = p->ps->left; | |||
p->ps->psrow = p->ps->top; | |||
p->ps->flags |= PS_NEWPAGE; | |||
ps_setfont(p, TERMFONT_NONE); | ps_setfont(p, TERMFONT_NONE); | ||
p->engine.ps.pscol = p->engine.ps.left; | |||
p->engine.ps.psrow = p->engine.ps.top; | |||
} | } | ||
static void | static void | ||
ps_pletter(struct termp *p, int c) | ps_pletter(struct termp *p, int c) | ||
{ | { | ||
int f; | int f; | ||
/* | /* | ||
* If we haven't opened a page context, then output that we're | |||
* in a new page and make sure the font is correctly set. | |||
*/ | |||
if (PS_NEWPAGE & p->ps->flags) { | |||
if (TERMTYPE_PS == p->type) { | |||
ps_printf(p, "%%%%Page: %zu %zu\n", | |||
p->ps->pages + 1, p->ps->pages + 1); | |||
ps_printf(p, "/%s %zu selectfont\n", | |||
fonts[(int)p->ps->lastf].name, | |||
p->ps->scale); | |||
} else { | |||
pdf_obj(p, p->ps->pdfbody + | |||
p->ps->pages * 4); | |||
ps_printf(p, "<<\n"); | |||
ps_printf(p, "/Length %zu 0 R\n", | |||
p->ps->pdfbody + 1 + p->ps->pages * 4); | |||
ps_printf(p, ">>\nstream\n"); | |||
} | |||
p->ps->pdflastpg = p->ps->pdfbytes; | |||
p->ps->flags &= ~PS_NEWPAGE; | |||
} | |||
/* | |||
* If we're not in a PostScript "word" context, then open one | * If we're not in a PostScript "word" context, then open one | ||
* now at the current cursor. | * now at the current cursor. | ||
*/ | */ | ||
if ( ! (PS_INLINE & p->engine.ps.psstate)) { | if ( ! (PS_INLINE & p->ps->flags)) { | ||
ps_printf(p, "%zu %zu moveto\n(", | if (TERMTYPE_PS != p->type) { | ||
AFM2PNT(p, p->engine.ps.pscol), | ps_printf(p, "BT\n/F%d %zu Tf\n", | ||
AFM2PNT(p, p->engine.ps.psrow)); | (int)p->ps->lastf, p->ps->scale); | ||
p->engine.ps.psstate |= PS_INLINE; | ps_printf(p, "%.3f %.3f Td\n(", | ||
AFM2PNT(p, p->ps->pscol), | |||
AFM2PNT(p, p->ps->psrow)); | |||
} else | |||
ps_printf(p, "%.3f %.3f moveto\n(", | |||
AFM2PNT(p, p->ps->pscol), | |||
AFM2PNT(p, p->ps->psrow)); | |||
p->ps->flags |= PS_INLINE; | |||
} | } | ||
assert( ! (PS_NEWPAGE & p->ps->flags)); | |||
/* | /* | ||
* We need to escape these characters as per the PostScript | * We need to escape these characters as per the PostScript | ||
* specification. We would also escape non-graphable characters | * specification. We would also escape non-graphable characters | ||
|
|
||
*/ | */ | ||
switch (c) { | switch (c) { | ||
case ('('): | case '(': | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case (')'): | case ')': | ||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||
case ('\\'): | case '\\': | ||
ps_putchar(p, '\\'); | ps_putchar(p, '\\'); | ||
break; | break; | ||
default: | default: | ||
|
|
||
/* Write the character and adjust where we are on the page. */ | /* Write the character and adjust where we are on the page. */ | ||
f = (int)p->engine.ps.lastf; | f = (int)p->ps->lastf; | ||
if (c <= 32 || (c - 32 > MAXCHAR)) { | if (c <= 32 || c - 32 >= MAXCHAR) | ||
ps_putchar(p, ' '); | c = 32; | ||
p->engine.ps.pscol += fonts[f].gly[0].wx; | |||
return; | |||
} | |||
ps_putchar(p, (char)c); | ps_putchar(p, (char)c); | ||
c -= 32; | c -= 32; | ||
p->engine.ps.pscol += fonts[f].gly[c].wx; | p->ps->pscol += (size_t)fonts[f].gly[c].wx; | ||
} | } | ||
static void | static void | ||
ps_pclose(struct termp *p) | ps_pclose(struct termp *p) | ||
{ | { | ||
/* | /* | ||
* Spit out that we're exiting a word context (this is a | * Spit out that we're exiting a word context (this is a | ||
* "partial close" because we don't check the last-char buffer | * "partial close" because we don't check the last-char buffer | ||
* or anything). | * or anything). | ||
*/ | */ | ||
if ( ! (PS_INLINE & p->engine.ps.psstate)) | if ( ! (PS_INLINE & p->ps->flags)) | ||
return; | return; | ||
ps_printf(p, ") show\n"); | |||
p->engine.ps.psstate &= ~PS_INLINE; | |||
} | |||
if (TERMTYPE_PS != p->type) { | |||
ps_printf(p, ") Tj\nET\n"); | |||
} else | |||
ps_printf(p, ") show\n"); | |||
p->ps->flags &= ~PS_INLINE; | |||
} | |||
static void | static void | ||
ps_fclose(struct termp *p) | ps_fclose(struct termp *p) | ||
{ | { | ||
|
|
||
* Following this, close out any scope that's open. | * Following this, close out any scope that's open. | ||
*/ | */ | ||
if ('\0' != p->engine.ps.last) { | if ('\0' != p->ps->last) { | ||
if (p->engine.ps.lastf != TERMFONT_NONE) { | if (p->ps->lastf != TERMFONT_NONE) { | ||
ps_pclose(p); | ps_pclose(p); | ||
ps_setfont(p, TERMFONT_NONE); | ps_setfont(p, TERMFONT_NONE); | ||
} | } | ||
ps_pletter(p, p->engine.ps.last); | ps_pletter(p, p->ps->last); | ||
p->engine.ps.last = '\0'; | p->ps->last = '\0'; | ||
} | } | ||
if ( ! (PS_INLINE & p->engine.ps.psstate)) | if ( ! (PS_INLINE & p->ps->flags)) | ||
return; | return; | ||
ps_pclose(p); | ps_pclose(p); | ||
} | } | ||
static void | static void | ||
ps_letter(struct termp *p, char c) | ps_letter(struct termp *p, int arg) | ||
{ | { | ||
char cc; | char cc, c; | ||
c = arg >= 128 || arg <= 0 ? '?' : arg; | |||
/* | /* | ||
* State machine dictates whether to buffer the last character | * State machine dictates whether to buffer the last character | ||
* or not. Basically, encoded words are detected by checking if | * or not. Basically, encoded words are detected by checking if | ||
|
|
||
* regular character and a regular buffer character. | * regular character and a regular buffer character. | ||
*/ | */ | ||
if ('\0' == p->engine.ps.last) { | if ('\0' == p->ps->last) { | ||
assert(8 != c); | assert(8 != c); | ||
p->engine.ps.last = c; | p->ps->last = c; | ||
return; | return; | ||
} else if (8 == p->engine.ps.last) { | } else if (8 == p->ps->last) { | ||
assert(8 != c); | assert(8 != c); | ||
p->engine.ps.last = '\0'; | p->ps->last = '\0'; | ||
} else if (8 == c) { | } else if (8 == c) { | ||
assert(8 != p->engine.ps.last); | assert(8 != p->ps->last); | ||
if ('_' == p->engine.ps.last) { | if ('_' == p->ps->last) { | ||
if (p->engine.ps.lastf != TERMFONT_UNDER) { | if (p->ps->lastf != TERMFONT_UNDER) { | ||
ps_pclose(p); | ps_pclose(p); | ||
ps_setfont(p, TERMFONT_UNDER); | ps_setfont(p, TERMFONT_UNDER); | ||
} | } | ||
} else if (p->engine.ps.lastf != TERMFONT_BOLD) { | } else if (p->ps->lastf != TERMFONT_BOLD) { | ||
ps_pclose(p); | ps_pclose(p); | ||
ps_setfont(p, TERMFONT_BOLD); | ps_setfont(p, TERMFONT_BOLD); | ||
} | } | ||
p->engine.ps.last = c; | p->ps->last = c; | ||
return; | return; | ||
} else { | } else { | ||
if (p->engine.ps.lastf != TERMFONT_NONE) { | if (p->ps->lastf != TERMFONT_NONE) { | ||
ps_pclose(p); | ps_pclose(p); | ||
ps_setfont(p, TERMFONT_NONE); | ps_setfont(p, TERMFONT_NONE); | ||
} | } | ||
cc = p->engine.ps.last; | cc = p->ps->last; | ||
p->engine.ps.last = c; | p->ps->last = c; | ||
c = cc; | c = cc; | ||
} | } | ||
ps_pletter(p, c); | ps_pletter(p, c); | ||
} | } | ||
static void | static void | ||
ps_advance(struct termp *p, size_t len) | ps_advance(struct termp *p, size_t len) | ||
{ | { | ||
|
|
||
*/ | */ | ||
ps_fclose(p); | ps_fclose(p); | ||
p->engine.ps.pscol += len; | p->ps->pscol += len; | ||
} | } | ||
static void | static void | ||
ps_endline(struct termp *p) | ps_endline(struct termp *p) | ||
{ | { | ||
|
|
||
/* | /* | ||
* If we're in the margin, don't try to recalculate our current | * If we're in the margin, don't try to recalculate our current | ||
* row. XXX: if the column tries to be fancy with multiple | * row. XXX: if the column tries to be fancy with multiple | ||
* lines, we'll do nasty stuff. | * lines, we'll do nasty stuff. | ||
*/ | */ | ||
if (PS_MARGINS & p->engine.ps.psstate) | if (PS_MARGINS & p->ps->flags) | ||
return; | return; | ||
/* Left-justify. */ | |||
p->ps->pscol = p->ps->left; | |||
/* If we haven't printed anything, return. */ | |||
if (PS_NEWPAGE & p->ps->flags) | |||
return; | |||
/* | /* | ||
* Put us down a line. If we're at the page bottom, spit out a | * Put us down a line. If we're at the page bottom, spit out a | ||
* showpage and restart our row. | * showpage and restart our row. | ||
*/ | */ | ||
p->engine.ps.pscol = p->engine.ps.left; | if (p->ps->psrow >= p->ps->lineheight + p->ps->bottom) { | ||
if (p->engine.ps.psrow >= p->engine.ps.lineheight + | p->ps->psrow -= p->ps->lineheight; | ||
p->engine.ps.bottom) { | |||
p->engine.ps.psrow -= p->engine.ps.lineheight; | |||
return; | return; | ||
} | } | ||
assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]); | ps_closepage(p); | ||
printf("%s", p->engine.ps.psmarg); | |||
printf("showpage\n"); | |||
p->engine.ps.pages++; | |||
printf("%%%%Page: %zu %zu\n", | |||
p->engine.ps.pages + 1, | |||
p->engine.ps.pages + 1); | |||
p->engine.ps.psrow = p->engine.ps.top; | |||
} | } | ||
static void | static void | ||
ps_setfont(struct termp *p, enum termfont f) | ps_setfont(struct termp *p, enum termfont f) | ||
{ | { | ||
assert(f < TERMFONT__MAX); | assert(f < TERMFONT__MAX); | ||
ps_printf(p, "/%s %zu selectfont\n", | p->ps->lastf = f; | ||
fonts[(int)f].name, p->engine.ps.scale); | |||
p->engine.ps.lastf = f; | |||
} | |||
/* | |||
* If we're still at the top of the page, let the font-setting | |||
* be delayed until we actually have stuff to print. | |||
*/ | |||
/* ARGSUSED */ | if (PS_NEWPAGE & p->ps->flags) | ||
return; | |||
if (TERMTYPE_PS == p->type) | |||
ps_printf(p, "/%s %zu selectfont\n", | |||
fonts[(int)f].name, p->ps->scale); | |||
else | |||
ps_printf(p, "/F%d %zu Tf\n", | |||
(int)f, p->ps->scale); | |||
} | |||
static size_t | static size_t | ||
ps_width(const struct termp *p, char c) | ps_width(const struct termp *p, int c) | ||
{ | { | ||
if (c <= 32 || c - 32 >= MAXCHAR) | if (c <= 32 || c - 32 >= MAXCHAR) | ||
return(fonts[(int)TERMFONT_NONE].gly[0].wx); | c = 0; | ||
else | |||
c -= 32; | |||
c -= 32; | return((size_t)fonts[(int)TERMFONT_NONE].gly[c].wx); | ||
return(fonts[(int)TERMFONT_NONE].gly[(int)c].wx); | |||
} | } | ||
static double | static double | ||
ps_hspan(const struct termp *p, const struct roffsu *su) | ps_hspan(const struct termp *p, const struct roffsu *su) | ||
{ | { | ||
double r; | double r; | ||
/* | /* | ||
* All of these measurements are derived by converting from the | * All of these measurements are derived by converting from the | ||
* native measurement to AFM units. | * native measurement to AFM units. | ||
*/ | */ | ||
switch (su->unit) { | switch (su->unit) { | ||
case (SCALE_CM): | case SCALE_CM: | ||
r = PNT2AFM(p, su->scale * 28.34); | r = PNT2AFM(p, su->scale * 28.34); | ||
break; | break; | ||
case (SCALE_IN): | case SCALE_IN: | ||
r = PNT2AFM(p, su->scale * 72); | r = PNT2AFM(p, su->scale * 72.0); | ||
break; | break; | ||
case (SCALE_PC): | case SCALE_PC: | ||
r = PNT2AFM(p, su->scale * 12); | r = PNT2AFM(p, su->scale * 12.0); | ||
break; | break; | ||
case (SCALE_PT): | case SCALE_PT: | ||
r = PNT2AFM(p, su->scale * 100); | r = PNT2AFM(p, su->scale * 100.0); | ||
break; | break; | ||
case (SCALE_EM): | case SCALE_EM: | ||
r = su->scale * | r = su->scale * | ||
fonts[(int)TERMFONT_NONE].gly[109 - 32].wx; | fonts[(int)TERMFONT_NONE].gly[109 - 32].wx; | ||
break; | break; | ||
case (SCALE_MM): | case SCALE_MM: | ||
r = PNT2AFM(p, su->scale * 2.834); | r = PNT2AFM(p, su->scale * 2.834); | ||
break; | break; | ||
case (SCALE_EN): | case SCALE_EN: | ||
r = su->scale * | r = su->scale * | ||
fonts[(int)TERMFONT_NONE].gly[110 - 32].wx; | fonts[(int)TERMFONT_NONE].gly[110 - 32].wx; | ||
break; | break; | ||
case (SCALE_VS): | case SCALE_VS: | ||
r = su->scale * p->engine.ps.lineheight; | r = su->scale * p->ps->lineheight; | ||
break; | break; | ||
default: | default: | ||
r = su->scale; | r = su->scale; | ||
|
|
||
return(r); | return(r); | ||
} | } | ||
static void | |||
ps_growbuf(struct termp *p, size_t sz) | |||
{ | |||
if (p->ps->psmargcur + sz <= p->ps->psmargsz) | |||
return; | |||
if (sz < PS_BUFSLOP) | |||
sz = PS_BUFSLOP; | |||
p->ps->psmargsz += sz; | |||
p->ps->psmarg = mandoc_realloc(p->ps->psmarg, p->ps->psmargsz); | |||
} |