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