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
8This file is part of groff.
9
10groff is free software; you can redistribute it and/or modify it under
11the terms of the GNU General Public License as published by the Free
12Software Foundation; either version 2, or (at your option) any later
13version.
14
15groff is distributed in the hope that it will be useful, but WITHOUT ANY
16WARRANTY; without even the implied warranty of MERCHANTABILITY or
17FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
18for more details.
19
20You should have received a copy of the GNU General Public License along
21with groff; see the file COPYING.  If not, write to the Free Software
22Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
23
24/*
25TODO
26
27option to use beziers for circle/ellipse/arc
28option to use lines for spline (for LJ3)
29left/top offset registration
30output bin selection option
31paper source option
32output non-integer parameters using fixed point numbers
33X command to insert contents of file
34X command to specify inline escape sequence (how to specify unprintable chars?)
35X command to include bitmap graphics
36*/
37
38#include "driver.h"
39#include "nonposix.h"
40
41extern "C" const char *Version_string;
42
43static 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
61static int user_paper_size = -1;
62static int landscape_flag = 0;
63static 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
78const int DEFAULT_HPGL_UNITS = 1016;
79int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR;
80unsigned ncopies = 0;		// 0 means don't send ncopies command
81
82static int lookup_paper_size(const char *);
83
84class lj4_font : public font {
85public:
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;
94private:
95  lj4_font(const char *);
96};
97
98lj4_font::lj4_font(const char *nm)
99: font(nm), weight(0), style(0), proportional(0), typeface(0)
100{
101}
102
103lj4_font::~lj4_font()
104{
105}
106
107lj4_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
117static 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
129void 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
164class lj4_printer : public printer {
165public:
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();
174private:
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
195inline
196int 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
204inline
205void lj4_printer::hpgl_start()
206{
207  fputs("\033%1B", stdout);
208}
209
210inline
211void lj4_printer::hpgl_end()
212{
213  fputs(";\033%0A", stdout);
214}
215
216lj4_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
257lj4_printer::~lj4_printer()
258{
259  fputs("\033E", stdout);
260}
261
262void lj4_printer::begin_page(int)
263{
264}
265
266void lj4_printer::end_page(int)
267{
268  putchar('\f');
269  cur_hpos = -1;
270}
271
272void lj4_printer::end_of_line()
273{
274  cur_hpos = -1;		// force absolute motion
275}
276
277inline
278int is_unprintable(unsigned char c)
279{
280  return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27);
281}
282
283void 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
327int 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
346void 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
544void 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
564void 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
579font *lj4_printer::make_font(const char *nm)
580{
581  return lj4_font::load_lj4_font(nm);
582}
583
584printer *make_printer()
585{
586  return new lj4_printer(user_paper_size);
587}
588
589static
590int 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
601static void usage(FILE *stream);
602
603extern "C" int optopt, optind;
604
605int 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
704static 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