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