Update to groff 1.19.2.
[dragonfly.git] / contrib / groff-1.19 / src / preproc / pic / troff.cpp
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
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with groff; see the file COPYING.  If not, write to the Free Software
20 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21
22 #include "pic.h"
23 #include "common.h"
24
25
26 const double RELATIVE_THICKNESS = -1.0;
27 const double BAD_THICKNESS = -2.0;
28
29 class 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;
43 public:
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
60 int simple_output::supports_filled_polygons()
61 {
62   return driver_extension_flag != 0;
63 }
64
65 void 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
84 void 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
135 void 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
142 void 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
160 void 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
186 void 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
213 class 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
222 public:
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
246 output *make_troff_output()
247 {
248   return new troff_output;
249 }
250
251 troff_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
257 troff_output::~troff_output()
258 {
259 }
260
261 inline 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
276 void 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
298 void 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
313 void 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
322 void 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
335 void 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
349 void 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
363 void 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
374 void 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
395 void 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
413 const double TEXT_AXIS = 0.22;  // in ems
414
415 static const char *choose_delimiter(const char *text)
416 {
417   if (strchr(text, '\'') == 0)
418     return "'";
419   else
420     return "\\(ts";
421 }
422
423 void 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
470 void 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
480 void 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
495 void 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
514 void 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
530 char *troff_output::get_last_filled()
531 {
532   return last_filled;
533 }
534
535 char *troff_output::get_outline_color()
536 {
537   return last_outlined;
538 }
539
540 const double DOT_AXIS = .044;
541
542 void 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
559 void 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 }