common.cpp revision 114402
1// -*- C++ -*-
2/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
3     Written by James Clark (jjc@jclark.com)
4
5This file is part of groff.
6
7groff is free software; you can redistribute it and/or modify it under
8the terms of the GNU General Public License as published by the Free
9Software Foundation; either version 2, or (at your option) any later
10version.
11
12groff is distributed in the hope that it will be useful, but WITHOUT ANY
13WARRANTY; without even the implied warranty of MERCHANTABILITY or
14FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15for more details.
16
17You should have received a copy of the GNU General Public License along
18with groff; see the file COPYING.  If not, write to the Free Software
19Foundation, 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
26void 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
61void 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
83int 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
99void 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
130void 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
157void 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
170void 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
193void 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
253void 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
292void 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
331void 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
390void 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
416void 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
440void 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
475void 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}
497