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