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