1/*	$NetBSD: troff.cpp,v 1.1.1.1 2016/01/13 18:41:49 christos Exp $	*/
2
3// -*- C++ -*-
4/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2005
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#include "pic.h"
25#include "common.h"
26
27
28const double RELATIVE_THICKNESS = -1.0;
29const double BAD_THICKNESS = -2.0;
30
31class simple_output : public common_output {
32  virtual void simple_line(const position &, const position &) = 0;
33  virtual void simple_spline(const position &, const position *, int n) = 0;
34  virtual void simple_arc(const position &, const position &,
35			  const position &) = 0;
36  virtual void simple_circle(int, const position &, double rad) = 0;
37  virtual void simple_ellipse(int, const position &, const distance &) = 0;
38  virtual void simple_polygon(int, const position *, int) = 0;
39  virtual void line_thickness(double) = 0;
40  virtual void set_fill(double) = 0;
41  virtual void set_color(char *, char *) = 0;
42  virtual void reset_color() = 0;
43  virtual char *get_last_filled() = 0;
44  void dot(const position &, const line_type &) = 0;
45public:
46  void start_picture(double sc, const position &ll, const position &ur) = 0;
47  void finish_picture() = 0;
48  void text(const position &, text_piece *, int, double) = 0;
49  void line(const position &, const position *, int n,
50	    const line_type &);
51  void polygon(const position *, int n,
52	       const line_type &, double);
53  void spline(const position &, const position *, int n,
54	      const line_type &);
55  void arc(const position &, const position &, const position &,
56	   const line_type &);
57  void circle(const position &, double rad, const line_type &, double);
58  void ellipse(const position &, const distance &, const line_type &, double);
59  int supports_filled_polygons();
60};
61
62int simple_output::supports_filled_polygons()
63{
64  return driver_extension_flag != 0;
65}
66
67void simple_output::arc(const position &start, const position &cent,
68			const position &end, const line_type &lt)
69{
70  switch (lt.type) {
71  case line_type::solid:
72    line_thickness(lt.thickness);
73    simple_arc(start, cent, end);
74    break;
75  case line_type::invisible:
76    break;
77  case line_type::dashed:
78    dashed_arc(start, cent, end, lt);
79    break;
80  case line_type::dotted:
81    dotted_arc(start, cent, end, lt);
82    break;
83  }
84}
85
86void simple_output::line(const position &start, const position *v, int n,
87			 const line_type &lt)
88{
89  position pos = start;
90  line_thickness(lt.thickness);
91  for (int i = 0; i < n; i++) {
92    switch (lt.type) {
93    case line_type::solid:
94      simple_line(pos, v[i]);
95      break;
96    case line_type::dotted:
97      {
98	distance vec(v[i] - pos);
99	double dist = hypot(vec);
100	int ndots = int(dist/lt.dash_width + .5);
101	if (ndots == 0)
102	  dot(pos, lt);
103	else {
104	  vec /= double(ndots);
105	  for (int j = 0; j <= ndots; j++)
106	    dot(pos + vec*j, lt);
107	}
108      }
109      break;
110    case line_type::dashed:
111      {
112	distance vec(v[i] - pos);
113	double dist = hypot(vec);
114	if (dist <= lt.dash_width*2.0)
115	  simple_line(pos, v[i]);
116	else {
117	  int ndashes = int((dist - lt.dash_width)/(lt.dash_width*2.0) + .5);
118	  distance dash_vec = vec*(lt.dash_width/dist);
119	  double dash_gap = (dist - lt.dash_width)/ndashes;
120	  distance dash_gap_vec = vec*(dash_gap/dist);
121	  for (int j = 0; j <= ndashes; j++) {
122	    position s(pos + dash_gap_vec*j);
123	    simple_line(s, s + dash_vec);
124	  }
125	}
126      }
127      break;
128    case line_type::invisible:
129      break;
130    default:
131      assert(0);
132    }
133    pos = v[i];
134  }
135}
136
137void simple_output::spline(const position &start, const position *v, int n,
138			   const line_type &lt)
139{
140  line_thickness(lt.thickness);
141  simple_spline(start, v, n);
142}
143
144void simple_output::polygon(const position *v, int n,
145			    const line_type &lt, double fill)
146{
147  if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
148    if (get_last_filled() == 0)
149      set_fill(fill);
150    simple_polygon(1, v, n);
151  }
152  if (lt.type == line_type::solid && driver_extension_flag) {
153    line_thickness(lt.thickness);
154    simple_polygon(0, v, n);
155  }
156  else if (lt.type != line_type::invisible) {
157    line_thickness(lt.thickness);
158    line(v[n - 1], v, n, lt);
159  }
160}
161
162void simple_output::circle(const position &cent, double rad,
163			   const line_type &lt, double fill)
164{
165  if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
166    if (get_last_filled() == 0)
167      set_fill(fill);
168    simple_circle(1, cent, rad);
169  }
170  line_thickness(lt.thickness);
171  switch (lt.type) {
172  case line_type::invisible:
173    break;
174  case line_type::dashed:
175    dashed_circle(cent, rad, lt);
176    break;
177  case line_type::dotted:
178    dotted_circle(cent, rad, lt);
179    break;
180  case line_type::solid:
181    simple_circle(0, cent, rad);
182    break;
183  default:
184    assert(0);
185  }
186}
187
188void simple_output::ellipse(const position &cent, const distance &dim,
189			    const line_type &lt, double fill)
190{
191  if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
192    if (get_last_filled() == 0)
193      set_fill(fill);
194    simple_ellipse(1, cent, dim);
195  }
196  if (lt.type != line_type::invisible)
197    line_thickness(lt.thickness);
198  switch (lt.type) {
199  case line_type::invisible:
200    break;
201  case line_type::dotted:
202    dotted_ellipse(cent, dim, lt);
203    break;
204  case line_type::dashed:
205    dashed_ellipse(cent, dim, lt);
206    break;
207  case line_type::solid:
208    simple_ellipse(0, cent, dim);
209    break;
210  default:
211    assert(0);
212  }
213}
214
215class troff_output : public simple_output {
216  const char *last_filename;
217  position upper_left;
218  double height;
219  double scale;
220  double last_line_thickness;
221  double last_fill;
222  char *last_filled;		// color
223  char *last_outlined;		// color
224public:
225  troff_output();
226  ~troff_output();
227  void start_picture(double, const position &ll, const position &ur);
228  void finish_picture();
229  void text(const position &, text_piece *, int, double);
230  void dot(const position &, const line_type &);
231  void command(const char *, const char *, int);
232  void set_location(const char *, int);
233  void simple_line(const position &, const position &);
234  void simple_spline(const position &, const position *, int n);
235  void simple_arc(const position &, const position &, const position &);
236  void simple_circle(int, const position &, double rad);
237  void simple_ellipse(int, const position &, const distance &);
238  void simple_polygon(int, const position *, int);
239  void line_thickness(double p);
240  void set_fill(double);
241  void set_color(char *, char *);
242  void reset_color();
243  char *get_last_filled();
244  char *get_outline_color();
245  position transform(const position &);
246};
247
248output *make_troff_output()
249{
250  return new troff_output;
251}
252
253troff_output::troff_output()
254: last_filename(0), last_line_thickness(BAD_THICKNESS),
255  last_fill(-1.0), last_filled(0), last_outlined(0)
256{
257}
258
259troff_output::~troff_output()
260{
261}
262
263inline position troff_output::transform(const position &pos)
264{
265  return position((pos.x - upper_left.x)/scale,
266		  (upper_left.y - pos.y)/scale);
267}
268
269#define FILL_REG "00"
270
271// If this register > 0, then pic will generate \X'ps: ...' commands
272// if the aligned attribute is used.
273#define GROPS_REG "0p"
274
275// If this register is defined, geqn won't produce `\x's.
276#define EQN_NO_EXTRA_SPACE_REG "0x"
277
278void troff_output::start_picture(double sc,
279				 const position &ll, const position &ur)
280{
281  upper_left.x = ll.x;
282  upper_left.y = ur.y;
283  scale = compute_scale(sc, ll, ur);
284  height = (ur.y - ll.y)/scale;
285  double width = (ur.x - ll.x)/scale;
286  printf(".PS %.3fi %.3fi", height, width);
287  if (args)
288    printf(" %s\n", args);
289  else
290    putchar('\n');
291  printf(".\\\" %g %g %g %g\n", ll.x, ll.y, ur.x, ur.y);
292  printf(".\\\" %.3fi %.3fi %.3fi %.3fi\n", 0.0, height, width, 0.0);
293  printf(".nr " FILL_REG " \\n(.u\n.nf\n");
294  printf(".nr " EQN_NO_EXTRA_SPACE_REG " 1\n");
295  // This guarantees that if the picture is used in a diversion it will
296  // have the right width.
297  printf("\\h'%.3fi'\n.sp -1\n", width);
298}
299
300void troff_output::finish_picture()
301{
302  line_thickness(BAD_THICKNESS);
303  last_fill = -1.0;		// force it to be reset for each picture
304  reset_color();
305  if (!flyback_flag)
306    printf(".sp %.3fi+1\n", height);
307  printf(".if \\n(" FILL_REG " .fi\n");
308  printf(".br\n");
309  printf(".nr " EQN_NO_EXTRA_SPACE_REG " 0\n");
310  // this is a little gross
311  set_location(current_filename, current_lineno);
312  fputs(flyback_flag ? ".PF\n" : ".PE\n", stdout);
313}
314
315void troff_output::command(const char *s,
316			   const char *filename, int lineno)
317{
318  if (filename != 0)
319    set_location(filename, lineno);
320  fputs(s, stdout);
321  putchar('\n');
322}
323
324void troff_output::simple_circle(int filled, const position &cent, double rad)
325{
326  position c = transform(cent);
327  printf("\\h'%.3fi'"
328	 "\\v'%.3fi'"
329	 "\\D'%c %.3fi'"
330	 "\n.sp -1\n",
331	 c.x - rad/scale,
332	 c.y,
333	 (filled ? 'C' : 'c'),
334	 rad*2.0/scale);
335}
336
337void troff_output::simple_ellipse(int filled, const position &cent,
338				  const distance &dim)
339{
340  position c = transform(cent);
341  printf("\\h'%.3fi'"
342	 "\\v'%.3fi'"
343	 "\\D'%c %.3fi %.3fi'"
344	 "\n.sp -1\n",
345	 c.x - dim.x/(2.0*scale),
346	 c.y,
347	 (filled ? 'E' : 'e'),
348	 dim.x/scale, dim.y/scale);
349}
350
351void troff_output::simple_arc(const position &start, const distance &cent,
352			      const distance &end)
353{
354  position s = transform(start);
355  position c = transform(cent);
356  distance cv = c - s;
357  distance ev = transform(end) - c;
358  printf("\\h'%.3fi'"
359	 "\\v'%.3fi'"
360	 "\\D'a %.3fi %.3fi %.3fi %.3fi'"
361	 "\n.sp -1\n",
362	 s.x, s.y, cv.x, cv.y, ev.x, ev.y);
363}
364
365void troff_output::simple_line(const position &start, const position &end)
366{
367  position s = transform(start);
368  distance ev = transform(end) - s;
369  printf("\\h'%.3fi'"
370	 "\\v'%.3fi'"
371	 "\\D'l %.3fi %.3fi'"
372	 "\n.sp -1\n",
373	 s.x, s.y, ev.x, ev.y);
374}
375
376void troff_output::simple_spline(const position &start,
377				 const position *v, int n)
378{
379  position pos = transform(start);
380  printf("\\h'%.3fi'"
381	 "\\v'%.3fi'",
382	 pos.x, pos.y);
383  fputs("\\D'~ ", stdout);
384  for (int i = 0; i < n; i++) {
385    position temp = transform(v[i]);
386    distance d = temp - pos;
387    pos = temp;
388    if (i != 0)
389      putchar(' ');
390    printf("%.3fi %.3fi", d.x, d.y);
391  }
392  printf("'\n.sp -1\n");
393}
394
395// a solid polygon
396
397void troff_output::simple_polygon(int filled, const position *v, int n)
398{
399  position pos = transform(v[0]);
400  printf("\\h'%.3fi'"
401	 "\\v'%.3fi'",
402	 pos.x, pos.y);
403  printf("\\D'%c ", (filled ? 'P' : 'p'));
404  for (int i = 1; i < n; i++) {
405    position temp = transform(v[i]);
406    distance d = temp - pos;
407    pos = temp;
408    if (i != 1)
409      putchar(' ');
410    printf("%.3fi %.3fi", d.x, d.y);
411  }
412  printf("'\n.sp -1\n");
413}
414
415const double TEXT_AXIS = 0.22;	// in ems
416
417static const char *choose_delimiter(const char *text)
418{
419  if (strchr(text, '\'') == 0)
420    return "'";
421  else
422    return "\\(ts";
423}
424
425void troff_output::text(const position &center, text_piece *v, int n,
426			double ang)
427{
428  line_thickness(BAD_THICKNESS); // the text might use lines (eg in equations)
429  int rotate_flag = 0;
430  if (driver_extension_flag && ang != 0.0) {
431    rotate_flag = 1;
432    position c = transform(center);
433    printf(".if \\n(" GROPS_REG " \\{\\\n"
434	   "\\h'%.3fi'"
435	   "\\v'%.3fi'"
436	   "\\X'ps: exec gsave currentpoint 2 copy translate %.4f rotate neg exch neg exch translate'"
437	   "\n.sp -1\n"
438	   ".\\}\n",
439	   c.x, c.y, -ang*180.0/M_PI);
440  }
441  for (int i = 0; i < n; i++)
442    if (v[i].text != 0 && *v[i].text != '\0') {
443      position c = transform(center);
444      if (v[i].filename != 0)
445	set_location(v[i].filename, v[i].lineno);
446      printf("\\h'%.3fi", c.x);
447      const char *delim = choose_delimiter(v[i].text);
448      if (v[i].adj.h == RIGHT_ADJUST)
449	printf("-\\w%s%s%su", delim, v[i].text, delim);
450      else if (v[i].adj.h != LEFT_ADJUST)
451	printf("-(\\w%s%s%su/2u)", delim, v[i].text, delim);
452      putchar('\'');
453      printf("\\v'%.3fi-(%dv/2u)+%dv+%.2fm",
454	     c.y,
455	     n - 1,
456	     i,
457	     TEXT_AXIS);
458      if (v[i].adj.v == ABOVE_ADJUST)
459	printf("-.5v");
460      else if (v[i].adj.v == BELOW_ADJUST)
461	printf("+.5v");
462      putchar('\'');
463      fputs(v[i].text, stdout);
464      fputs("\n.sp -1\n", stdout);
465    }
466  if (rotate_flag)
467    printf(".if '\\*(.T'ps' \\{\\\n"
468	   "\\X'ps: exec grestore'\n.sp -1\n"
469	   ".\\}\n");
470}
471
472void troff_output::line_thickness(double p)
473{
474  if (p < 0.0)
475    p = RELATIVE_THICKNESS;
476  if (driver_extension_flag && p != last_line_thickness) {
477    printf("\\D't %.3fp'\\h'%.3fp'\n.sp -1\n", p, -p);
478    last_line_thickness = p;
479  }
480}
481
482void troff_output::set_fill(double f)
483{
484  if (driver_extension_flag && f != last_fill) {
485    // \D'Fg ...' emits a node only in compatibility mode,
486    // thus we add a dummy node
487    printf("\\&\\D'Fg %.3f'\n.sp -1\n", 1.0 - f);
488    last_fill = f;
489  }
490  if (last_filled) {
491    free(last_filled);
492    last_filled = 0;
493    printf(".fcolor\n");
494  }
495}
496
497void troff_output::set_color(char *color_fill, char *color_outlined)
498{
499  if (driver_extension_flag) {
500    if (last_filled || last_outlined) {
501      reset_color();
502    }
503    // .gcolor and .fcolor emit a node in compatibility mode only,
504    // but that won't work anyway
505    if (color_fill) {
506      printf(".fcolor %s\n", color_fill);
507      last_filled = strsave(color_fill);
508    }
509    if (color_outlined) {
510      printf(".gcolor %s\n", color_outlined);
511      last_outlined = strsave(color_outlined);
512    }
513  }
514}
515
516void troff_output::reset_color()
517{
518  if (driver_extension_flag) {
519    if (last_filled) {
520      printf(".fcolor\n");
521      a_delete last_filled;
522      last_filled = 0;
523    }
524    if (last_outlined) {
525      printf(".gcolor\n");
526      a_delete last_outlined;
527      last_outlined = 0;
528    }
529  }
530}
531
532char *troff_output::get_last_filled()
533{
534  return last_filled;
535}
536
537char *troff_output::get_outline_color()
538{
539  return last_outlined;
540}
541
542const double DOT_AXIS = .044;
543
544void troff_output::dot(const position &cent, const line_type &lt)
545{
546  if (driver_extension_flag) {
547    line_thickness(lt.thickness);
548    simple_line(cent, cent);
549  }
550  else {
551    position c = transform(cent);
552    printf("\\h'%.3fi-(\\w'.'u/2u)'"
553	   "\\v'%.3fi+%.2fm'"
554	   ".\n.sp -1\n",
555	   c.x,
556	   c.y,
557	   DOT_AXIS);
558  }
559}
560
561void troff_output::set_location(const char *s, int n)
562{
563  if (last_filename != 0 && strcmp(s, last_filename) == 0)
564    printf(".lf %d\n", n);
565  else {
566    printf(".lf %d %s\n", n, s);
567    last_filename = s;
568  }
569}
570