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
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
10Software Foundation; either version 2, or (at your option) any later
11version.
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
18You should have received a copy of the GNU General Public License along
19with groff; see the file COPYING.  If not, write to the Free Software
20Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22/*
23TODO
24
25option to use beziers for circle/ellipse/arc
26option to use lines for spline (for LJ3)
27left/top offset registration
28output bin selection option
29paper source option
30output non-integer parameters using fixed point numbers
31X command to insert contents of file
32X command to specify inline escape sequence (how to specify unprintable chars?)
33X command to include bitmap graphics
34*/
35
36#include "driver.h"
37#include "nonposix.h"
38
39extern "C" const char *Version_string;
40
41static 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
59static int user_paper_size = -1;
60static int landscape_flag = 0;
61static 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
76const int DEFAULT_HPGL_UNITS = 1016;
77int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR;
78unsigned ncopies = 0;		// 0 means don't send ncopies command
79
80static int lookup_paper_size(const char *);
81
82class lj4_font : public font {
83public:
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;
92private:
93  lj4_font(const char *);
94};
95
96lj4_font::lj4_font(const char *nm)
97: font(nm), weight(0), style(0), proportional(0), typeface(0)
98{
99}
100
101lj4_font::~lj4_font()
102{
103}
104
105lj4_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
115static 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
127void 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
162class lj4_printer : public printer {
163public:
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();
172private:
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
193inline
194int 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
202inline
203void lj4_printer::hpgl_start()
204{
205  fputs("\033%1B", stdout);
206}
207
208inline
209void lj4_printer::hpgl_end()
210{
211  fputs(";\033%0A", stdout);
212}
213
214lj4_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
255lj4_printer::~lj4_printer()
256{
257  fputs("\033E", stdout);
258}
259
260void lj4_printer::begin_page(int)
261{
262}
263
264void lj4_printer::end_page(int)
265{
266  putchar('\f');
267  cur_hpos = -1;
268}
269
270void lj4_printer::end_of_line()
271{
272  cur_hpos = -1;		// force absolute motion
273}
274
275inline
276int is_unprintable(unsigned char c)
277{
278  return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27);
279}
280
281void 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
325int 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
344void 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
542void 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
562void 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
577font *lj4_printer::make_font(const char *nm)
578{
579  return lj4_font::load_lj4_font(nm);
580}
581
582printer *make_printer()
583{
584  return new lj4_printer(user_paper_size);
585}
586
587static
588int 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
599static void usage(FILE *stream);
600
601extern "C" int optopt, optind;
602
603int 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
702static 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