Return to term_ps.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.36, 2010/07/21 08:24:39 | version 1.66, 2014/08/28 01:37:12 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2010 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 <stdio.h> | #include <stdio.h> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <string.h> | #include <string.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) \ | ||
((double)(x) / (1000.0 / (double)(p)->engine.ps.scale)) | ((double)(x) / (1000.0 / (double)(p)->ps->scale)) | ||
struct glyph { | struct glyph { | ||
unsigned short wx; /* WX in AFM */ | unsigned short wx; /* WX in AFM */ | ||
|
|
||
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 */ | |||
enum termfont nextf; /* building next font here */ | |||
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 | ||
|
|
||
{ 400 }, | { 400 }, | ||
{ 541 }, | { 541 }, | ||
} }, | } }, | ||
{ "Times-BoldItalic", { | |||
{ 250 }, | |||
{ 389 }, | |||
{ 555 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 833 }, | |||
{ 778 }, | |||
{ 333 }, | |||
{ 333 }, | |||
{ 333 }, | |||
{ 500 }, | |||
{ 570 }, | |||
{ 250 }, | |||
{ 333 }, | |||
{ 250 }, | |||
{ 278 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 333 }, | |||
{ 333 }, | |||
{ 570 }, | |||
{ 570 }, | |||
{ 570 }, | |||
{ 500 }, | |||
{ 832 }, | |||
{ 667 }, | |||
{ 667 }, | |||
{ 667 }, | |||
{ 722 }, | |||
{ 667 }, | |||
{ 667 }, | |||
{ 722 }, | |||
{ 778 }, | |||
{ 389 }, | |||
{ 500 }, | |||
{ 667 }, | |||
{ 611 }, | |||
{ 889 }, | |||
{ 722 }, | |||
{ 722 }, | |||
{ 611 }, | |||
{ 722 }, | |||
{ 667 }, | |||
{ 556 }, | |||
{ 611 }, | |||
{ 722 }, | |||
{ 667 }, | |||
{ 889 }, | |||
{ 667 }, | |||
{ 611 }, | |||
{ 611 }, | |||
{ 333 }, | |||
{ 278 }, | |||
{ 333 }, | |||
{ 570 }, | |||
{ 500 }, | |||
{ 333 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 444 }, | |||
{ 500 }, | |||
{ 444 }, | |||
{ 333 }, | |||
{ 500 }, | |||
{ 556 }, | |||
{ 278 }, | |||
{ 278 }, | |||
{ 500 }, | |||
{ 278 }, | |||
{ 778 }, | |||
{ 556 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 500 }, | |||
{ 389 }, | |||
{ 389 }, | |||
{ 278 }, | |||
{ 556 }, | |||
{ 444 }, | |||
{ 667 }, | |||
{ 500 }, | |||
{ 444 }, | |||
{ 389 }, | |||
{ 348 }, | |||
{ 220 }, | |||
{ 348 }, | |||
{ 570 }, | |||
} }, | |||
}; | }; | ||
/* 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, marginx, marginy, 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; | ||
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: | ||
|
|
||
} 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); | ||
} else if (NULL == pp) | } | ||
pp = "letter"; | |||
/* | /* | ||
* This MUST be defined before any PNT2AFM or AFM2PNT | * This MUST be defined before any PNT2AFM or AFM2PNT | ||
* calculations occur. | * calculations occur. | ||
*/ | */ | ||
p->engine.ps.scale = 11; | p->ps->scale = 11; | ||
/* Remember millimetres -> AFM units. */ | /* Remember millimetres -> AFM units. */ | ||
|
|
||
/* Margins are 1/9 the page x and y. */ | /* Margins are 1/9 the page x and y. */ | ||
marginx = /* LINTED */ | marginx = (size_t)((double)pagex / 9.0); | ||
(size_t)((double)pagex / 9.0); | marginy = (size_t)((double)pagey / 9.0); | ||
marginy = /* LINTED */ | |||
(size_t)((double)pagey / 9.0); | |||
/* Line-height is 1.4em. */ | /* Line-height is 1.4em. */ | ||
lineheight = PNT2AFM(p, ((double)p->engine.ps.scale * 1.4)); | lineheight = PNT2AFM(p, ((double)p->ps->scale * 1.4)); | ||
p->engine.ps.width = pagex; | p->ps->width = p->ps->lastwidth = (size_t)pagex; | ||
p->engine.ps.height = pagey; | p->ps->height = (size_t)pagey; | ||
p->engine.ps.header = pagey - (marginy / 2) - (lineheight / 2); | p->ps->header = pagey - (marginy / 2) - (lineheight / 2); | ||
p->engine.ps.top = pagey - marginy; | p->ps->top = pagey - marginy; | ||
p->engine.ps.footer = (marginy / 2) - (lineheight / 2); | p->ps->footer = (marginy / 2) - (lineheight / 2); | ||
p->engine.ps.bottom = marginy; | p->ps->bottom = marginy; | ||
p->engine.ps.left = marginx; | p->ps->left = marginx; | ||
p->engine.ps.lineheight = lineheight; | p->ps->lineheight = lineheight; | ||
p->defrmargin = pagex - (marginx * 2); | 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.flags)) { | 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.flags)) { | 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. | ||
*/ | */ | ||
if ( ! (PS_NEWPAGE & p->engine.ps.flags)) { | if ( ! (PS_NEWPAGE & p->ps->flags)) { | ||
assert(0 == p->engine.ps.flags); | assert(0 == p->ps->flags); | ||
assert('\0' == p->engine.ps.last); | assert('\0' == p->ps->last); | ||
assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]); | ps_closepage(p); | ||
printf("%s", p->engine.ps.psmarg); | |||
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; | |||
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.flags = 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.flags &= ~PS_MARGINS; | p->ps->flags &= ~PS_MARGINS; | ||
assert(0 == p->engine.ps.flags); | 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); | if (TERMTYPE_PS == p->type) { | ||
ps_printf(p, "%%!PS-Adobe-3.0\n"); | |||
ps_printf(p, "%%%%DocumentData: Clean7Bit\n"); | |||
ps_printf(p, "%%%%Orientation: Portrait\n"); | |||
ps_printf(p, "%%%%Pages: (atend)\n"); | |||
ps_printf(p, "%%%%PageOrder: Ascend\n"); | |||
ps_printf(p, "%%%%DocumentMedia: " | |||
"Default %zu %zu 0 () ()\n", | |||
(size_t)AFM2PNT(p, p->ps->width), | |||
(size_t)AFM2PNT(p, p->ps->height)); | |||
ps_printf(p, "%%%%DocumentNeededResources: font"); | |||
printf("%%!PS-Adobe-3.0\n"); | for (i = 0; i < (int)TERMFONT__MAX; i++) | ||
printf("%%%%Creator: mandoc-%s\n", VERSION); | ps_printf(p, " %s", fonts[i].name); | ||
printf("%%%%CreationDate: %s", ctime(&t)); | |||
printf("%%%%DocumentData: Clean7Bit\n"); | |||
printf("%%%%Orientation: Portrait\n"); | |||
printf("%%%%Pages: (atend)\n"); | |||
printf("%%%%PageOrder: Ascend\n"); | |||
printf("%%%%DocumentMedia: Default %zu %zu 0 () ()\n", | |||
(size_t)AFM2PNT(p, p->engine.ps.width), | |||
(size_t)AFM2PNT(p, p->engine.ps.height)); | |||
printf("%%%%DocumentNeededResources: font"); | |||
for (i = 0; i < (int)TERMFONT__MAX; i++) | |||
printf(" %s", fonts[i].name); | |||
printf("\n%%%%EndComments\n"); | |||
p->engine.ps.pscol = p->engine.ps.left; | ps_printf(p, "\n%%%%EndComments\n"); | ||
p->engine.ps.psrow = p->engine.ps.top; | } else { | ||
p->engine.ps.flags |= PS_NEWPAGE; | 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); | ||
} | } | ||
static void | static void | ||
ps_pletter(struct termp *p, int c) | ps_pletter(struct termp *p, int c) | ||
{ | { | ||
|
|
||
* in a new page and make sure the font is correctly set. | * in a new page and make sure the font is correctly set. | ||
*/ | */ | ||
if (PS_NEWPAGE & p->engine.ps.flags) { | if (PS_NEWPAGE & p->ps->flags) { | ||
printf("%%%%Page: %zu %zu\n", | if (TERMTYPE_PS == p->type) { | ||
p->engine.ps.pages + 1, | ps_printf(p, "%%%%Page: %zu %zu\n", | ||
p->engine.ps.pages + 1); | p->ps->pages + 1, p->ps->pages + 1); | ||
ps_printf(p, "/%s %zu selectfont\n", | ps_printf(p, "/%s %zu selectfont\n", | ||
fonts[(int)p->engine.ps.lastf].name, | fonts[(int)p->ps->lastf].name, | ||
p->engine.ps.scale); | p->ps->scale); | ||
p->engine.ps.flags &= ~PS_NEWPAGE; | } 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.flags)) { | if ( ! (PS_INLINE & p->ps->flags)) { | ||
ps_printf(p, "%.3f %.3f 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.flags |= 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->engine.ps.flags)); | 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 | ||
|
|
||
*/ | */ | ||
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 += (size_t)fonts[f].gly[0].wx; | |||
return; | |||
} | |||
ps_putchar(p, (char)c); | ps_putchar(p, (char)c); | ||
c -= 32; | c -= 32; | ||
p->engine.ps.pscol += (size_t)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.flags)) | if ( ! (PS_INLINE & p->ps->flags)) | ||
return; | return; | ||
ps_printf(p, ") show\n"); | |||
p->engine.ps.flags &= ~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 (p->ps->last != '\0') { | ||
if (p->engine.ps.lastf != TERMFONT_NONE) { | assert(p->ps->last != 8); | ||
if (p->ps->nextf != p->ps->lastf) { | |||
ps_pclose(p); | ps_pclose(p); | ||
ps_setfont(p, TERMFONT_NONE); | ps_setfont(p, p->ps->nextf); | ||
} | } | ||
ps_pletter(p, p->engine.ps.last); | p->ps->nextf = TERMFONT_NONE; | ||
p->engine.ps.last = '\0'; | ps_pletter(p, p->ps->last); | ||
p->ps->last = '\0'; | |||
} | } | ||
if ( ! (PS_INLINE & p->engine.ps.flags)) | 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 c; | ||
c = arg >= 128 || arg <= 0 ? '?' : arg; | |||
/* | /* | ||
* State machine dictates whether to buffer the last character | * When receiving an initial character, merely buffer it, | ||
* or not. Basically, encoded words are detected by checking if | * because a backspace might follow to specify formatting. | ||
* we're an "8" and switching on the buffer. Then we put "8" in | * When receiving a backspace, use the buffered character | ||
* our buffer, and on the next charater, flush both character | * to build the font instruction and clear the buffer. | ||
* and buffer. Thus, "regular" words are detected by having a | * Only when there are two non-backspace characters in a row, | ||
* regular character and a regular buffer character. | * activate the font built so far and print the first of them; | ||
* the second, again, merely gets buffered. | |||
* The final character will get printed from ps_fclose(). | |||
*/ | */ | ||
if ('\0' == p->engine.ps.last) { | if (c == 8) { | ||
assert(8 != c); | assert(p->ps->last != '\0'); | ||
p->engine.ps.last = c; | assert(p->ps->last != 8); | ||
return; | if ('_' == p->ps->last) { | ||
} else if (8 == p->engine.ps.last) { | switch (p->ps->nextf) { | ||
assert(8 != c); | case TERMFONT_BI: | ||
p->engine.ps.last = '\0'; | break; | ||
} else if (8 == c) { | case TERMFONT_BOLD: | ||
assert(8 != p->engine.ps.last); | p->ps->nextf = TERMFONT_BI; | ||
if ('_' == p->engine.ps.last) { | break; | ||
if (p->engine.ps.lastf != TERMFONT_UNDER) { | default: | ||
ps_pclose(p); | p->ps->nextf = TERMFONT_UNDER; | ||
ps_setfont(p, TERMFONT_UNDER); | |||
} | } | ||
} else if (p->engine.ps.lastf != TERMFONT_BOLD) { | } else { | ||
ps_pclose(p); | switch (p->ps->nextf) { | ||
ps_setfont(p, TERMFONT_BOLD); | case TERMFONT_BI: | ||
break; | |||
case TERMFONT_UNDER: | |||
p->ps->nextf = TERMFONT_BI; | |||
break; | |||
default: | |||
p->ps->nextf = TERMFONT_BOLD; | |||
} | |||
} | } | ||
p->engine.ps.last = c; | } else if (p->ps->last != '\0' && p->ps->last != 8) { | ||
return; | if (p->ps->nextf != p->ps->lastf) { | ||
} else { | |||
if (p->engine.ps.lastf != TERMFONT_NONE) { | |||
ps_pclose(p); | ps_pclose(p); | ||
ps_setfont(p, TERMFONT_NONE); | ps_setfont(p, p->ps->nextf); | ||
} | } | ||
cc = p->engine.ps.last; | p->ps->nextf = TERMFONT_NONE; | ||
p->engine.ps.last = c; | ps_pletter(p, p->ps->last); | ||
c = cc; | |||
} | } | ||
p->ps->last = 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.flags) | if (PS_MARGINS & p->ps->flags) | ||
return; | return; | ||
/* Left-justify. */ | /* Left-justify. */ | ||
p->engine.ps.pscol = p->engine.ps.left; | p->ps->pscol = p->ps->left; | ||
/* If we haven't printed anything, return. */ | /* If we haven't printed anything, return. */ | ||
if (PS_NEWPAGE & p->engine.ps.flags) | if (PS_NEWPAGE & p->ps->flags) | ||
return; | return; | ||
/* | /* | ||
|
|
||
* showpage and restart our row. | * showpage and restart our row. | ||
*/ | */ | ||
if (p->engine.ps.psrow >= p->engine.ps.lineheight + | if (p->ps->psrow >= p->ps->lineheight + p->ps->bottom) { | ||
p->engine.ps.bottom) { | p->ps->psrow -= p->ps->lineheight; | ||
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++; | |||
p->engine.ps.psrow = p->engine.ps.top; | |||
assert( ! (PS_NEWPAGE & p->engine.ps.flags)); | |||
p->engine.ps.flags |= PS_NEWPAGE; | |||
} | } | ||
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); | ||
p->engine.ps.lastf = f; | p->ps->lastf = f; | ||
/* | /* | ||
* If we're still at the top of the page, let the font-setting | * If we're still at the top of the page, let the font-setting | ||
* be delayed until we actually have stuff to print. | * be delayed until we actually have stuff to print. | ||
*/ | */ | ||
if (PS_NEWPAGE & p->engine.ps.flags) | if (PS_NEWPAGE & p->ps->flags) | ||
return; | return; | ||
ps_printf(p, "/%s %zu selectfont\n", | if (TERMTYPE_PS == p->type) | ||
fonts[(int)f].name, p->engine.ps.scale); | 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); | |||
} | } | ||
/* ARGSUSED */ | |||
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((size_t)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((size_t)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_BU: | ||
r = PNT2AFM(p, su->scale * 28.34); | /* | ||
* Traditionally, the default unit is fixed to the | |||
* output media. So this would refer to the point. In | |||
* mandoc(1), however, we stick to the default terminal | |||
* scaling unit so that output is the same regardless | |||
* the media. | |||
*/ | |||
r = PNT2AFM(p, su->scale * 72.0 / 240.0); | |||
break; | break; | ||
case (SCALE_IN): | case SCALE_CM: | ||
r = PNT2AFM(p, su->scale * 72); | r = PNT2AFM(p, su->scale * 72.0 / 2.54); | ||
break; | break; | ||
case (SCALE_PC): | case SCALE_EM: | ||
r = PNT2AFM(p, su->scale * 12); | r = su->scale * | ||
fonts[(int)TERMFONT_NONE].gly[109 - 32].wx; | |||
break; | break; | ||
case (SCALE_PT): | case SCALE_EN: | ||
r = PNT2AFM(p, su->scale * 100); | |||
break; | |||
case (SCALE_EM): | |||
r = su->scale * | r = su->scale * | ||
fonts[(int)TERMFONT_NONE].gly[109 - 32].wx; | fonts[(int)TERMFONT_NONE].gly[110 - 32].wx; | ||
break; | break; | ||
case (SCALE_MM): | case SCALE_IN: | ||
r = PNT2AFM(p, su->scale * 2.834); | r = PNT2AFM(p, su->scale * 72.0); | ||
break; | break; | ||
case (SCALE_EN): | case SCALE_MM: | ||
r = su->scale * | r = su->scale * | ||
fonts[(int)TERMFONT_NONE].gly[110 - 32].wx; | fonts[(int)TERMFONT_NONE].gly[109 - 32].wx / 100.0; | ||
break; | break; | ||
case (SCALE_VS): | case SCALE_PC: | ||
r = su->scale * p->engine.ps.lineheight; | r = PNT2AFM(p, su->scale * 12.0); | ||
break; | break; | ||
case SCALE_PT: | |||
r = PNT2AFM(p, su->scale * 1.0); | |||
break; | |||
case SCALE_VS: | |||
r = su->scale * p->ps->lineheight; | |||
break; | |||
default: | default: | ||
r = su->scale; | r = su->scale; | ||
break; | break; | ||
|
|
||
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); | |||
} |