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