1 /* $NetBSD: lj4.cpp,v 1.1.1.1 2016/01/13 18:41:49 christos Exp $ */ 2 3 // -*- C++ -*- 4 /* Copyright (C) 1994, 2000, 2001, 2002, 2003, 2004 5 Free Software Foundation, Inc. 6 Written by James Clark (jjc@jclark.com) 7 8 This file is part of groff. 9 10 groff is free software; you can redistribute it and/or modify it under 11 the terms of the GNU General Public License as published by the Free 12 Software Foundation; either version 2, or (at your option) any later 13 version. 14 15 groff is distributed in the hope that it will be useful, but WITHOUT ANY 16 WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 18 for more details. 19 20 You should have received a copy of the GNU General Public License along 21 with groff; see the file COPYING. If not, write to the Free Software 22 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */ 23 24 /* 25 TODO 26 27 option to use beziers for circle/ellipse/arc 28 option to use lines for spline (for LJ3) 29 left/top offset registration 30 output bin selection option 31 paper source option 32 output non-integer parameters using fixed point numbers 33 X command to insert contents of file 34 X command to specify inline escape sequence (how to specify unprintable chars?) 35 X command to include bitmap graphics 36 */ 37 38 #include "driver.h" 39 #include "nonposix.h" 40 41 extern "C" const char *Version_string; 42 43 static struct { 44 const char *name; 45 int code; 46 // at 300dpi 47 int x_offset_portrait; 48 int x_offset_landscape; 49 } paper_table[] = { 50 { "letter", 2, 75, 60 }, 51 { "legal", 3, 75, 60 }, 52 { "executive", 1, 75, 60 }, 53 { "a4", 26, 71, 59 }, 54 { "com10", 81, 75, 60 }, 55 { "monarch", 80, 75, 60 }, 56 { "c5", 91, 71, 59 }, 57 { "b5", 100, 71, 59 }, 58 { "dl", 90, 71, 59 }, 59 }; 60 61 static int user_paper_size = -1; 62 static int landscape_flag = 0; 63 static int duplex_flag = 0; 64 65 // An upper limit on the paper size in centipoints, 66 // used for setting HPGL picture frame. 67 #define MAX_PAPER_WIDTH (12*720) 68 #define MAX_PAPER_HEIGHT (17*720) 69 70 // Dotted lines that are thinner than this don't work right. 71 #define MIN_DOT_PEN_WIDTH .351 72 73 #ifndef DEFAULT_LINE_WIDTH_FACTOR 74 // in ems/1000 75 #define DEFAULT_LINE_WIDTH_FACTOR 40 76 #endif 77 78 const int DEFAULT_HPGL_UNITS = 1016; 79 int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR; 80 unsigned ncopies = 0; // 0 means don't send ncopies command 81 82 static int lookup_paper_size(const char *); 83 84 class lj4_font : public font { 85 public: 86 ~lj4_font(); 87 void handle_unknown_font_command(const char *command, const char *arg, 88 const char *filename, int lineno); 89 static lj4_font *load_lj4_font(const char *); 90 int weight; 91 int style; 92 int proportional; 93 int typeface; 94 private: 95 lj4_font(const char *); 96 }; 97 98 lj4_font::lj4_font(const char *nm) 99 : font(nm), weight(0), style(0), proportional(0), typeface(0) 100 { 101 } 102 103 lj4_font::~lj4_font() 104 { 105 } 106 107 lj4_font *lj4_font::load_lj4_font(const char *s) 108 { 109 lj4_font *f = new lj4_font(s); 110 if (!f->load()) { 111 delete f; 112 return 0; 113 } 114 return f; 115 } 116 117 static struct { 118 const char *s; 119 int lj4_font::*ptr; 120 int min; 121 int max; 122 } command_table[] = { 123 { "pclweight", &lj4_font::weight, -7, 7 }, 124 { "pclstyle", &lj4_font::style, 0, 32767 }, 125 { "pclproportional", &lj4_font::proportional, 0, 1 }, 126 { "pcltypeface", &lj4_font::typeface, 0, 65535 }, 127 }; 128 129 void lj4_font::handle_unknown_font_command(const char *command, 130 const char *arg, 131 const char *filename, int lineno) 132 { 133 for (unsigned int i = 0; 134 i < sizeof(command_table)/sizeof(command_table[0]); i++) { 135 if (strcmp(command, command_table[i].s) == 0) { 136 if (arg == 0) 137 fatal_with_file_and_line(filename, lineno, 138 "`%1' command requires an argument", 139 command); 140 char *ptr; 141 long n = strtol(arg, &ptr, 10); 142 if (n == 0 && ptr == arg) 143 fatal_with_file_and_line(filename, lineno, 144 "`%1' command requires numeric argument", 145 command); 146 if (n < command_table[i].min) { 147 error_with_file_and_line(filename, lineno, 148 "argument for `%1' command must not be less than %2", 149 command, command_table[i].min); 150 n = command_table[i].min; 151 } 152 else if (n > command_table[i].max) { 153 error_with_file_and_line(filename, lineno, 154 "argument for `%1' command must not be greater than %2", 155 command, command_table[i].max); 156 n = command_table[i].max; 157 } 158 this->*command_table[i].ptr = int(n); 159 break; 160 } 161 } 162 } 163 164 class lj4_printer : public printer { 165 public: 166 lj4_printer(int); 167 ~lj4_printer(); 168 void set_char(int, font *, const environment *, int, const char *name); 169 void draw(int code, int *p, int np, const environment *env); 170 void begin_page(int); 171 void end_page(int page_length); 172 font *make_font(const char *); 173 void end_of_line(); 174 private: 175 void set_line_thickness(int size, int dot = 0); 176 void hpgl_init(); 177 void hpgl_start(); 178 void hpgl_end(); 179 int moveto(int hpos, int vpos); 180 int moveto1(int hpos, int vpos); 181 182 int cur_hpos; 183 int cur_vpos; 184 lj4_font *cur_font; 185 int cur_size; 186 unsigned short cur_symbol_set; 187 int x_offset; 188 int line_thickness; 189 double pen_width; 190 double hpgl_scale; 191 int hpgl_inited; 192 int paper_size; 193 }; 194 195 inline 196 int lj4_printer::moveto(int hpos, int vpos) 197 { 198 if (cur_hpos != hpos || cur_vpos != vpos || cur_hpos < 0) 199 return moveto1(hpos, vpos); 200 else 201 return 1; 202 } 203 204 inline 205 void lj4_printer::hpgl_start() 206 { 207 fputs("\033%1B", stdout); 208 } 209 210 inline 211 void lj4_printer::hpgl_end() 212 { 213 fputs(";\033%0A", stdout); 214 } 215 216 lj4_printer::lj4_printer(int ps) 217 : cur_hpos(-1), 218 cur_font(0), 219 cur_size(0), 220 cur_symbol_set(0), 221 line_thickness(-1), 222 pen_width(-1.0), 223 hpgl_inited(0) 224 { 225 if (7200 % font::res != 0) 226 fatal("invalid resolution %1: resolution must be a factor of 7200", 227 font::res); 228 fputs("\033E", stdout); // reset 229 if (font::res != 300) 230 printf("\033&u%dD", font::res); // unit of measure 231 if (ncopies > 0) 232 printf("\033&l%uX", ncopies); 233 paper_size = 0; // default to letter 234 if (font::papersize) { 235 int n = lookup_paper_size(font::papersize); 236 if (n < 0) 237 error("unknown paper size `%1'", font::papersize); 238 else 239 paper_size = n; 240 } 241 if (ps >= 0) 242 paper_size = ps; 243 printf("\033&l%dA" // paper size 244 "\033&l%dO" // orientation 245 "\033&l0E", // no top margin 246 paper_table[paper_size].code, 247 landscape_flag != 0); 248 if (landscape_flag) 249 x_offset = paper_table[paper_size].x_offset_landscape; 250 else 251 x_offset = paper_table[paper_size].x_offset_portrait; 252 x_offset = (x_offset * font::res) / 300; 253 if (duplex_flag) 254 printf("\033&l%dS", duplex_flag); 255 } 256 257 lj4_printer::~lj4_printer() 258 { 259 fputs("\033E", stdout); 260 } 261 262 void lj4_printer::begin_page(int) 263 { 264 } 265 266 void lj4_printer::end_page(int) 267 { 268 putchar('\f'); 269 cur_hpos = -1; 270 } 271 272 void lj4_printer::end_of_line() 273 { 274 cur_hpos = -1; // force absolute motion 275 } 276 277 inline 278 int is_unprintable(unsigned char c) 279 { 280 return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27); 281 } 282 283 void lj4_printer::set_char(int idx, font *f, const environment *env, 284 int w, const char *) 285 { 286 int code = f->get_code(idx); 287 288 unsigned char ch = code & 0xff; 289 unsigned short symbol_set = code >> 8; 290 if (symbol_set != cur_symbol_set) { 291 printf("\033(%d%c", symbol_set/32, (symbol_set & 31) + 64); 292 cur_symbol_set = symbol_set; 293 } 294 if (f != cur_font) { 295 lj4_font *psf = (lj4_font *)f; 296 // FIXME only output those that are needed 297 printf("\033(s%dp%ds%db%dT", 298 psf->proportional, 299 psf->style, 300 psf->weight, 301 psf->typeface); 302 if (!psf->proportional || !cur_font || !cur_font->proportional) 303 cur_size = 0; 304 cur_font = psf; 305 } 306 if (env->size != cur_size) { 307 if (cur_font->proportional) { 308 static const char *quarters[] = { "", ".25", ".5", ".75" }; 309 printf("\033(s%d%sV", env->size/4, quarters[env->size & 3]); 310 } 311 else { 312 double pitch = double(font::res)/w; 313 // PCL uses the next largest pitch, so round it down. 314 pitch = floor(pitch*100.0)/100.0; 315 printf("\033(s%.2fH", pitch); 316 } 317 cur_size = env->size; 318 } 319 if (!moveto(env->hpos, env->vpos)) 320 return; 321 if (is_unprintable(ch)) 322 fputs("\033&p1X", stdout); 323 putchar(ch); 324 cur_hpos += w; 325 } 326 327 int lj4_printer::moveto1(int hpos, int vpos) 328 { 329 if (hpos < x_offset || vpos < 0) 330 return 0; 331 fputs("\033*p", stdout); 332 if (cur_hpos < 0) 333 printf("%dx%dY", hpos - x_offset, vpos); 334 else { 335 if (cur_hpos != hpos) 336 printf("%s%d%c", hpos > cur_hpos ? "+" : "", 337 hpos - cur_hpos, vpos == cur_vpos ? 'X' : 'x'); 338 if (cur_vpos != vpos) 339 printf("%s%dY", vpos > cur_vpos ? "+" : "", vpos - cur_vpos); 340 } 341 cur_hpos = hpos; 342 cur_vpos = vpos; 343 return 1; 344 } 345 346 void lj4_printer::draw(int code, int *p, int np, const environment *env) 347 { 348 switch (code) { 349 case 'R': 350 { 351 if (np != 2) { 352 error("2 arguments required for rule"); 353 break; 354 } 355 int hpos = env->hpos; 356 int vpos = env->vpos; 357 int hsize = p[0]; 358 int vsize = p[1]; 359 if (hsize < 0) { 360 hpos += hsize; 361 hsize = -hsize; 362 } 363 if (vsize < 0) { 364 vpos += vsize; 365 vsize = -vsize; 366 } 367 if (!moveto(hpos, vpos)) 368 return; 369 printf("\033*c%da%db0P", hsize, vsize); 370 break; 371 } 372 case 'l': 373 if (np != 2) { 374 error("2 arguments required for line"); 375 break; 376 } 377 hpgl_init(); 378 if (!moveto(env->hpos, env->vpos)) 379 return; 380 hpgl_start(); 381 set_line_thickness(env->size, p[0] == 0 && p[1] == 0); 382 printf("PD%d,%d", p[0], p[1]); 383 hpgl_end(); 384 break; 385 case 'p': 386 case 'P': 387 { 388 if (np & 1) { 389 error("even number of arguments required for polygon"); 390 break; 391 } 392 if (np == 0) { 393 error("no arguments for polygon"); 394 break; 395 } 396 hpgl_init(); 397 if (!moveto(env->hpos, env->vpos)) 398 return; 399 hpgl_start(); 400 if (code == 'p') 401 set_line_thickness(env->size); 402 printf("PMPD%d", p[0]); 403 for (int i = 1; i < np; i++) 404 printf(",%d", p[i]); 405 printf("PM2%cP", code == 'p' ? 'E' : 'F'); 406 hpgl_end(); 407 break; 408 } 409 case '~': 410 { 411 if (np & 1) { 412 error("even number of arguments required for spline"); 413 break; 414 } 415 if (np == 0) { 416 error("no arguments for spline"); 417 break; 418 } 419 hpgl_init(); 420 if (!moveto(env->hpos, env->vpos)) 421 return; 422 hpgl_start(); 423 set_line_thickness(env->size); 424 printf("PD%d,%d", p[0]/2, p[1]/2); 425 const int tnum = 2; 426 const int tden = 3; 427 if (np > 2) { 428 fputs("BR", stdout); 429 for (int i = 0; i < np - 2; i += 2) { 430 if (i != 0) 431 putchar(','); 432 printf("%d,%d,%d,%d,%d,%d", 433 (p[i]*tnum)/(2*tden), 434 (p[i + 1]*tnum)/(2*tden), 435 p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden), 436 p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden), 437 (p[i] - p[i]/2) + p[i + 2]/2, 438 (p[i + 1] - p[i + 1]/2) + p[i + 3]/2); 439 } 440 } 441 printf("PR%d,%d", p[np - 2] - p[np - 2]/2, p[np - 1] - p[np - 1]/2); 442 hpgl_end(); 443 break; 444 } 445 case 'c': 446 case 'C': 447 // troff adds an extra argument to C 448 if (np != 1 && !(code == 'C' && np == 2)) { 449 error("1 argument required for circle"); 450 break; 451 } 452 hpgl_init(); 453 if (!moveto(env->hpos + p[0]/2, env->vpos)) 454 return; 455 hpgl_start(); 456 if (code == 'c') { 457 set_line_thickness(env->size); 458 printf("CI%d", p[0]/2); 459 } 460 else 461 printf("WG%d,0,360", p[0]/2); 462 hpgl_end(); 463 break; 464 case 'e': 465 case 'E': 466 if (np != 2) { 467 error("2 arguments required for ellipse"); 468 break; 469 } 470 hpgl_init(); 471 if (!moveto(env->hpos + p[0]/2, env->vpos)) 472 return; 473 hpgl_start(); 474 printf("SC0,%.4f,0,-%.4f,2", hpgl_scale * double(p[0])/p[1], hpgl_scale); 475 if (code == 'e') { 476 set_line_thickness(env->size); 477 printf("CI%d", p[1]/2); 478 } 479 else 480 printf("WG%d,0,360", p[1]/2); 481 printf("SC0,%.4f,0,-%.4f,2", hpgl_scale, hpgl_scale); 482 hpgl_end(); 483 break; 484 case 'a': 485 { 486 if (np != 4) { 487 error("4 arguments required for arc"); 488 break; 489 } 490 hpgl_init(); 491 if (!moveto(env->hpos, env->vpos)) 492 return; 493 hpgl_start(); 494 set_line_thickness(env->size); 495 double c[2]; 496 if (adjust_arc_center(p, c)) { 497 double sweep = ((atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0]) 498 - atan2(-c[1], -c[0])) 499 * 180.0/PI); 500 if (sweep > 0.0) 501 sweep -= 360.0; 502 printf("PDAR%d,%d,%f", int(c[0]), int(c[1]), sweep); 503 } 504 else 505 printf("PD%d,%d", p[0] + p[2], p[1] + p[3]); 506 hpgl_end(); 507 } 508 break; 509 case 'f': 510 if (np != 1 && np != 2) { 511 error("1 argument required for fill"); 512 break; 513 } 514 hpgl_init(); 515 hpgl_start(); 516 if (p[0] >= 0 && p[0] <= 1000) 517 printf("FT10,%d", p[0]/10); 518 hpgl_end(); 519 break; 520 case 'F': 521 // not implemented yet 522 break; 523 case 't': 524 { 525 if (np == 0) { 526 line_thickness = -1; 527 } 528 else { 529 // troff gratuitously adds an extra 0 530 if (np != 1 && np != 2) { 531 error("0 or 1 argument required for thickness"); 532 break; 533 } 534 line_thickness = p[0]; 535 } 536 break; 537 } 538 default: 539 error("unrecognised drawing command `%1'", char(code)); 540 break; 541 } 542 } 543 544 void lj4_printer::hpgl_init() 545 { 546 if (hpgl_inited) 547 return; 548 hpgl_inited = 1; 549 hpgl_scale = double(DEFAULT_HPGL_UNITS)/font::res; 550 printf("\033&f0S" // push position 551 "\033*p0x0Y" // move to 0,0 552 "\033*c%dx%dy0T" // establish picture frame 553 "\033%%1B" // switch to HPGL 554 "SP1SC0,%.4f,0,-%.4f,2IR0,100,0,100" // set up scaling 555 "LA1,4,2,4" // round line ends and joins 556 "PR" // relative plotting 557 "TR0" // opaque 558 ";\033%%1A" // back to PCL 559 "\033&f1S", // pop position 560 MAX_PAPER_WIDTH, MAX_PAPER_HEIGHT, 561 hpgl_scale, hpgl_scale); 562 } 563 564 void lj4_printer::set_line_thickness(int size, int dot) 565 { 566 double pw; 567 if (line_thickness < 0) 568 pw = (size * (line_width_factor * 25.4))/(font::sizescale * 72000.0); 569 else 570 pw = line_thickness*25.4/font::res; 571 if (dot && pw < MIN_DOT_PEN_WIDTH) 572 pw = MIN_DOT_PEN_WIDTH; 573 if (pw != pen_width) { 574 printf("PW%f", pw); 575 pen_width = pw; 576 } 577 } 578 579 font *lj4_printer::make_font(const char *nm) 580 { 581 return lj4_font::load_lj4_font(nm); 582 } 583 584 printer *make_printer() 585 { 586 return new lj4_printer(user_paper_size); 587 } 588 589 static 590 int lookup_paper_size(const char *s) 591 { 592 for (unsigned int i = 0; 593 i < sizeof(paper_table)/sizeof(paper_table[0]); i++) { 594 // FIXME Perhaps allow unique prefix. 595 if (strcasecmp(s, paper_table[i].name) == 0) 596 return i; 597 } 598 return -1; 599 } 600 601 static void usage(FILE *stream); 602 603 extern "C" int optopt, optind; 604 605 int main(int argc, char **argv) 606 { 607 setlocale(LC_NUMERIC, "C"); 608 program_name = argv[0]; 609 static char stderr_buf[BUFSIZ]; 610 setbuf(stderr, stderr_buf); 611 int c; 612 static const struct option long_options[] = { 613 { "help", no_argument, 0, CHAR_MAX + 1 }, 614 { "version", no_argument, 0, 'v' }, 615 { NULL, 0, 0, 0 } 616 }; 617 while ((c = getopt_long(argc, argv, "c:d:F:I:lp:vw:", long_options, NULL)) 618 != EOF) 619 switch(c) { 620 case 'l': 621 landscape_flag = 1; 622 break; 623 case 'I': 624 // ignore include search path 625 break; 626 case ':': 627 if (optopt == 'd') { 628 fprintf(stderr, "duplex assumed to be long-side\n"); 629 duplex_flag = 1; 630 } else 631 fprintf(stderr, "option -%c requires an argument\n", optopt); 632 fflush(stderr); 633 break; 634 case 'd': 635 if (!isdigit(*optarg)) // this ugly hack prevents -d without 636 optind--; // args from messing up the arg list 637 duplex_flag = atoi(optarg); 638 if (duplex_flag != 1 && duplex_flag != 2) { 639 fprintf(stderr, "odd value for duplex; assumed to be long-side\n"); 640 duplex_flag = 1; 641 } 642 break; 643 case 'p': 644 { 645 int n = lookup_paper_size(optarg); 646 if (n < 0) 647 error("unknown paper size `%1'", optarg); 648 else 649 user_paper_size = n; 650 break; 651 } 652 case 'v': 653 printf("GNU grolj4 (groff) version %s\n", Version_string); 654 exit(0); 655 break; 656 case 'F': 657 font::command_line_font_dir(optarg); 658 break; 659 case 'c': 660 { 661 char *ptr; 662 long n = strtol(optarg, &ptr, 10); 663 if (n == 0 && ptr == optarg) 664 error("argument for -c must be a positive integer"); 665 else if (n <= 0 || n > 32767) 666 error("out of range argument for -c"); 667 else 668 ncopies = unsigned(n); 669 break; 670 } 671 case 'w': 672 { 673 char *ptr; 674 long n = strtol(optarg, &ptr, 10); 675 if (n == 0 && ptr == optarg) 676 error("argument for -w must be a non-negative integer"); 677 else if (n < 0 || n > INT_MAX) 678 error("out of range argument for -w"); 679 else 680 line_width_factor = int(n); 681 break; 682 } 683 case CHAR_MAX + 1: // --help 684 usage(stdout); 685 exit(0); 686 break; 687 case '?': 688 usage(stderr); 689 exit(1); 690 break; 691 default: 692 assert(0); 693 } 694 SET_BINARY(fileno(stdout)); 695 if (optind >= argc) 696 do_file("-"); 697 else { 698 for (int i = optind; i < argc; i++) 699 do_file(argv[i]); 700 } 701 return 0; 702 } 703 704 static void usage(FILE *stream) 705 { 706 fprintf(stream, 707 "usage: %s [-lv] [-d [n]] [-c n] [-p paper_size]\n" 708 " [-w n] [-F dir] [files ...]\n", 709 program_name); 710 } 711