troff.cpp revision 114402
1230479Snetchild// -*- C++ -*-
2230479Snetchild/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003
3230479Snetchild   Free Software Foundation, Inc.
4230479Snetchild     Written by James Clark (jjc@jclark.com)
5230479Snetchild
6230479SnetchildThis file is part of groff.
7230479Snetchild
8230479Snetchildgroff is free software; you can redistribute it and/or modify it under
9230479Snetchildthe terms of the GNU General Public License as published by the Free
10230479SnetchildSoftware Foundation; either version 2, or (at your option) any later
11230479Snetchildversion.
12230479Snetchild
13230479Snetchildgroff is distributed in the hope that it will be useful, but WITHOUT ANY
14230479SnetchildWARRANTY; without even the implied warranty of MERCHANTABILITY or
15230479SnetchildFITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16230479Snetchildfor more details.
17230479Snetchild
18230479SnetchildYou should have received a copy of the GNU General Public License along
19230479Snetchildwith groff; see the file COPYING.  If not, write to the Free Software
20230479SnetchildFoundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21230479Snetchild
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  case line_type::dashed:
201  case line_type::solid:
202    simple_ellipse(0, cent, dim);
203    break;
204  default:
205    assert(0);
206  }
207}
208
209class troff_output : public simple_output {
210  const char *last_filename;
211  position upper_left;
212  double height;
213  double scale;
214  double last_line_thickness;
215  double last_fill;
216  char *last_filled;		// color
217  char *last_outlined;		// color
218public:
219  troff_output();
220  ~troff_output();
221  void start_picture(double, const position &ll, const position &ur);
222  void finish_picture();
223  void text(const position &, text_piece *, int, double);
224  void dot(const position &, const line_type &);
225  void command(const char *, const char *, int);
226  void set_location(const char *, int);
227  void simple_line(const position &, const position &);
228  void simple_spline(const position &, const position *, int n);
229  void simple_arc(const position &, const position &, const position &);
230  void simple_circle(int, const position &, double rad);
231  void simple_ellipse(int, const position &, const distance &);
232  void simple_polygon(int, const position *, int);
233  void line_thickness(double p);
234  void set_fill(double);
235  void set_color(char *, char *);
236  void reset_color();
237  char *get_last_filled();
238  char *get_outline_color();
239  position transform(const position &);
240};
241
242output *make_troff_output()
243{
244  return new troff_output;
245}
246
247troff_output::troff_output()
248: last_filename(0), last_line_thickness(BAD_THICKNESS),
249  last_fill(-1.0), last_filled(0), last_outlined(0)
250{
251}
252
253troff_output::~troff_output()
254{
255}
256
257inline position troff_output::transform(const position &pos)
258{
259  return position((pos.x - upper_left.x)/scale,
260		  (upper_left.y - pos.y)/scale);
261}
262
263#define FILL_REG "00"
264
265// If this register > 0, then pic will generate \X'ps: ...' commands
266// if the aligned attribute is used.
267#define GROPS_REG "0p"
268
269// If this register is defined, geqn won't produce `\x's.
270#define EQN_NO_EXTRA_SPACE_REG "0x"
271
272void troff_output::start_picture(double sc,
273				 const position &ll, const position &ur)
274{
275  upper_left.x = ll.x;
276  upper_left.y = ur.y;
277  scale = compute_scale(sc, ll, ur);
278  height = (ur.y - ll.y)/scale;
279  double width = (ur.x - ll.x)/scale;
280  printf(".PS %.3fi %.3fi", height, width);
281  if (args)
282    printf(" %s\n", args);
283  else
284    putchar('\n');
285  printf(".\\\" %g %g %g %g\n", ll.x, ll.y, ur.x, ur.y);
286  printf(".\\\" %.3fi %.3fi %.3fi %.3fi\n", 0.0, height, width, 0.0);
287  printf(".nr " FILL_REG " \\n(.u\n.nf\n");
288  printf(".nr " EQN_NO_EXTRA_SPACE_REG " 1\n");
289  // This guarantees that if the picture is used in a diversion it will
290  // have the right width.
291  printf("\\h'%.3fi'\n.sp -1\n", width);
292}
293
294void troff_output::finish_picture()
295{
296  line_thickness(BAD_THICKNESS);
297  last_fill = -1.0;		// force it to be reset for each picture
298  reset_color();
299  if (!flyback_flag)
300    printf(".sp %.3fi+1\n", height);
301  printf(".if \\n(" FILL_REG " .fi\n");
302  printf(".br\n");
303  printf(".nr " EQN_NO_EXTRA_SPACE_REG " 0\n");
304  // this is a little gross
305  set_location(current_filename, current_lineno);
306  fputs(flyback_flag ? ".PF\n" : ".PE\n", stdout);
307}
308
309void troff_output::command(const char *s,
310			   const char *filename, int lineno)
311{
312  if (filename != 0)
313    set_location(filename, lineno);
314  fputs(s, stdout);
315  putchar('\n');
316}
317
318void troff_output::simple_circle(int filled, const position &cent, double rad)
319{
320  position c = transform(cent);
321  printf("\\h'%.3fi'"
322	 "\\v'%.3fi'"
323	 "\\D'%c%.3fi'"
324	 "\n.sp -1\n",
325	 c.x - rad/scale,
326	 c.y,
327	 (filled ? 'C' : 'c'),
328	 rad*2.0/scale);
329}
330
331void troff_output::simple_ellipse(int filled, const position &cent,
332				  const distance &dim)
333{
334  position c = transform(cent);
335  printf("\\h'%.3fi'"
336	 "\\v'%.3fi'"
337	 "\\D'%c%.3fi %.3fi'"
338	 "\n.sp -1\n",
339	 c.x - dim.x/(2.0*scale),
340	 c.y,
341	 (filled ? 'E' : 'e'),
342	 dim.x/scale, dim.y/scale);
343}
344
345void troff_output::simple_arc(const position &start, const distance &cent,
346			      const distance &end)
347{
348  position s = transform(start);
349  position c = transform(cent);
350  distance cv = c - s;
351  distance ev = transform(end) - c;
352  printf("\\h'%.3fi'"
353	 "\\v'%.3fi'"
354	 "\\D'a%.3fi %.3fi %.3fi %.3fi'"
355	 "\n.sp -1\n",
356	 s.x, s.y, cv.x, cv.y, ev.x, ev.y);
357}
358
359void troff_output::simple_line(const position &start, const position &end)
360{
361  position s = transform(start);
362  distance ev = transform(end) - s;
363  printf("\\h'%.3fi'"
364	 "\\v'%.3fi'"
365	 "\\D'l%.3fi %.3fi'"
366	 "\n.sp -1\n",
367	 s.x, s.y, ev.x, ev.y);
368}
369
370void troff_output::simple_spline(const position &start,
371				 const position *v, int n)
372{
373  position pos = transform(start);
374  printf("\\h'%.3fi'"
375	 "\\v'%.3fi'",
376	 pos.x, pos.y);
377  fputs("\\D'~", stdout);
378  for (int i = 0; i < n; i++) {
379    position temp = transform(v[i]);
380    distance d = temp - pos;
381    pos = temp;
382    if (i != 0)
383      putchar(' ');
384    printf("%.3fi %.3fi", d.x, d.y);
385  }
386  printf("'\n.sp -1\n");
387}
388
389// a solid polygon
390
391void troff_output::simple_polygon(int filled, const position *v, int n)
392{
393  position pos = transform(v[0]);
394  printf("\\h'%.3fi'"
395	 "\\v'%.3fi'",
396	 pos.x, pos.y);
397  printf("\\D'%c", (filled ? 'P' : 'p'));
398  for (int i = 1; i < n; i++) {
399    position temp = transform(v[i]);
400    distance d = temp - pos;
401    pos = temp;
402    if (i != 1)
403      putchar(' ');
404    printf("%.3fi %.3fi", d.x, d.y);
405  }
406  printf("'\n.sp -1\n");
407}
408
409const double TEXT_AXIS = 0.22;	// in ems
410
411static const char *choose_delimiter(const char *text)
412{
413  if (strchr(text, '\'') == 0)
414    return "'";
415  else
416    return "\\(ts";
417}
418
419void troff_output::text(const position &center, text_piece *v, int n,
420			double ang)
421{
422  line_thickness(BAD_THICKNESS); // the text might use lines (eg in equations)
423  int rotate_flag = 0;
424  if (driver_extension_flag && ang != 0.0) {
425    rotate_flag = 1;
426    position c = transform(center);
427    printf(".if \\n(" GROPS_REG " \\{\\\n"
428	   "\\h'%.3fi'"
429	   "\\v'%.3fi'"
430	   "\\X'ps: exec gsave currentpoint 2 copy translate %.4f rotate neg exch neg exch translate'"
431	   "\n.sp -1\n"
432	   ".\\}\n",
433	   c.x, c.y, -ang*180.0/M_PI);
434  }
435  for (int i = 0; i < n; i++)
436    if (v[i].text != 0 && *v[i].text != '\0') {
437      position c = transform(center);
438      if (v[i].filename != 0)
439	set_location(v[i].filename, v[i].lineno);
440      printf("\\h'%.3fi", c.x);
441      const char *delim = choose_delimiter(v[i].text);
442      if (v[i].adj.h == RIGHT_ADJUST)
443	printf("-\\w%s%s%su", delim, v[i].text, delim);
444      else if (v[i].adj.h != LEFT_ADJUST)
445	printf("-(\\w%s%s%su/2u)", delim, v[i].text, delim);
446      putchar('\'');
447      printf("\\v'%.3fi-(%dv/2u)+%dv+%.2fm",
448	     c.y,
449	     n - 1,
450	     i,
451	     TEXT_AXIS);
452      if (v[i].adj.v == ABOVE_ADJUST)
453	printf("-.5v");
454      else if (v[i].adj.v == BELOW_ADJUST)
455	printf("+.5v");
456      putchar('\'');
457      fputs(v[i].text, stdout);
458      fputs("\n.sp -1\n", stdout);
459    }
460  if (rotate_flag)
461    printf(".if '\\*(.T'ps' \\{\\\n"
462	   "\\X'ps: exec grestore'\n.sp -1\n"
463	   ".\\}\n");
464}
465
466void troff_output::line_thickness(double p)
467{
468  if (p < 0.0)
469    p = RELATIVE_THICKNESS;
470  if (driver_extension_flag && p != last_line_thickness) {
471    printf("\\D't %.3fp'\\h'%.3fp'\n.sp -1\n", p, -p);
472    last_line_thickness = p;
473  }
474}
475
476void troff_output::set_fill(double f)
477{
478  if (driver_extension_flag && f != last_fill) {
479    printf("\\D'Fg %.3f'\n.sp -1\n", 1.0 - f);
480    last_fill = f;
481  }
482  if (last_filled) {
483    free(last_filled);
484    last_filled = 0;
485    printf("\\M[]\n.sp -1\n");
486  }
487}
488
489void troff_output::set_color(char *color_fill, char *color_outlined)
490{
491  if (driver_extension_flag) {
492    if (last_filled || last_outlined) {
493      reset_color();
494    }
495    if (color_fill) {
496      printf("\\M[%s]\n.sp -1\n", color_fill);
497      last_filled = strsave(color_fill);
498    }
499    if (color_outlined) {
500      printf("\\m[%s]\n.sp -1\n", color_outlined);
501      last_outlined = strsave(color_outlined);
502    }
503  }
504}
505
506void troff_output::reset_color()
507{
508  if (driver_extension_flag) {
509    if (last_filled) {
510      printf("\\M[]\n.sp -1\n");
511      a_delete last_filled;
512      last_filled = 0;
513    }
514    if (last_outlined) {
515      printf("\\m[]\n.sp -1\n");
516      a_delete last_outlined;
517      last_outlined = 0;
518    }
519  }
520}
521
522char *troff_output::get_last_filled()
523{
524  return last_filled;
525}
526
527char *troff_output::get_outline_color()
528{
529  return last_outlined;
530}
531
532const double DOT_AXIS = .044;
533
534void troff_output::dot(const position &cent, const line_type &lt)
535{
536  if (driver_extension_flag) {
537    line_thickness(lt.thickness);
538    simple_line(cent, cent);
539  }
540  else {
541    position c = transform(cent);
542    printf("\\h'%.3fi-(\\w'.'u/2u)'"
543	   "\\v'%.3fi+%.2fm'"
544	   ".\n.sp -1\n",
545	   c.x,
546	   c.y,
547	   DOT_AXIS);
548  }
549}
550
551void troff_output::set_location(const char *s, int n)
552{
553  if (last_filename != 0 && strcmp(s, last_filename) == 0)
554    printf(".lf %d\n", n);
555  else {
556    printf(".lf %d %s\n", n, s);
557    last_filename = s;
558  }
559}
560