Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / groff / src / preproc / pic / common.cc
1 // -*- C++ -*-
2 /* Copyright (C) 1989, 1990, 1991, 1992 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 2, or (at your option) any later
10 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 along
18 with groff; see the file COPYING.  If not, write to the Free Software
19 Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
20
21 #include "pic.h"
22 #include "common.h"
23
24 // output a dashed circle as a series of arcs
25
26 void common_output::dashed_circle(const position &cent, double rad,
27                                   const line_type &lt)
28 {
29   assert(lt.type == line_type::dashed);
30   line_type slt = lt;
31   slt.type = line_type::solid;
32   double dash_angle = lt.dash_width/rad;
33   int ndashes;
34   double gap_angle;
35   if (dash_angle >= M_PI/4.0) {
36     if (dash_angle < M_PI/2.0) {
37       gap_angle = M_PI/2.0 - dash_angle;
38       ndashes = 4;
39     }
40     else if (dash_angle < M_PI) {
41       gap_angle = M_PI - dash_angle;
42       ndashes = 2;
43     }
44     else {
45       circle(cent, rad, slt, -1.0);
46       return;
47     }
48   }
49   else {
50     ndashes = 4*int(ceil(M_PI/(4.0*dash_angle)));
51     gap_angle = (M_PI*2.0)/ndashes - dash_angle;
52   }
53   for (int i = 0; i < ndashes; i++) {
54     double start_angle = i*(dash_angle+gap_angle) - dash_angle/2.0;
55     solid_arc(cent, rad, start_angle, start_angle + dash_angle, lt);
56   }
57 }
58
59 // output a dotted circle as a series of dots
60
61 void common_output::dotted_circle(const position &cent, double rad,
62                                   const line_type &lt)
63 {
64   assert(lt.type == line_type::dotted);
65   double gap_angle = lt.dash_width/rad;
66   int ndots;
67   if (gap_angle >= M_PI/2.0) {
68     // always have at least 2 dots
69     gap_angle = M_PI;
70     ndots = 2;
71   }
72   else {
73     ndots = 4*int(M_PI/(2.0*gap_angle));
74     gap_angle = (M_PI*2.0)/ndots;
75   }
76   double ang = 0.0;
77   for (int i = 0; i < ndots; i++, ang += gap_angle)
78     dot(cent + position(cos(ang), sin(ang))*rad, lt);
79 }
80
81 // return non-zero iff we can compute a center
82
83 int compute_arc_center(const position &start, const position &cent,
84                        const position &end, position *result)
85 {
86   // This finds the point along the vector from start to cent that
87   // is equidistant between start and end.
88   distance c = cent - start;
89   distance e = end - start;
90   double n = c*e;
91   if (n == 0.0)
92     return 0;
93   *result = start + c*((e*e)/(2.0*n));
94   return 1;
95 }
96
97 // output a dashed arc as a series of arcs
98
99 void common_output::dashed_arc(const position &start, const position &cent,
100                                const position &end, const line_type &lt)
101 {
102   assert(lt.type == line_type::dashed);
103   position c;
104   if (!compute_arc_center(start, cent, end, &c)) {
105     line(start, &end, 1, lt);
106     return;
107   }
108   distance start_offset = start - c;
109   distance end_offset = end - c;
110   double start_angle = atan2(start_offset.y, start_offset.x);
111   double end_angle = atan2(end_offset.y, end_offset.x);
112   double rad = hypot(c - start);
113   double dash_angle = lt.dash_width/rad;
114   double total_angle = end_angle - start_angle;
115   while (total_angle < 0)
116     total_angle += M_PI + M_PI;
117   if (total_angle <= dash_angle*2.0) {
118     solid_arc(cent, rad, start_angle, end_angle, lt);
119     return;
120   }
121   int ndashes = int((total_angle - dash_angle)/(dash_angle*2.0) + .5);
122   double dash_and_gap_angle = (total_angle - dash_angle)/ndashes;
123   for (int i = 0; i <= ndashes; i++)
124     solid_arc(cent, rad, start_angle + i*dash_and_gap_angle,
125               start_angle + i*dash_and_gap_angle + dash_angle, lt);
126 }
127
128 // output a dotted arc as a series of dots
129
130 void common_output::dotted_arc(const position &start, const position &cent,
131                                const position &end, const line_type &lt)
132 {
133   assert(lt.type == line_type::dotted);
134   position c;
135   if (!compute_arc_center(start, cent, end, &c)) {
136     line(start, &end, 1, lt);
137     return;
138   }
139   distance start_offset = start - c;
140   distance end_offset = end - c;
141   double start_angle = atan2(start_offset.y, start_offset.x);
142   double total_angle = atan2(end_offset.y, end_offset.x) - start_angle;
143   while (total_angle < 0)
144     total_angle += M_PI + M_PI;
145   double rad = hypot(c - start);
146   int ndots = int(total_angle/(lt.dash_width/rad) + .5);
147   if (ndots == 0)
148     dot(start, lt);
149   else {
150     for (int i = 0; i <= ndots; i++) {
151       double a = start_angle + (total_angle*i)/ndots;
152       dot(cent + position(cos(a), sin(a))*rad, lt);
153     }
154   }
155 }
156
157 void common_output::solid_arc(const position &cent, double rad,
158                               double start_angle, double end_angle,
159                               const line_type &lt)
160 {
161   line_type slt = lt;
162   slt.type = line_type::solid;
163   arc(cent + position(cos(start_angle), sin(start_angle))*rad,
164       cent,
165       cent + position(cos(end_angle), sin(end_angle))*rad,
166       slt);
167 }
168
169
170 void common_output::rounded_box(const position &cent, const distance &dim,
171                                 double rad, const line_type &lt, double fill)
172 {
173   if (fill >= 0.0)
174     filled_rounded_box(cent, dim, rad, fill);
175   switch (lt.type) {
176   case line_type::invisible:
177     break;
178   case line_type::dashed:
179     dashed_rounded_box(cent, dim, rad, lt);
180     break;
181   case line_type::dotted:
182     dotted_rounded_box(cent, dim, rad, lt);
183     break;
184   case line_type::solid:
185     solid_rounded_box(cent, dim, rad, lt);
186     break;
187   default:
188     assert(0);
189   }
190 }
191
192
193 void common_output::dashed_rounded_box(const position &cent,
194                                        const distance &dim, double rad,
195                                        const line_type &lt)
196 {
197   line_type slt = lt;
198   slt.type = line_type::solid;
199
200   double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad;
201   int n_hor_dashes = int(hor_length/(lt.dash_width*2.0) + .5);
202   double hor_gap_width = (n_hor_dashes != 0
203                           ? hor_length/n_hor_dashes - lt.dash_width
204                           : 0.0);
205
206   double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad;
207   int n_vert_dashes = int(vert_length/(lt.dash_width*2.0) + .5);
208   double vert_gap_width = (n_vert_dashes != 0
209                            ? vert_length/n_vert_dashes - lt.dash_width
210                            : 0.0);
211   // Note that each corner arc has to be split into two for dashing,
212   // because one part is dashed using vert_gap_width, and the other
213   // using hor_gap_width.
214   double offset = lt.dash_width/2.0;
215   dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
216            -M_PI/4.0, 0, slt, lt.dash_width, vert_gap_width, &offset);
217   dash_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad),
218             cent + position(dim.x/2.0, dim.y/2.0 - rad),
219             slt, lt.dash_width, vert_gap_width, &offset);
220   dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
221            0, M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset);
222
223   offset = lt.dash_width/2.0;
224   dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
225            M_PI/4.0, M_PI/2, slt, lt.dash_width, hor_gap_width, &offset);
226   dash_line(cent + position(dim.x/2.0 - rad, dim.y/2.0),
227             cent + position(-dim.x/2.0 + rad, dim.y/2.0),
228             slt, lt.dash_width, hor_gap_width, &offset);
229   dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
230            M_PI/2, 3*M_PI/4.0, slt, lt.dash_width, hor_gap_width, &offset);
231
232   offset = lt.dash_width/2.0;
233   dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
234            3.0*M_PI/4.0, M_PI, slt, lt.dash_width, vert_gap_width, &offset);
235   dash_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad),
236             cent + position(-dim.x/2.0, -dim.y/2.0 + rad),
237             slt, lt.dash_width, vert_gap_width, &offset);
238   dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
239            M_PI, 5.0*M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset);
240
241   offset = lt.dash_width/2.0;
242   dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
243            5*M_PI/4.0, 3*M_PI/2.0, slt, lt.dash_width, hor_gap_width, &offset);
244   dash_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0),
245             cent + position(dim.x/2.0 - rad, -dim.y/2.0),
246             slt, lt.dash_width, hor_gap_width, &offset);
247   dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
248            3*M_PI/2, 7*M_PI/4, slt, lt.dash_width, hor_gap_width, &offset);
249 }
250
251 // Used by dashed_rounded_box.
252
253 void common_output::dash_arc(const position &cent, double rad,
254                              double start_angle, double end_angle,
255                              const line_type &lt,
256                              double dash_width, double gap_width,
257                              double *offsetp)
258 {
259   double length = (end_angle - start_angle)*rad;
260   double pos = 0.0;
261   for (;;) {
262     if (*offsetp >= dash_width) {
263       double rem = dash_width + gap_width - *offsetp;
264       if (pos + rem > length) {
265         *offsetp += length - pos;
266         break;
267       }
268       else {
269         pos += rem;
270         *offsetp = 0.0;
271       }
272     }
273     else {
274       double rem = dash_width  - *offsetp;
275       if (pos + rem > length) {
276         solid_arc(cent, rad, start_angle + pos/rad, end_angle, lt);
277         *offsetp += length - pos;
278         break;
279       }
280       else {
281         solid_arc(cent, rad, start_angle + pos/rad,
282                   start_angle + (pos + rem)/rad, lt);
283         pos += rem;
284         *offsetp = dash_width;
285       }
286     }
287   }
288 }
289
290 // Used by dashed_rounded_box.
291
292 void common_output::dash_line(const position &start, const position &end,
293                               const line_type &lt,
294                               double dash_width, double gap_width,
295                               double *offsetp)
296 {
297   distance dist = end - start;
298   double length = hypot(dist);
299   if (length == 0.0)
300     return;
301   double pos = 0.0;
302   for (;;) {
303     if (*offsetp >= dash_width) {
304       double rem = dash_width + gap_width - *offsetp;
305       if (pos + rem > length) {
306         *offsetp += length - pos;
307         break;
308       }
309       else {
310         pos += rem;
311         *offsetp = 0.0;
312       }
313     }
314     else {
315       double rem = dash_width  - *offsetp;
316       if (pos + rem > length) {
317         line(start + dist*(pos/length), &end, 1, lt);
318         *offsetp += length - pos;
319         break;
320       }
321       else {
322         position p(start + dist*((pos + rem)/length));
323         line(start + dist*(pos/length), &p, 1, lt);
324         pos += rem;
325         *offsetp = dash_width;
326       }
327     }
328   }
329 }
330
331 void common_output::dotted_rounded_box(const position &cent,
332                                        const distance &dim, double rad,
333                                        const line_type &lt)
334 {
335   line_type slt = lt;
336   slt.type = line_type::solid;
337
338   double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad;
339   int n_hor_dots = int(hor_length/lt.dash_width + .5);
340   double hor_gap_width = (n_hor_dots != 0
341                           ? hor_length/n_hor_dots
342                           : lt.dash_width);
343
344   double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad;
345   int n_vert_dots = int(vert_length/lt.dash_width + .5);
346   double vert_gap_width = (n_vert_dots != 0
347                            ? vert_length/n_vert_dots
348                            : lt.dash_width);
349   double epsilon = lt.dash_width/(rad*100.0);
350
351   double offset = 0.0;
352   dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
353            -M_PI/4.0, 0, slt, vert_gap_width, &offset);
354   dot_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad),
355             cent + position(dim.x/2.0, dim.y/2.0 - rad),
356             slt, vert_gap_width, &offset);
357   dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
358            0, M_PI/4.0 - epsilon, slt, vert_gap_width, &offset);
359
360   offset = 0.0;
361   dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
362            M_PI/4.0, M_PI/2, slt, hor_gap_width, &offset);
363   dot_line(cent + position(dim.x/2.0 - rad, dim.y/2.0),
364             cent + position(-dim.x/2.0 + rad, dim.y/2.0),
365             slt, hor_gap_width, &offset);
366   dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
367            M_PI/2, 3*M_PI/4.0 - epsilon, slt, hor_gap_width, &offset);
368
369   offset = 0.0;
370   dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
371            3.0*M_PI/4.0, M_PI, slt, vert_gap_width, &offset);
372   dot_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad),
373             cent + position(-dim.x/2.0, -dim.y/2.0 + rad),
374             slt, vert_gap_width, &offset);
375   dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
376            M_PI, 5.0*M_PI/4.0 - epsilon, slt, vert_gap_width, &offset);
377
378   offset = 0.0;
379   dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
380            5*M_PI/4.0, 3*M_PI/2.0, slt, hor_gap_width, &offset);
381   dot_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0),
382             cent + position(dim.x/2.0 - rad, -dim.y/2.0),
383             slt, hor_gap_width, &offset);
384   dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
385            3*M_PI/2, 7*M_PI/4 - epsilon, slt, hor_gap_width, &offset);
386 }
387
388 // Used by dotted_rounded_box.
389
390 void common_output::dot_arc(const position &cent, double rad,
391                             double start_angle, double end_angle,
392                             const line_type &lt, double gap_width,
393                             double *offsetp)
394 {
395   double length = (end_angle - start_angle)*rad;
396   double pos = 0.0;
397   for (;;) {
398     if (*offsetp == 0.0) {
399       double ang = start_angle + pos/rad;
400       dot(cent + position(cos(ang), sin(ang))*rad, lt);
401     }
402     double rem = gap_width - *offsetp;
403     if (pos + rem > length) {
404       *offsetp += length - pos;
405       break;
406     }
407     else {
408       pos += rem;
409       *offsetp = 0.0;
410     }
411   }
412 }
413
414 // Used by dotted_rounded_box.
415
416 void common_output::dot_line(const position &start, const position &end,
417                              const line_type &lt, double gap_width,
418                              double *offsetp)
419 {
420   distance dist = end - start;
421   double length = hypot(dist);
422   if (length == 0.0)
423     return;
424   double pos = 0.0;
425   for (;;) {
426     if (*offsetp == 0.0)
427       dot(start + dist*(pos/length), lt);
428     double rem = gap_width - *offsetp;
429     if (pos + rem > length) {
430       *offsetp += length - pos;
431       break;
432     }
433     else {
434       pos += rem;
435       *offsetp = 0.0;
436     }
437   }
438 }
439
440 void common_output::solid_rounded_box(const position &cent,
441                                       const distance &dim, double rad,
442                                       const line_type &lt)
443 {
444   position tem = cent - dim/2.0;
445   arc(tem + position(0.0, rad),
446       tem + position(rad, rad),
447       tem + position(rad, 0.0),
448       lt);
449   tem = cent + position(-dim.x/2.0, dim.y/2.0);
450   arc(tem + position(rad, 0.0),
451       tem + position(rad, -rad),
452       tem + position(0.0, -rad),
453       lt);
454   tem = cent + dim/2.0;
455   arc(tem + position(0.0, -rad),
456       tem + position(-rad, -rad),
457       tem + position(-rad, 0.0),
458       lt);
459   tem = cent + position(dim.x/2.0, -dim.y/2.0);
460   arc(tem + position(-rad, 0.0),
461       tem + position(-rad, rad),
462       tem + position(0.0, rad),
463       lt);
464   position end;
465   end = cent + position(-dim.x/2.0, dim.y/2.0 - rad);
466   line(cent - dim/2.0 + position(0.0, rad), &end, 1, lt);
467   end = cent + position(dim.x/2.0 - rad, dim.y/2.0);
468   line(cent + position(-dim.x/2.0 + rad, dim.y/2.0), &end, 1, lt);
469   end = cent + position(dim.x/2.0, -dim.y/2.0 + rad);
470   line(cent + position(dim.x/2.0, dim.y/2.0 - rad), &end, 1, lt);
471   end = cent + position(-dim.x/2.0 + rad, -dim.y/2.0);
472   line(cent + position(dim.x/2.0 - rad, -dim.y/2.0), &end, 1, lt);
473 }
474
475 void common_output::filled_rounded_box(const position &cent,
476                                        const distance &dim, double rad,
477                                        double fill)
478 {
479   line_type ilt;
480   ilt.type = line_type::invisible;
481   circle(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, ilt, fill);
482   circle(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, ilt, fill);
483   circle(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, ilt, fill);
484   circle(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, ilt, fill);
485   position vec[4];
486   vec[0] = cent + position(dim.x/2.0, dim.y/2.0 - rad);
487   vec[1] = cent + position(-dim.x/2.0, dim.y/2.0 - rad);
488   vec[2] = cent + position(-dim.x/2.0, -dim.y/2.0 + rad);
489   vec[3] = cent + position(dim.x/2.0, -dim.y/2.0 + rad);
490   polygon(vec, 4, ilt, fill);
491   vec[0] = cent + position(dim.x/2.0 - rad, dim.y/2.0);
492   vec[1] = cent + position(-dim.x/2.0 + rad, dim.y/2.0);
493   vec[2] = cent + position(-dim.x/2.0 + rad, -dim.y/2.0);
494   vec[3] = cent + position(dim.x/2.0 - rad, -dim.y/2.0);
495   polygon(vec, 4, ilt, fill);
496 }