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