]> git.cameronkatri.com Git - mandoc.git/blob - term_ps.c
56d1be2400880cd705e3f402ee91410cb7490c69
[mandoc.git] / term_ps.c
1 /* $Id: term_ps.c,v 1.10 2010/06/19 20:46:28 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 void ps_pclose(struct termp *);
64 static void ps_pletter(struct termp *, char);
65 static void ps_printf(struct termp *, const char *, ...);
66 static void ps_putchar(struct termp *, char);
67 static void ps_setfont(struct termp *, enum termfont);
68
69
70 void *
71 ps_alloc(void)
72 {
73 struct termp *p;
74
75 if (NULL == (p = term_alloc(TERMENC_ASCII)))
76 return(NULL);
77
78 p->type = TERMTYPE_PS;
79 p->letter = ps_letter;
80 p->begin = ps_begin;
81 p->end = ps_end;
82 p->advance = ps_advance;
83 p->endline = ps_endline;
84 return(p);
85 }
86
87
88 void
89 ps_free(void *arg)
90 {
91 struct termp *p;
92
93 p = (struct termp *)arg;
94
95 if (p->engine.ps.psmarg)
96 free(p->engine.ps.psmarg);
97
98 term_free(p);
99 }
100
101
102 static void
103 ps_printf(struct termp *p, const char *fmt, ...)
104 {
105 va_list ap;
106 int pos;
107
108 va_start(ap, fmt);
109
110 /*
111 * If we're running in regular mode, then pipe directly into
112 * vprintf(). If we're processing margins, then push the data
113 * into our growable margin buffer.
114 */
115
116 if ( ! (PS_MARGINS & p->engine.ps.psstate)) {
117 vprintf(fmt, ap);
118 va_end(ap);
119 return;
120 }
121
122 /*
123 * XXX: I assume that the in-margin print won't exceed
124 * PS_BUFSLOP (128 bytes), which is reasonable but still an
125 * assumption that will cause pukeage if it's not the case.
126 */
127
128 PS_GROWBUF(p, PS_BUFSLOP);
129
130 pos = (int)p->engine.ps.psmargcur;
131 vsnprintf(&p->engine.ps.psmarg[pos], PS_BUFSLOP, fmt, ap);
132 p->engine.ps.psmargcur = strlen(p->engine.ps.psmarg);
133
134 va_end(ap);
135 }
136
137
138 static void
139 ps_putchar(struct termp *p, char c)
140 {
141 int pos;
142
143 /* See ps_printf(). */
144
145 if ( ! (PS_MARGINS & p->engine.ps.psstate)) {
146 putchar(c);
147 return;
148 }
149
150 PS_GROWBUF(p, 2);
151
152 pos = (int)p->engine.ps.psmargcur++;
153 p->engine.ps.psmarg[pos++] = c;
154 p->engine.ps.psmarg[pos] = '\0';
155 }
156
157
158 /* ARGSUSED */
159 static void
160 ps_end(struct termp *p)
161 {
162
163 /*
164 * At the end of the file, do one last showpage. This is the
165 * same behaviour as groff(1) and works for multiple pages as
166 * well as just one.
167 */
168
169 assert(0 == p->engine.ps.psstate);
170 assert('\0' == p->engine.ps.last);
171 assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]);
172 printf("%s", p->engine.ps.psmarg);
173 printf("showpage\n");
174 printf("%s\n", "%%EOF");
175 }
176
177
178 static void
179 ps_begin(struct termp *p)
180 {
181
182 /*
183 * Print margins into margin buffer. Nothing gets output to the
184 * screen yet, so we don't need to initialise the primary state.
185 */
186
187 if (p->engine.ps.psmarg) {
188 assert(p->engine.ps.psmargsz);
189 p->engine.ps.psmarg[0] = '\0';
190 }
191
192 p->engine.ps.psmargcur = 0;
193 p->engine.ps.psstate = PS_MARGINS;
194 p->engine.ps.pscol = PS_CHAR_LEFT;
195 p->engine.ps.psrow = PS_CHAR_TOPMARG;
196
197 ps_setfont(p, TERMFONT_NONE);
198
199 (*p->headf)(p, p->argf);
200 (*p->endline)(p);
201
202 p->engine.ps.pscol = PS_CHAR_LEFT;
203 p->engine.ps.psrow = PS_CHAR_BOTMARG;
204
205 (*p->footf)(p, p->argf);
206 (*p->endline)(p);
207
208 p->engine.ps.psstate &= ~PS_MARGINS;
209
210 assert(0 == p->engine.ps.psstate);
211 assert(p->engine.ps.psmarg);
212 assert('\0' != p->engine.ps.psmarg[0]);
213
214 /*
215 * Print header and initialise page state. Following this,
216 * stuff gets printed to the screen, so make sure we're sane.
217 */
218
219 printf("%s\n", "%!PS");
220 ps_setfont(p, TERMFONT_NONE);
221 p->engine.ps.pscol = PS_CHAR_LEFT;
222 p->engine.ps.psrow = PS_CHAR_TOP;
223 }
224
225
226 static void
227 ps_pletter(struct termp *p, char c)
228 {
229
230 /*
231 * If we're not in a PostScript "word" context, then open one
232 * now at the current cursor.
233 */
234
235 if ( ! (PS_INLINE & p->engine.ps.psstate)) {
236 ps_printf(p, "%zu %zu moveto\n(",
237 p->engine.ps.pscol,
238 p->engine.ps.psrow);
239 p->engine.ps.psstate |= PS_INLINE;
240 }
241
242 /*
243 * We need to escape these characters as per the PostScript
244 * specification. We would also escape non-graphable characters
245 * (like tabs), but none of them would get to this point and
246 * it's superfluous to abort() on them.
247 */
248
249 switch (c) {
250 case ('('):
251 /* FALLTHROUGH */
252 case (')'):
253 /* FALLTHROUGH */
254 case ('\\'):
255 ps_putchar(p, '\\');
256 break;
257 default:
258 break;
259 }
260
261 /* Write the character and adjust where we are on the page. */
262
263 ps_putchar(p, c);
264 p->engine.ps.pscol += PS_CHAR_WIDTH;
265 }
266
267
268 static void
269 ps_pclose(struct termp *p)
270 {
271
272 /*
273 * Spit out that we're exiting a word context (this is a
274 * "partial close" because we don't check the last-char buffer
275 * or anything).
276 */
277
278 if ( ! (PS_INLINE & p->engine.ps.psstate))
279 return;
280
281 ps_printf(p, ") show\n");
282 p->engine.ps.psstate &= ~PS_INLINE;
283 }
284
285
286 static void
287 ps_fclose(struct termp *p)
288 {
289
290 /*
291 * Strong closure: if we have a last-char, spit it out after
292 * checking that we're in the right font mode. This will of
293 * course open a new scope, if applicable.
294 *
295 * Following this, close out any scope that's open.
296 */
297
298 if ('\0' != p->engine.ps.last) {
299 if (p->engine.ps.lastf != TERMFONT_NONE) {
300 ps_pclose(p);
301 ps_setfont(p, TERMFONT_NONE);
302 }
303 ps_pletter(p, p->engine.ps.last);
304 p->engine.ps.last = '\0';
305 }
306
307 if ( ! (PS_INLINE & p->engine.ps.psstate))
308 return;
309
310 ps_pclose(p);
311 }
312
313
314 static void
315 ps_letter(struct termp *p, char c)
316 {
317 char cc;
318
319 /*
320 * State machine dictates whether to buffer the last character
321 * or not. Basically, encoded words are detected by checking if
322 * we're an "8" and switching on the buffer. Then we put "8" in
323 * our buffer, and on the next charater, flush both character
324 * and buffer. Thus, "regular" words are detected by having a
325 * regular character and a regular buffer character.
326 */
327
328 if ('\0' == p->engine.ps.last) {
329 assert(8 != c);
330 p->engine.ps.last = c;
331 return;
332 } else if (8 == p->engine.ps.last) {
333 assert(8 != c);
334 p->engine.ps.last = '\0';
335 } else if (8 == c) {
336 assert(8 != p->engine.ps.last);
337 if ('_' == p->engine.ps.last) {
338 if (p->engine.ps.lastf != TERMFONT_UNDER) {
339 ps_pclose(p);
340 ps_setfont(p, TERMFONT_UNDER);
341 }
342 } else if (p->engine.ps.lastf != TERMFONT_BOLD) {
343 ps_pclose(p);
344 ps_setfont(p, TERMFONT_BOLD);
345 }
346 p->engine.ps.last = c;
347 return;
348 } else {
349 if (p->engine.ps.lastf != TERMFONT_NONE) {
350 ps_pclose(p);
351 ps_setfont(p, TERMFONT_NONE);
352 }
353 cc = p->engine.ps.last;
354 p->engine.ps.last = c;
355 c = cc;
356 }
357
358 ps_pletter(p, c);
359 }
360
361
362 static void
363 ps_advance(struct termp *p, size_t len)
364 {
365
366 /*
367 * Advance some spaces. This can probably be made smarter,
368 * i.e., to have multiple space-separated words in the same
369 * scope, but this is easier: just close out the current scope
370 * and readjust our column settings.
371 */
372
373 ps_fclose(p);
374 p->engine.ps.pscol += len ? len * PS_CHAR_WIDTH : 0;
375 }
376
377
378 static void
379 ps_endline(struct termp *p)
380 {
381
382 /* Close out any scopes we have open: we're at eoln. */
383
384 ps_fclose(p);
385
386 /*
387 * If we're in the margin, don't try to recalculate our current
388 * row. XXX: if the column tries to be fancy with multiple
389 * lines, we'll do nasty stuff.
390 */
391
392 if (PS_MARGINS & p->engine.ps.psstate)
393 return;
394
395 /*
396 * Put us down a line. If we're at the page bottom, spit out a
397 * showpage and restart our row.
398 */
399
400 p->engine.ps.pscol = PS_CHAR_LEFT;
401 if (p->engine.ps.psrow >= PS_CHAR_HEIGHT + PS_CHAR_BOT) {
402 p->engine.ps.psrow -= PS_CHAR_HEIGHT;
403 return;
404 }
405
406 assert(p->engine.ps.psmarg && p->engine.ps.psmarg[0]);
407 printf("%s", p->engine.ps.psmarg);
408 printf("showpage\n");
409 p->engine.ps.psrow = PS_CHAR_TOP;
410 }
411
412
413 static void
414 ps_setfont(struct termp *p, enum termfont f)
415 {
416
417 if (TERMFONT_BOLD == f)
418 ps_printf(p, "/Courier-Bold\n");
419 else if (TERMFONT_UNDER == f)
420 ps_printf(p, "/Courier-Oblique\n");
421 else
422 ps_printf(p, "/Courier\n");
423
424 ps_printf(p, "10 selectfont\n");
425 p->engine.ps.lastf = f;
426 }
427