2 /* Copyright (C) 1989-2018 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
5 This file is part of groff.
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.
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
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/>. */
23 // output a dashed circle as a series of arcs
25 void common_output::dashed_circle(const position ¢, double rad,
28 assert(lt.type == line_type::dashed);
30 slt.type = line_type::solid;
31 double dash_angle = lt.dash_width/rad;
34 if (dash_angle >= M_PI/4.0) {
35 if (dash_angle < M_PI/2.0) {
36 gap_angle = M_PI/2.0 - dash_angle;
39 else if (dash_angle < M_PI) {
40 gap_angle = M_PI - dash_angle;
44 circle(cent, rad, slt, -1.0);
49 ndashes = 4*int(ceil(M_PI/(4.0*dash_angle)));
50 gap_angle = (M_PI*2.0)/ndashes - dash_angle;
52 for (int i = 0; i < ndashes; i++) {
53 double start_angle = i*(dash_angle+gap_angle) - dash_angle/2.0;
54 solid_arc(cent, rad, start_angle, start_angle + dash_angle, lt);
58 // output a dotted circle as a series of dots
60 void common_output::dotted_circle(const position ¢, double rad,
63 assert(lt.type == line_type::dotted);
64 double gap_angle = lt.dash_width/rad;
66 if (gap_angle >= M_PI/2.0) {
67 // always have at least 2 dots
72 ndots = 4*int(M_PI/(2.0*gap_angle));
73 gap_angle = (M_PI*2.0)/ndots;
76 for (int i = 0; i < ndots; i++, ang += gap_angle)
77 dot(cent + position(cos(ang), sin(ang))*rad, lt);
80 // recursive function for dash drawing, used by dashed_ellipse
82 void common_output::ellipse_arc(const position ¢,
83 const position &z0, const position &z1,
84 const distance &dim, const line_type <)
86 assert(lt.type == line_type::solid);
87 assert(dim.x != 0 && dim.y != 0);
89 position zml = (z0 + z1) / 2;
90 // apply affine transformation (from ellipse to circle) to compute angle
91 // of new position, then invert transformation to get exact position
92 double psi = atan2(zml.y / dim.y, zml.x / dim.x);
93 position zm = position(dim.x * cos(psi), dim.y * sin(psi));
94 // to approximate the ellipse arc with one or more circle arcs, we
95 // first compute the radius of curvature in zm
96 double a_2 = dim.x * dim.x;
97 double a_4 = a_2 * a_2;
98 double b_2 = dim.y * dim.y;
99 double b_4 = b_2 * b_2;
100 double e_2 = a_2 - b_2;
101 double temp = a_4 * zm.y * zm.y + b_4 * zm.x * zm.x;
102 double rho = sqrt(temp / a_4 / b_4 * temp / a_4 / b_4 * temp);
103 // compute center of curvature circle
104 position M = position(e_2 * zm.x / a_2 * zm.x / a_2 * zm.x,
105 -e_2 * zm.y / b_2 * zm.y / b_2 * zm.y);
106 // compute distance between circle and ellipse arc at start and end
107 double phi0 = atan2(z0.y - M.y, z0.x - M.x);
108 double phi1 = atan2(z1.y - M.y, z1.x - M.x);
109 position M0 = position(rho * cos(phi0), rho * sin(phi0)) + M;
110 position M1 = position(rho * cos(phi1), rho * sin(phi1)) + M;
111 double dist0 = hypot(z0 - M0) / sqrt(z0 * z0);
112 double dist1 = hypot(z1 - M1) / sqrt(z1 * z1);
113 if (dist0 < eps && dist1 < eps)
114 solid_arc(M + cent, rho, phi0, phi1, lt);
116 ellipse_arc(cent, z0, zm, dim, lt);
117 ellipse_arc(cent, zm, z1, dim, lt);
121 // output a dashed ellipse as a series of arcs
123 void common_output::dashed_ellipse(const position ¢, const distance &dim,
126 assert(lt.type == line_type::dashed);
127 double dim_x = dim.x / 2;
128 double dim_y = dim.y / 2;
130 slt.type = line_type::solid;
131 double dw = lt.dash_width;
132 // we use an approximation to compute the ellipse length (found in:
133 // Bronstein, Semendjajew, Taschenbuch der Mathematik)
134 double lambda = (dim.x - dim.y) / (dim.x + dim.y);
135 double le = M_PI / 2 * (dim.x + dim.y)
136 * ((64 - 3 * lambda * lambda * lambda * lambda )
137 / (64 - 16 * lambda * lambda));
138 // for symmetry we make nmax a multiple of 8
139 int nmax = 8 * int(le / dw / 8 + 0.5);
144 int ndash = nmax / 2;
145 double gapwidth = (le - dw * ndash) / ndash;
147 position z = position(dim_x, 0);
150 int jmax = int(10 / lt.dash_width);
151 for (int i = 0; i <= nmax; i++) {
153 position zpre = zdot;
154 double ld = (int(i / 2) + 0.5) * dw + int((i + 1) / 2) * gapwidth;
157 // find next position for fixed arc length
162 double phi = j * 2 * M_PI / jmax;
163 z = position(dim_x * cos(phi), dim_y * sin(phi));
164 dl = hypot(z - zold);
167 // interpolate linearly between the last two points,
168 // using the length difference as the scaling factor
169 double delta = (ld - lold) / dl;
170 zdot = zold + (z - zold) * delta;
171 // compute angle of new position on the affine circle
172 // and use it to get the exact value on the ellipse
173 double psi = atan2(zdot.y / dim_y, zdot.x / dim_x);
174 zdot = position(dim_x * cos(psi), dim_y * sin(psi));
175 if ((i % 2 == 0) && (i > 1))
176 ellipse_arc(cent, zpre, zdot, dim / 2, slt);
180 // output a dotted ellipse as a series of dots
182 void common_output::dotted_ellipse(const position ¢, const distance &dim,
185 assert(lt.type == line_type::dotted);
186 double dim_x = dim.x / 2;
187 double dim_y = dim.y / 2;
189 slt.type = line_type::solid;
190 // we use an approximation to compute the ellipse length (found in:
191 // Bronstein, Semendjajew, Taschenbuch der Mathematik)
192 double lambda = (dim.x - dim.y) / (dim.x + dim.y);
193 double le = M_PI / 2 * (dim.x + dim.y)
194 * ((64 - 3 * lambda * lambda * lambda * lambda )
195 / (64 - 16 * lambda * lambda));
196 // for symmetry we make nmax a multiple of 4
197 int ndots = 4 * int(le / lt.dash_width / 4 + 0.5);
201 position z = position(dim_x, 0);
203 int jmax = int(10 / lt.dash_width);
204 for (int i = 1; i <= ndots; i++) {
207 double ld = i * le / ndots;
209 // find next position for fixed arc length
214 double phi = j * 2 * M_PI / jmax;
215 z = position(dim_x * cos(phi), dim_y * sin(phi));
216 dl = hypot(z - zold);
219 // interpolate linearly between the last two points,
220 // using the length difference as the scaling factor
221 double delta = (ld - lold) / dl;
222 position zdot = zold + (z - zold) * delta;
223 // compute angle of new position on the affine circle
224 // and use it to get the exact value on the ellipse
225 double psi = atan2(zdot.y / dim_y, zdot.x / dim_x);
226 zdot = position(dim_x * cos(psi), dim_y * sin(psi));
227 dot(cent + zdot, slt);
231 // return non-zero iff we can compute a center
233 int compute_arc_center(const position &start, const position ¢,
234 const position &end, position *result)
236 // This finds the point along the vector from start to cent that
237 // is equidistant between start and end.
238 distance c = cent - start;
239 distance e = end - start;
243 *result = start + c*((e*e)/(2.0*n));
247 // output a dashed arc as a series of arcs
249 void common_output::dashed_arc(const position &start, const position ¢,
250 const position &end, const line_type <)
252 assert(lt.type == line_type::dashed);
254 if (!compute_arc_center(start, cent, end, &c)) {
255 line(start, &end, 1, lt);
258 distance start_offset = start - c;
259 distance end_offset = end - c;
260 double start_angle = atan2(start_offset.y, start_offset.x);
261 double end_angle = atan2(end_offset.y, end_offset.x);
262 double rad = hypot(c - start);
263 double dash_angle = lt.dash_width/rad;
264 double total_angle = end_angle - start_angle;
265 while (total_angle < 0)
266 total_angle += M_PI + M_PI;
267 if (total_angle <= dash_angle*2.0) {
268 solid_arc(cent, rad, start_angle, end_angle, lt);
271 int ndashes = int((total_angle - dash_angle)/(dash_angle*2.0) + .5);
272 double dash_and_gap_angle = (total_angle - dash_angle)/ndashes;
273 for (int i = 0; i <= ndashes; i++)
274 solid_arc(cent, rad, start_angle + i*dash_and_gap_angle,
275 start_angle + i*dash_and_gap_angle + dash_angle, lt);
278 // output a dotted arc as a series of dots
280 void common_output::dotted_arc(const position &start, const position ¢,
281 const position &end, const line_type <)
283 assert(lt.type == line_type::dotted);
285 if (!compute_arc_center(start, cent, end, &c)) {
286 line(start, &end, 1, lt);
289 distance start_offset = start - c;
290 distance end_offset = end - c;
291 double start_angle = atan2(start_offset.y, start_offset.x);
292 double total_angle = atan2(end_offset.y, end_offset.x) - start_angle;
293 while (total_angle < 0)
294 total_angle += M_PI + M_PI;
295 double rad = hypot(c - start);
296 int ndots = int(total_angle/(lt.dash_width/rad) + .5);
300 for (int i = 0; i <= ndots; i++) {
301 double a = start_angle + (total_angle*i)/ndots;
302 dot(cent + position(cos(a), sin(a))*rad, lt);
307 void common_output::solid_arc(const position ¢, double rad,
308 double start_angle, double end_angle,
312 slt.type = line_type::solid;
313 arc(cent + position(cos(start_angle), sin(start_angle))*rad,
315 cent + position(cos(end_angle), sin(end_angle))*rad,
320 void common_output::rounded_box(const position ¢, const distance &dim,
321 double rad, const line_type <,
322 double fill, char *color_fill)
324 if (fill >= 0.0 || color_fill)
325 filled_rounded_box(cent, dim, rad, fill);
327 case line_type::invisible:
329 case line_type::dashed:
330 dashed_rounded_box(cent, dim, rad, lt);
332 case line_type::dotted:
333 dotted_rounded_box(cent, dim, rad, lt);
335 case line_type::solid:
336 solid_rounded_box(cent, dim, rad, lt);
344 void common_output::dashed_rounded_box(const position ¢,
345 const distance &dim, double rad,
349 slt.type = line_type::solid;
351 double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad;
352 int n_hor_dashes = int(hor_length/(lt.dash_width*2.0) + .5);
353 double hor_gap_width = (n_hor_dashes != 0
354 ? hor_length/n_hor_dashes - lt.dash_width
357 double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad;
358 int n_vert_dashes = int(vert_length/(lt.dash_width*2.0) + .5);
359 double vert_gap_width = (n_vert_dashes != 0
360 ? vert_length/n_vert_dashes - lt.dash_width
362 // Note that each corner arc has to be split into two for dashing,
363 // because one part is dashed using vert_gap_width, and the other
364 // using hor_gap_width.
365 double offset = lt.dash_width/2.0;
366 dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
367 -M_PI/4.0, 0, slt, lt.dash_width, vert_gap_width, &offset);
368 dash_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad),
369 cent + position(dim.x/2.0, dim.y/2.0 - rad),
370 slt, lt.dash_width, vert_gap_width, &offset);
371 dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
372 0, M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset);
374 offset = lt.dash_width/2.0;
375 dash_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
376 M_PI/4.0, M_PI/2, slt, lt.dash_width, hor_gap_width, &offset);
377 dash_line(cent + position(dim.x/2.0 - rad, dim.y/2.0),
378 cent + position(-dim.x/2.0 + rad, dim.y/2.0),
379 slt, lt.dash_width, hor_gap_width, &offset);
380 dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
381 M_PI/2, 3*M_PI/4.0, slt, lt.dash_width, hor_gap_width, &offset);
383 offset = lt.dash_width/2.0;
384 dash_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
385 3.0*M_PI/4.0, M_PI, slt, lt.dash_width, vert_gap_width, &offset);
386 dash_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad),
387 cent + position(-dim.x/2.0, -dim.y/2.0 + rad),
388 slt, lt.dash_width, vert_gap_width, &offset);
389 dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
390 M_PI, 5.0*M_PI/4.0, slt, lt.dash_width, vert_gap_width, &offset);
392 offset = lt.dash_width/2.0;
393 dash_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
394 5*M_PI/4.0, 3*M_PI/2.0, slt, lt.dash_width, hor_gap_width, &offset);
395 dash_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0),
396 cent + position(dim.x/2.0 - rad, -dim.y/2.0),
397 slt, lt.dash_width, hor_gap_width, &offset);
398 dash_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
399 3*M_PI/2, 7*M_PI/4, slt, lt.dash_width, hor_gap_width, &offset);
402 // Used by dashed_rounded_box.
404 void common_output::dash_arc(const position ¢, double rad,
405 double start_angle, double end_angle,
407 double dash_width, double gap_width,
410 double length = (end_angle - start_angle)*rad;
413 if (*offsetp >= dash_width) {
414 double rem = dash_width + gap_width - *offsetp;
415 if (pos + rem > length) {
416 *offsetp += length - pos;
425 double rem = dash_width - *offsetp;
426 if (pos + rem > length) {
427 solid_arc(cent, rad, start_angle + pos/rad, end_angle, lt);
428 *offsetp += length - pos;
432 solid_arc(cent, rad, start_angle + pos/rad,
433 start_angle + (pos + rem)/rad, lt);
435 *offsetp = dash_width;
441 // Used by dashed_rounded_box.
443 void common_output::dash_line(const position &start, const position &end,
445 double dash_width, double gap_width,
448 distance dist = end - start;
449 double length = hypot(dist);
454 if (*offsetp >= dash_width) {
455 double rem = dash_width + gap_width - *offsetp;
456 if (pos + rem > length) {
457 *offsetp += length - pos;
466 double rem = dash_width - *offsetp;
467 if (pos + rem > length) {
468 line(start + dist*(pos/length), &end, 1, lt);
469 *offsetp += length - pos;
473 position p(start + dist*((pos + rem)/length));
474 line(start + dist*(pos/length), &p, 1, lt);
476 *offsetp = dash_width;
482 void common_output::dotted_rounded_box(const position ¢,
483 const distance &dim, double rad,
487 slt.type = line_type::solid;
489 double hor_length = dim.x + (M_PI/2.0 - 2.0)*rad;
490 int n_hor_dots = int(hor_length/lt.dash_width + .5);
491 double hor_gap_width = (n_hor_dots != 0
492 ? hor_length/n_hor_dots
495 double vert_length = dim.y + (M_PI/2.0 - 2.0)*rad;
496 int n_vert_dots = int(vert_length/lt.dash_width + .5);
497 double vert_gap_width = (n_vert_dots != 0
498 ? vert_length/n_vert_dots
500 double epsilon = lt.dash_width/(rad*100.0);
503 dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
504 -M_PI/4.0, 0, slt, vert_gap_width, &offset);
505 dot_line(cent + position(dim.x/2.0, -dim.y/2.0 + rad),
506 cent + position(dim.x/2.0, dim.y/2.0 - rad),
507 slt, vert_gap_width, &offset);
508 dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
509 0, M_PI/4.0 - epsilon, slt, vert_gap_width, &offset);
512 dot_arc(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad,
513 M_PI/4.0, M_PI/2, slt, hor_gap_width, &offset);
514 dot_line(cent + position(dim.x/2.0 - rad, dim.y/2.0),
515 cent + position(-dim.x/2.0 + rad, dim.y/2.0),
516 slt, hor_gap_width, &offset);
517 dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
518 M_PI/2, 3*M_PI/4.0 - epsilon, slt, hor_gap_width, &offset);
521 dot_arc(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad,
522 3.0*M_PI/4.0, M_PI, slt, vert_gap_width, &offset);
523 dot_line(cent + position(-dim.x/2.0, dim.y/2.0 - rad),
524 cent + position(-dim.x/2.0, -dim.y/2.0 + rad),
525 slt, vert_gap_width, &offset);
526 dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
527 M_PI, 5.0*M_PI/4.0 - epsilon, slt, vert_gap_width, &offset);
530 dot_arc(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad,
531 5*M_PI/4.0, 3*M_PI/2.0, slt, hor_gap_width, &offset);
532 dot_line(cent + position(-dim.x/2.0 + rad, -dim.y/2.0),
533 cent + position(dim.x/2.0 - rad, -dim.y/2.0),
534 slt, hor_gap_width, &offset);
535 dot_arc(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad,
536 3*M_PI/2, 7*M_PI/4 - epsilon, slt, hor_gap_width, &offset);
539 // Used by dotted_rounded_box.
541 void common_output::dot_arc(const position ¢, double rad,
542 double start_angle, double end_angle,
543 const line_type <, double gap_width,
546 double length = (end_angle - start_angle)*rad;
549 if (*offsetp == 0.0) {
550 double ang = start_angle + pos/rad;
551 dot(cent + position(cos(ang), sin(ang))*rad, lt);
553 double rem = gap_width - *offsetp;
554 if (pos + rem > length) {
555 *offsetp += length - pos;
565 // Used by dotted_rounded_box.
567 void common_output::dot_line(const position &start, const position &end,
568 const line_type <, double gap_width,
571 distance dist = end - start;
572 double length = hypot(dist);
578 dot(start + dist*(pos/length), lt);
579 double rem = gap_width - *offsetp;
580 if (pos + rem > length) {
581 *offsetp += length - pos;
591 void common_output::solid_rounded_box(const position ¢,
592 const distance &dim, double rad,
595 position tem = cent - dim/2.0;
596 arc(tem + position(0.0, rad),
597 tem + position(rad, rad),
598 tem + position(rad, 0.0),
600 tem = cent + position(-dim.x/2.0, dim.y/2.0);
601 arc(tem + position(rad, 0.0),
602 tem + position(rad, -rad),
603 tem + position(0.0, -rad),
605 tem = cent + dim/2.0;
606 arc(tem + position(0.0, -rad),
607 tem + position(-rad, -rad),
608 tem + position(-rad, 0.0),
610 tem = cent + position(dim.x/2.0, -dim.y/2.0);
611 arc(tem + position(-rad, 0.0),
612 tem + position(-rad, rad),
613 tem + position(0.0, rad),
616 end = cent + position(-dim.x/2.0, dim.y/2.0 - rad);
617 line(cent - dim/2.0 + position(0.0, rad), &end, 1, lt);
618 end = cent + position(dim.x/2.0 - rad, dim.y/2.0);
619 line(cent + position(-dim.x/2.0 + rad, dim.y/2.0), &end, 1, lt);
620 end = cent + position(dim.x/2.0, -dim.y/2.0 + rad);
621 line(cent + position(dim.x/2.0, dim.y/2.0 - rad), &end, 1, lt);
622 end = cent + position(-dim.x/2.0 + rad, -dim.y/2.0);
623 line(cent + position(dim.x/2.0 - rad, -dim.y/2.0), &end, 1, lt);
626 void common_output::filled_rounded_box(const position ¢,
627 const distance &dim, double rad,
631 ilt.type = line_type::invisible;
632 circle(cent + position(dim.x/2.0 - rad, dim.y/2.0 - rad), rad, ilt, fill);
633 circle(cent + position(-dim.x/2.0 + rad, dim.y/2.0 - rad), rad, ilt, fill);
634 circle(cent + position(-dim.x/2.0 + rad, -dim.y/2.0 + rad), rad, ilt, fill);
635 circle(cent + position(dim.x/2.0 - rad, -dim.y/2.0 + rad), rad, ilt, fill);
637 vec[0] = cent + position(dim.x/2.0, dim.y/2.0 - rad);
638 vec[1] = cent + position(-dim.x/2.0, dim.y/2.0 - rad);
639 vec[2] = cent + position(-dim.x/2.0, -dim.y/2.0 + rad);
640 vec[3] = cent + position(dim.x/2.0, -dim.y/2.0 + rad);
641 polygon(vec, 4, ilt, fill);
642 vec[0] = cent + position(dim.x/2.0 - rad, dim.y/2.0);
643 vec[1] = cent + position(-dim.x/2.0 + rad, dim.y/2.0);
644 vec[2] = cent + position(-dim.x/2.0 + rad, -dim.y/2.0);
645 vec[3] = cent + position(dim.x/2.0 - rad, -dim.y/2.0);
646 polygon(vec, 4, ilt, fill);