1 // -*- C++ -*-
2 /* Copyright (C) 1994, 2000, 2001, 2002, 2003, 2004
3 Free Software Foundation, Inc.
4 Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING. If not, write to the Free Software
20 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22 /*
23 TODO
24
25 option to use beziers for circle/ellipse/arc
26 option to use lines for spline (for LJ3)
27 left/top offset registration
28 output bin selection option
29 paper source option
30 output non-integer parameters using fixed point numbers
31 X command to insert contents of file
32 X command to specify inline escape sequence (how to specify unprintable chars?)
33 X command to include bitmap graphics
34 */
35
36 #include "driver.h"
37 #include "nonposix.h"
38
39 extern "C" const char *Version_string;
40
41 static struct {
42 const char *name;
43 int code;
44 // at 300dpi
45 int x_offset_portrait;
46 int x_offset_landscape;
47 } paper_table[] = {
48 { "letter", 2, 75, 60 },
49 { "legal", 3, 75, 60 },
50 { "executive", 1, 75, 60 },
51 { "a4", 26, 71, 59 },
52 { "com10", 81, 75, 60 },
53 { "monarch", 80, 75, 60 },
54 { "c5", 91, 71, 59 },
55 { "b5", 100, 71, 59 },
56 { "dl", 90, 71, 59 },
57 };
58
59 static int user_paper_size = -1;
60 static int landscape_flag = 0;
61 static int duplex_flag = 0;
62
63 // An upper limit on the paper size in centipoints,
64 // used for setting HPGL picture frame.
65 #define MAX_PAPER_WIDTH (12*720)
66 #define MAX_PAPER_HEIGHT (17*720)
67
68 // Dotted lines that are thinner than this don't work right.
69 #define MIN_DOT_PEN_WIDTH .351
70
71 #ifndef DEFAULT_LINE_WIDTH_FACTOR
72 // in ems/1000
73 #define DEFAULT_LINE_WIDTH_FACTOR 40
74 #endif
75
76 const int DEFAULT_HPGL_UNITS = 1016;
77 int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR;
78 unsigned ncopies = 0; // 0 means don't send ncopies command
79
80 static int lookup_paper_size(const char *);
81
82 class lj4_font : public font {
83 public:
84 ~lj4_font();
85 void handle_unknown_font_command(const char *command, const char *arg,
86 const char *filename, int lineno);
87 static lj4_font *load_lj4_font(const char *);
88 int weight;
89 int style;
90 int proportional;
91 int typeface;
92 private:
93 lj4_font(const char *);
94 };
95
lj4_font(const char * nm)96 lj4_font::lj4_font(const char *nm)
97 : font(nm), weight(0), style(0), proportional(0), typeface(0)
98 {
99 }
100
~lj4_font()101 lj4_font::~lj4_font()
102 {
103 }
104
load_lj4_font(const char * s)105 lj4_font *lj4_font::load_lj4_font(const char *s)
106 {
107 lj4_font *f = new lj4_font(s);
108 if (!f->load()) {
109 delete f;
110 return 0;
111 }
112 return f;
113 }
114
115 static struct {
116 const char *s;
117 int lj4_font::*ptr;
118 int min;
119 int max;
120 } command_table[] = {
121 { "pclweight", &lj4_font::weight, -7, 7 },
122 { "pclstyle", &lj4_font::style, 0, 32767 },
123 { "pclproportional", &lj4_font::proportional, 0, 1 },
124 { "pcltypeface", &lj4_font::typeface, 0, 65535 },
125 };
126
handle_unknown_font_command(const char * command,const char * arg,const char * filename,int lineno)127 void lj4_font::handle_unknown_font_command(const char *command,
128 const char *arg,
129 const char *filename, int lineno)
130 {
131 for (unsigned int i = 0;
132 i < sizeof(command_table)/sizeof(command_table[0]); i++) {
133 if (strcmp(command, command_table[i].s) == 0) {
134 if (arg == 0)
135 fatal_with_file_and_line(filename, lineno,
136 "`%1' command requires an argument",
137 command);
138 char *ptr;
139 long n = strtol(arg, &ptr, 10);
140 if (n == 0 && ptr == arg)
141 fatal_with_file_and_line(filename, lineno,
142 "`%1' command requires numeric argument",
143 command);
144 if (n < command_table[i].min) {
145 error_with_file_and_line(filename, lineno,
146 "argument for `%1' command must not be less than %2",
147 command, command_table[i].min);
148 n = command_table[i].min;
149 }
150 else if (n > command_table[i].max) {
151 error_with_file_and_line(filename, lineno,
152 "argument for `%1' command must not be greater than %2",
153 command, command_table[i].max);
154 n = command_table[i].max;
155 }
156 this->*command_table[i].ptr = int(n);
157 break;
158 }
159 }
160 }
161
162 class lj4_printer : public printer {
163 public:
164 lj4_printer(int);
165 ~lj4_printer();
166 void set_char(int, font *, const environment *, int, const char *name);
167 void draw(int code, int *p, int np, const environment *env);
168 void begin_page(int);
169 void end_page(int page_length);
170 font *make_font(const char *);
171 void end_of_line();
172 private:
173 void set_line_thickness(int size, int dot = 0);
174 void hpgl_init();
175 void hpgl_start();
176 void hpgl_end();
177 int moveto(int hpos, int vpos);
178 int moveto1(int hpos, int vpos);
179
180 int cur_hpos;
181 int cur_vpos;
182 lj4_font *cur_font;
183 int cur_size;
184 unsigned short cur_symbol_set;
185 int x_offset;
186 int line_thickness;
187 double pen_width;
188 double hpgl_scale;
189 int hpgl_inited;
190 int paper_size;
191 };
192
193 inline
moveto(int hpos,int vpos)194 int lj4_printer::moveto(int hpos, int vpos)
195 {
196 if (cur_hpos != hpos || cur_vpos != vpos || cur_hpos < 0)
197 return moveto1(hpos, vpos);
198 else
199 return 1;
200 }
201
202 inline
hpgl_start()203 void lj4_printer::hpgl_start()
204 {
205 fputs("\033%1B", stdout);
206 }
207
208 inline
hpgl_end()209 void lj4_printer::hpgl_end()
210 {
211 fputs(";\033%0A", stdout);
212 }
213
lj4_printer(int ps)214 lj4_printer::lj4_printer(int ps)
215 : cur_hpos(-1),
216 cur_font(0),
217 cur_size(0),
218 cur_symbol_set(0),
219 line_thickness(-1),
220 pen_width(-1.0),
221 hpgl_inited(0)
222 {
223 if (7200 % font::res != 0)
224 fatal("invalid resolution %1: resolution must be a factor of 7200",
225 font::res);
226 fputs("\033E", stdout); // reset
227 if (font::res != 300)
228 printf("\033&u%dD", font::res); // unit of measure
229 if (ncopies > 0)
230 printf("\033&l%uX", ncopies);
231 paper_size = 0; // default to letter
232 if (font::papersize) {
233 int n = lookup_paper_size(font::papersize);
234 if (n < 0)
235 error("unknown paper size `%1'", font::papersize);
236 else
237 paper_size = n;
238 }
239 if (ps >= 0)
240 paper_size = ps;
241 printf("\033&l%dA" // paper size
242 "\033&l%dO" // orientation
243 "\033&l0E", // no top margin
244 paper_table[paper_size].code,
245 landscape_flag != 0);
246 if (landscape_flag)
247 x_offset = paper_table[paper_size].x_offset_landscape;
248 else
249 x_offset = paper_table[paper_size].x_offset_portrait;
250 x_offset = (x_offset * font::res) / 300;
251 if (duplex_flag)
252 printf("\033&l%dS", duplex_flag);
253 }
254
~lj4_printer()255 lj4_printer::~lj4_printer()
256 {
257 fputs("\033E", stdout);
258 }
259
begin_page(int)260 void lj4_printer::begin_page(int)
261 {
262 }
263
end_page(int)264 void lj4_printer::end_page(int)
265 {
266 putchar('\f');
267 cur_hpos = -1;
268 }
269
end_of_line()270 void lj4_printer::end_of_line()
271 {
272 cur_hpos = -1; // force absolute motion
273 }
274
275 inline
is_unprintable(unsigned char c)276 int is_unprintable(unsigned char c)
277 {
278 return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27);
279 }
280
set_char(int idx,font * f,const environment * env,int w,const char *)281 void lj4_printer::set_char(int idx, font *f, const environment *env,
282 int w, const char *)
283 {
284 int code = f->get_code(idx);
285
286 unsigned char ch = code & 0xff;
287 unsigned short symbol_set = code >> 8;
288 if (symbol_set != cur_symbol_set) {
289 printf("\033(%d%c", symbol_set/32, (symbol_set & 31) + 64);
290 cur_symbol_set = symbol_set;
291 }
292 if (f != cur_font) {
293 lj4_font *psf = (lj4_font *)f;
294 // FIXME only output those that are needed
295 printf("\033(s%dp%ds%db%dT",
296 psf->proportional,
297 psf->style,
298 psf->weight,
299 psf->typeface);
300 if (!psf->proportional || !cur_font || !cur_font->proportional)
301 cur_size = 0;
302 cur_font = psf;
303 }
304 if (env->size != cur_size) {
305 if (cur_font->proportional) {
306 static const char *quarters[] = { "", ".25", ".5", ".75" };
307 printf("\033(s%d%sV", env->size/4, quarters[env->size & 3]);
308 }
309 else {
310 double pitch = double(font::res)/w;
311 // PCL uses the next largest pitch, so round it down.
312 pitch = floor(pitch*100.0)/100.0;
313 printf("\033(s%.2fH", pitch);
314 }
315 cur_size = env->size;
316 }
317 if (!moveto(env->hpos, env->vpos))
318 return;
319 if (is_unprintable(ch))
320 fputs("\033&p1X", stdout);
321 putchar(ch);
322 cur_hpos += w;
323 }
324
moveto1(int hpos,int vpos)325 int lj4_printer::moveto1(int hpos, int vpos)
326 {
327 if (hpos < x_offset || vpos < 0)
328 return 0;
329 fputs("\033*p", stdout);
330 if (cur_hpos < 0)
331 printf("%dx%dY", hpos - x_offset, vpos);
332 else {
333 if (cur_hpos != hpos)
334 printf("%s%d%c", hpos > cur_hpos ? "+" : "",
335 hpos - cur_hpos, vpos == cur_vpos ? 'X' : 'x');
336 if (cur_vpos != vpos)
337 printf("%s%dY", vpos > cur_vpos ? "+" : "", vpos - cur_vpos);
338 }
339 cur_hpos = hpos;
340 cur_vpos = vpos;
341 return 1;
342 }
343
draw(int code,int * p,int np,const environment * env)344 void lj4_printer::draw(int code, int *p, int np, const environment *env)
345 {
346 switch (code) {
347 case 'R':
348 {
349 if (np != 2) {
350 error("2 arguments required for rule");
351 break;
352 }
353 int hpos = env->hpos;
354 int vpos = env->vpos;
355 int hsize = p[0];
356 int vsize = p[1];
357 if (hsize < 0) {
358 hpos += hsize;
359 hsize = -hsize;
360 }
361 if (vsize < 0) {
362 vpos += vsize;
363 vsize = -vsize;
364 }
365 if (!moveto(hpos, vpos))
366 return;
367 printf("\033*c%da%db0P", hsize, vsize);
368 break;
369 }
370 case 'l':
371 if (np != 2) {
372 error("2 arguments required for line");
373 break;
374 }
375 hpgl_init();
376 if (!moveto(env->hpos, env->vpos))
377 return;
378 hpgl_start();
379 set_line_thickness(env->size, p[0] == 0 && p[1] == 0);
380 printf("PD%d,%d", p[0], p[1]);
381 hpgl_end();
382 break;
383 case 'p':
384 case 'P':
385 {
386 if (np & 1) {
387 error("even number of arguments required for polygon");
388 break;
389 }
390 if (np == 0) {
391 error("no arguments for polygon");
392 break;
393 }
394 hpgl_init();
395 if (!moveto(env->hpos, env->vpos))
396 return;
397 hpgl_start();
398 if (code == 'p')
399 set_line_thickness(env->size);
400 printf("PMPD%d", p[0]);
401 for (int i = 1; i < np; i++)
402 printf(",%d", p[i]);
403 printf("PM2%cP", code == 'p' ? 'E' : 'F');
404 hpgl_end();
405 break;
406 }
407 case '~':
408 {
409 if (np & 1) {
410 error("even number of arguments required for spline");
411 break;
412 }
413 if (np == 0) {
414 error("no arguments for spline");
415 break;
416 }
417 hpgl_init();
418 if (!moveto(env->hpos, env->vpos))
419 return;
420 hpgl_start();
421 set_line_thickness(env->size);
422 printf("PD%d,%d", p[0]/2, p[1]/2);
423 const int tnum = 2;
424 const int tden = 3;
425 if (np > 2) {
426 fputs("BR", stdout);
427 for (int i = 0; i < np - 2; i += 2) {
428 if (i != 0)
429 putchar(',');
430 printf("%d,%d,%d,%d,%d,%d",
431 (p[i]*tnum)/(2*tden),
432 (p[i + 1]*tnum)/(2*tden),
433 p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden),
434 p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden),
435 (p[i] - p[i]/2) + p[i + 2]/2,
436 (p[i + 1] - p[i + 1]/2) + p[i + 3]/2);
437 }
438 }
439 printf("PR%d,%d", p[np - 2] - p[np - 2]/2, p[np - 1] - p[np - 1]/2);
440 hpgl_end();
441 break;
442 }
443 case 'c':
444 case 'C':
445 // troff adds an extra argument to C
446 if (np != 1 && !(code == 'C' && np == 2)) {
447 error("1 argument required for circle");
448 break;
449 }
450 hpgl_init();
451 if (!moveto(env->hpos + p[0]/2, env->vpos))
452 return;
453 hpgl_start();
454 if (code == 'c') {
455 set_line_thickness(env->size);
456 printf("CI%d", p[0]/2);
457 }
458 else
459 printf("WG%d,0,360", p[0]/2);
460 hpgl_end();
461 break;
462 case 'e':
463 case 'E':
464 if (np != 2) {
465 error("2 arguments required for ellipse");
466 break;
467 }
468 hpgl_init();
469 if (!moveto(env->hpos + p[0]/2, env->vpos))
470 return;
471 hpgl_start();
472 printf("SC0,%.4f,0,-%.4f,2", hpgl_scale * double(p[0])/p[1], hpgl_scale);
473 if (code == 'e') {
474 set_line_thickness(env->size);
475 printf("CI%d", p[1]/2);
476 }
477 else
478 printf("WG%d,0,360", p[1]/2);
479 printf("SC0,%.4f,0,-%.4f,2", hpgl_scale, hpgl_scale);
480 hpgl_end();
481 break;
482 case 'a':
483 {
484 if (np != 4) {
485 error("4 arguments required for arc");
486 break;
487 }
488 hpgl_init();
489 if (!moveto(env->hpos, env->vpos))
490 return;
491 hpgl_start();
492 set_line_thickness(env->size);
493 double c[2];
494 if (adjust_arc_center(p, c)) {
495 double sweep = ((atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])
496 - atan2(-c[1], -c[0]))
497 * 180.0/PI);
498 if (sweep > 0.0)
499 sweep -= 360.0;
500 printf("PDAR%d,%d,%f", int(c[0]), int(c[1]), sweep);
501 }
502 else
503 printf("PD%d,%d", p[0] + p[2], p[1] + p[3]);
504 hpgl_end();
505 }
506 break;
507 case 'f':
508 if (np != 1 && np != 2) {
509 error("1 argument required for fill");
510 break;
511 }
512 hpgl_init();
513 hpgl_start();
514 if (p[0] >= 0 && p[0] <= 1000)
515 printf("FT10,%d", p[0]/10);
516 hpgl_end();
517 break;
518 case 'F':
519 // not implemented yet
520 break;
521 case 't':
522 {
523 if (np == 0) {
524 line_thickness = -1;
525 }
526 else {
527 // troff gratuitously adds an extra 0
528 if (np != 1 && np != 2) {
529 error("0 or 1 argument required for thickness");
530 break;
531 }
532 line_thickness = p[0];
533 }
534 break;
535 }
536 default:
537 error("unrecognised drawing command `%1'", char(code));
538 break;
539 }
540 }
541
hpgl_init()542 void lj4_printer::hpgl_init()
543 {
544 if (hpgl_inited)
545 return;
546 hpgl_inited = 1;
547 hpgl_scale = double(DEFAULT_HPGL_UNITS)/font::res;
548 printf("\033&f0S" // push position
549 "\033*p0x0Y" // move to 0,0
550 "\033*c%dx%dy0T" // establish picture frame
551 "\033%%1B" // switch to HPGL
552 "SP1SC0,%.4f,0,-%.4f,2IR0,100,0,100" // set up scaling
553 "LA1,4,2,4" // round line ends and joins
554 "PR" // relative plotting
555 "TR0" // opaque
556 ";\033%%1A" // back to PCL
557 "\033&f1S", // pop position
558 MAX_PAPER_WIDTH, MAX_PAPER_HEIGHT,
559 hpgl_scale, hpgl_scale);
560 }
561
set_line_thickness(int size,int dot)562 void lj4_printer::set_line_thickness(int size, int dot)
563 {
564 double pw;
565 if (line_thickness < 0)
566 pw = (size * (line_width_factor * 25.4))/(font::sizescale * 72000.0);
567 else
568 pw = line_thickness*25.4/font::res;
569 if (dot && pw < MIN_DOT_PEN_WIDTH)
570 pw = MIN_DOT_PEN_WIDTH;
571 if (pw != pen_width) {
572 printf("PW%f", pw);
573 pen_width = pw;
574 }
575 }
576
make_font(const char * nm)577 font *lj4_printer::make_font(const char *nm)
578 {
579 return lj4_font::load_lj4_font(nm);
580 }
581
make_printer()582 printer *make_printer()
583 {
584 return new lj4_printer(user_paper_size);
585 }
586
587 static
lookup_paper_size(const char * s)588 int lookup_paper_size(const char *s)
589 {
590 for (unsigned int i = 0;
591 i < sizeof(paper_table)/sizeof(paper_table[0]); i++) {
592 // FIXME Perhaps allow unique prefix.
593 if (strcasecmp(s, paper_table[i].name) == 0)
594 return i;
595 }
596 return -1;
597 }
598
599 static void usage(FILE *stream);
600
601 extern "C" int optopt, optind;
602
main(int argc,char ** argv)603 int main(int argc, char **argv)
604 {
605 setlocale(LC_NUMERIC, "C");
606 program_name = argv[0];
607 static char stderr_buf[BUFSIZ];
608 setbuf(stderr, stderr_buf);
609 int c;
610 static const struct option long_options[] = {
611 { "help", no_argument, 0, CHAR_MAX + 1 },
612 { "version", no_argument, 0, 'v' },
613 { NULL, 0, 0, 0 }
614 };
615 while ((c = getopt_long(argc, argv, "c:d:F:I:lp:vw:", long_options, NULL))
616 != EOF)
617 switch(c) {
618 case 'l':
619 landscape_flag = 1;
620 break;
621 case 'I':
622 // ignore include search path
623 break;
624 case ':':
625 if (optopt == 'd') {
626 fprintf(stderr, "duplex assumed to be long-side\n");
627 duplex_flag = 1;
628 } else
629 fprintf(stderr, "option -%c requires an argument\n", optopt);
630 fflush(stderr);
631 break;
632 case 'd':
633 if (!isdigit(*optarg)) // this ugly hack prevents -d without
634 optind--; // args from messing up the arg list
635 duplex_flag = atoi(optarg);
636 if (duplex_flag != 1 && duplex_flag != 2) {
637 fprintf(stderr, "odd value for duplex; assumed to be long-side\n");
638 duplex_flag = 1;
639 }
640 break;
641 case 'p':
642 {
643 int n = lookup_paper_size(optarg);
644 if (n < 0)
645 error("unknown paper size `%1'", optarg);
646 else
647 user_paper_size = n;
648 break;
649 }
650 case 'v':
651 printf("GNU grolj4 (groff) version %s\n", Version_string);
652 exit(0);
653 break;
654 case 'F':
655 font::command_line_font_dir(optarg);
656 break;
657 case 'c':
658 {
659 char *ptr;
660 long n = strtol(optarg, &ptr, 10);
661 if (n == 0 && ptr == optarg)
662 error("argument for -c must be a positive integer");
663 else if (n <= 0 || n > 32767)
664 error("out of range argument for -c");
665 else
666 ncopies = unsigned(n);
667 break;
668 }
669 case 'w':
670 {
671 char *ptr;
672 long n = strtol(optarg, &ptr, 10);
673 if (n == 0 && ptr == optarg)
674 error("argument for -w must be a non-negative integer");
675 else if (n < 0 || n > INT_MAX)
676 error("out of range argument for -w");
677 else
678 line_width_factor = int(n);
679 break;
680 }
681 case CHAR_MAX + 1: // --help
682 usage(stdout);
683 exit(0);
684 break;
685 case '?':
686 usage(stderr);
687 exit(1);
688 break;
689 default:
690 assert(0);
691 }
692 SET_BINARY(fileno(stdout));
693 if (optind >= argc)
694 do_file("-");
695 else {
696 for (int i = optind; i < argc; i++)
697 do_file(argv[i]);
698 }
699 return 0;
700 }
701
usage(FILE * stream)702 static void usage(FILE *stream)
703 {
704 fprintf(stream,
705 "usage: %s [-lv] [-d [n]] [-c n] [-p paper_size]\n"
706 " [-w n] [-F dir] [files ...]\n",
707 program_name);
708 }
709