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