]> git.cameronkatri.com Git - mandoc.git/blob - term_ps.c
d4eaa9ba4cbd2b8fc059164d618935d53a7ffd51
[mandoc.git] / term_ps.c
1 /* $Id: term_ps.c,v 1.12 2010/06/25 19:50:23 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/param.h>
22
23 #include <assert.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include "out.h"
30 #include "main.h"
31 #include "term.h"
32
33 #define PS_CHAR_WIDTH 6
34 #define PS_CHAR_HEIGHT 12
35 #define PS_CHAR_TOPMARG (792 - 24)
36 #define PS_CHAR_TOP (PS_CHAR_TOPMARG - 36)
37 #define PS_CHAR_LEFT 36
38 #define PS_CHAR_BOTMARG 24
39 #define PS_CHAR_BOT (PS_CHAR_BOTMARG + 36)
40
41 #define PS_BUFSLOP 128
42 #define PS_GROWBUF(p, sz) \
43 do if ((p)->engine.ps.psmargcur + (sz) > \
44 (p)->engine.ps.psmargsz) { \
45 (p)->engine.ps.psmargsz += /* CONSTCOND */ \
46 MAX(PS_BUFSLOP, (sz)); \
47 (p)->engine.ps.psmarg = realloc \
48 ((p)->engine.ps.psmarg, \
49 (p)->engine.ps.psmargsz); \
50 if (NULL == (p)->engine.ps.psmarg) { \
51 perror(NULL); \
52 exit(EXIT_FAILURE); \
53 } \
54 } while (/* CONSTCOND */ 0)
55
56
57 static void ps_letter(struct termp *, char);
58 static void ps_begin(struct termp *);
59 static void ps_end(struct termp *);
60 static void ps_advance(struct termp *, size_t);
61 static void ps_endline(struct termp *);
62 static void ps_fclose(struct termp *);
63 static size_t ps_width(const struct termp *, char);
64 static void ps_pclose(struct termp *);
65 static void ps_pletter(struct termp *, char);
66 static void ps_printf(struct termp *, const char *, ...);
67 static void ps_putchar(struct termp *, char);
68 static void ps_setfont(struct termp *, enum termfont);
69
70
71 void *
72 ps_alloc(void)
73 {
74 struct termp *p;
75
76 if (NULL == (p = term_alloc(TERMENC_ASCII)))
77 return(NULL);
78
79 p->defrmargin = 78;
80 p->tabwidth = 5;
81
82 p->type = TERMTYPE_PS;
83 p->letter = ps_letter;
84 p->begin = ps_begin;
85 p->end = ps_end;
86 p->advance = ps_advance;
87 p->endline = ps_endline;
88 p->width = ps_width;
89 return(p);
90 }
91
92
93 void
94 ps_free(void *arg)
95 {
96 struct termp *p;
97
98 p = (struct termp *)arg;
99
100 if (p->engine.ps.psmarg)
101 free(p->engine.ps.psmarg);
102
103 term_free(p);
104 }
105
106
107 static void
108 ps_printf(struct termp *p, const char *fmt, ...)
109 {
110 va_list ap;
111 int pos;
112
113 va_start(ap, fmt);
114
115 /*
116 * If we're running in regular mode, then pipe directly into
117 * vprintf(). If we're processing margins, then push the data
118 * into our growable margin buffer.
119 */
120
121 if ( ! (PS_MARGINS & p->engine.ps.psstate)) {
122 vprintf(fmt, ap);
123 va_end(ap);
124 return;
125 }
126
127 /*
128 * XXX: I assume that the in-margin print won't exceed
129 * PS_BUFSLOP (128 bytes), which is reasonable but still an
130 * assumption that will cause pukeage if it's not the case.
131 */
132
133 PS_GROWBUF(p, PS_BUFSLOP);
134
135 pos = (int)p->engine.ps.psmargcur;
136 vsnprintf(&p->engine.ps.psmarg[pos], PS_BUFSLOP, fmt, ap);
137 p->engine.ps.psmargcur = strlen(p->engine.ps.psmarg);
138
139 va_end(ap);
140 }
141
142
143 static void
144 ps_putchar(struct termp *p, char c)
145 {
146 int pos;
147
148 /* See ps_printf(). */
149
150 if ( ! (PS_MARGINS & p->engine.ps.psstate)) {
151 putchar(c);
152 return;
153 }
154
155 PS_GROWBUF(p, 2);
156
157 pos = (int)p->engine.ps.psmargcur++;
158 p->engine.ps.psmarg[pos++] = c;
159 p->engine.ps.psmarg[pos] = '\0';
160 }
161
162
163 /* ARGSUSED */
164 static void
165 ps_end(struct termp *p)
166 {
167
168 /*
169 * At the end of the file, do one last showpage. This is the
170 * same behaviour as groff(1) and works for multiple pages as
171 * well as just one.
172 */
173
174 assert(0 == p->engine.ps.psstate);
175 assert('\0' == p->engine.ps.last);
176 assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]);
177 printf("%s", p->engine.ps.psmarg);
178 printf("showpage\n");
179 printf("%s\n", "%%EOF");
180 }
181
182
183 static void
184 ps_begin(struct termp *p)
185 {
186
187 /*
188 * Print margins into margin buffer. Nothing gets output to the
189 * screen yet, so we don't need to initialise the primary state.
190 */
191
192 if (p->engine.ps.psmarg) {
193 assert(p->engine.ps.psmargsz);
194 p->engine.ps.psmarg[0] = '\0';
195 }
196
197 p->engine.ps.psmargcur = 0;
198 p->engine.ps.psstate = PS_MARGINS;
199 p->engine.ps.pscol = PS_CHAR_LEFT;
200 p->engine.ps.psrow = PS_CHAR_TOPMARG;
201
202 ps_setfont(p, TERMFONT_NONE);
203
204 (*p->headf)(p, p->argf);
205 (*p->endline)(p);
206
207 p->engine.ps.pscol = PS_CHAR_LEFT;
208 p->engine.ps.psrow = PS_CHAR_BOTMARG;
209
210 (*p->footf)(p, p->argf);
211 (*p->endline)(p);
212
213 p->engine.ps.psstate &= ~PS_MARGINS;
214
215 assert(0 == p->engine.ps.psstate);
216 assert(p->engine.ps.psmarg);
217 assert('\0' != p->engine.ps.psmarg[0]);
218
219 /*
220 * Print header and initialise page state. Following this,
221 * stuff gets printed to the screen, so make sure we're sane.
222 */
223
224 printf("%s\n", "%!PS");
225 ps_setfont(p, TERMFONT_NONE);
226 p->engine.ps.pscol = PS_CHAR_LEFT;
227 p->engine.ps.psrow = PS_CHAR_TOP;
228 }
229
230
231 static void
232 ps_pletter(struct termp *p, char c)
233 {
234
235 /*
236 * If we're not in a PostScript "word" context, then open one
237 * now at the current cursor.
238 */
239
240 if ( ! (PS_INLINE & p->engine.ps.psstate)) {
241 ps_printf(p, "%zu %zu moveto\n(",
242 p->engine.ps.pscol,
243 p->engine.ps.psrow);
244 p->engine.ps.psstate |= PS_INLINE;
245 }
246
247 /*
248 * We need to escape these characters as per the PostScript
249 * specification. We would also escape non-graphable characters
250 * (like tabs), but none of them would get to this point and
251 * it's superfluous to abort() on them.
252 */
253
254 switch (c) {
255 case ('('):
256 /* FALLTHROUGH */
257 case (')'):
258 /* FALLTHROUGH */
259 case ('\\'):
260 ps_putchar(p, '\\');
261 break;
262 default:
263 break;
264 }
265
266 /* Write the character and adjust where we are on the page. */
267
268 ps_putchar(p, c);
269 p->engine.ps.pscol += PS_CHAR_WIDTH;
270 }
271
272
273 static void
274 ps_pclose(struct termp *p)
275 {
276
277 /*
278 * Spit out that we're exiting a word context (this is a
279 * "partial close" because we don't check the last-char buffer
280 * or anything).
281 */
282
283 if ( ! (PS_INLINE & p->engine.ps.psstate))
284 return;
285
286 ps_printf(p, ") show\n");
287 p->engine.ps.psstate &= ~PS_INLINE;
288 }
289
290
291 static void
292 ps_fclose(struct termp *p)
293 {
294
295 /*
296 * Strong closure: if we have a last-char, spit it out after
297 * checking that we're in the right font mode. This will of
298 * course open a new scope, if applicable.
299 *
300 * Following this, close out any scope that's open.
301 */
302
303 if ('\0' != p->engine.ps.last) {
304 if (p->engine.ps.lastf != TERMFONT_NONE) {
305 ps_pclose(p);
306 ps_setfont(p, TERMFONT_NONE);
307 }
308 ps_pletter(p, p->engine.ps.last);
309 p->engine.ps.last = '\0';
310 }
311
312 if ( ! (PS_INLINE & p->engine.ps.psstate))
313 return;
314
315 ps_pclose(p);
316 }
317
318
319 static void
320 ps_letter(struct termp *p, char c)
321 {
322 char cc;
323
324 /*
325 * State machine dictates whether to buffer the last character
326 * or not. Basically, encoded words are detected by checking if
327 * we're an "8" and switching on the buffer. Then we put "8" in
328 * our buffer, and on the next charater, flush both character
329 * and buffer. Thus, "regular" words are detected by having a
330 * regular character and a regular buffer character.
331 */
332
333 if ('\0' == p->engine.ps.last) {
334 assert(8 != c);
335 p->engine.ps.last = c;
336 return;
337 } else if (8 == p->engine.ps.last) {
338 assert(8 != c);
339 p->engine.ps.last = '\0';
340 } else if (8 == c) {
341 assert(8 != p->engine.ps.last);
342 if ('_' == p->engine.ps.last) {
343 if (p->engine.ps.lastf != TERMFONT_UNDER) {
344 ps_pclose(p);
345 ps_setfont(p, TERMFONT_UNDER);
346 }
347 } else if (p->engine.ps.lastf != TERMFONT_BOLD) {
348 ps_pclose(p);
349 ps_setfont(p, TERMFONT_BOLD);
350 }
351 p->engine.ps.last = c;
352 return;
353 } else {
354 if (p->engine.ps.lastf != TERMFONT_NONE) {
355 ps_pclose(p);
356 ps_setfont(p, TERMFONT_NONE);
357 }
358 cc = p->engine.ps.last;
359 p->engine.ps.last = c;
360 c = cc;
361 }
362
363 ps_pletter(p, c);
364 }
365
366
367 static void
368 ps_advance(struct termp *p, size_t len)
369 {
370
371 /*
372 * Advance some spaces. This can probably be made smarter,
373 * i.e., to have multiple space-separated words in the same
374 * scope, but this is easier: just close out the current scope
375 * and readjust our column settings.
376 */
377
378 ps_fclose(p);
379 p->engine.ps.pscol += len ? len * PS_CHAR_WIDTH : 0;
380 }
381
382
383 static void
384 ps_endline(struct termp *p)
385 {
386
387 /* Close out any scopes we have open: we're at eoln. */
388
389 ps_fclose(p);
390
391 /*
392 * If we're in the margin, don't try to recalculate our current
393 * row. XXX: if the column tries to be fancy with multiple
394 * lines, we'll do nasty stuff.
395 */
396
397 if (PS_MARGINS & p->engine.ps.psstate)
398 return;
399
400 /*
401 * Put us down a line. If we're at the page bottom, spit out a
402 * showpage and restart our row.
403 */
404
405 p->engine.ps.pscol = PS_CHAR_LEFT;
406 if (p->engine.ps.psrow >= PS_CHAR_HEIGHT + PS_CHAR_BOT) {
407 p->engine.ps.psrow -= PS_CHAR_HEIGHT;
408 return;
409 }
410
411 assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]);
412 printf("%s", p->engine.ps.psmarg);
413 printf("showpage\n");
414 p->engine.ps.psrow = PS_CHAR_TOP;
415 }
416
417
418 static void
419 ps_setfont(struct termp *p, enum termfont f)
420 {
421
422 if (TERMFONT_BOLD == f)
423 ps_printf(p, "/Courier-Bold\n");
424 else if (TERMFONT_UNDER == f)
425 ps_printf(p, "/Courier-Oblique\n");
426 else
427 ps_printf(p, "/Courier\n");
428
429 ps_printf(p, "10 selectfont\n");
430 p->engine.ps.lastf = f;
431 }
432
433
434 /* ARGSUSED */
435 static size_t
436 ps_width(const struct termp *p, char c)
437 {
438
439 return(1);
440 }