xref: /netbsd-src/external/gpl2/groff/dist/src/devices/grolj4/lj4.cpp (revision 89a07cf815a29524268025a1139fac4c5190f765)
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