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 8This file is part of groff. 9 10groff is free software; you can redistribute it and/or modify it under 11the terms of the GNU General Public License as published by the Free 12Software Foundation; either version 2, or (at your option) any later 13version. 14 15groff is distributed in the hope that it will be useful, but WITHOUT ANY 16WARRANTY; without even the implied warranty of MERCHANTABILITY or 17FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 18for more details. 19 20You should have received a copy of the GNU General Public License along 21with groff; see the file COPYING. If not, write to the Free Software 22Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */ 23 24/* 25TODO 26 27option to use beziers for circle/ellipse/arc 28option to use lines for spline (for LJ3) 29left/top offset registration 30output bin selection option 31paper source option 32output non-integer parameters using fixed point numbers 33X command to insert contents of file 34X command to specify inline escape sequence (how to specify unprintable chars?) 35X command to include bitmap graphics 36*/ 37 38#include "driver.h" 39#include "nonposix.h" 40 41extern "C" const char *Version_string; 42 43static 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 61static int user_paper_size = -1; 62static int landscape_flag = 0; 63static 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 78const int DEFAULT_HPGL_UNITS = 1016; 79int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR; 80unsigned ncopies = 0; // 0 means don't send ncopies command 81 82static int lookup_paper_size(const char *); 83 84class lj4_font : public font { 85public: 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; 94private: 95 lj4_font(const char *); 96}; 97 98lj4_font::lj4_font(const char *nm) 99: font(nm), weight(0), style(0), proportional(0), typeface(0) 100{ 101} 102 103lj4_font::~lj4_font() 104{ 105} 106 107lj4_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 117static 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 129void 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 164class lj4_printer : public printer { 165public: 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(); 174private: 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 195inline 196int 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 204inline 205void lj4_printer::hpgl_start() 206{ 207 fputs("\033%1B", stdout); 208} 209 210inline 211void lj4_printer::hpgl_end() 212{ 213 fputs(";\033%0A", stdout); 214} 215 216lj4_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 257lj4_printer::~lj4_printer() 258{ 259 fputs("\033E", stdout); 260} 261 262void lj4_printer::begin_page(int) 263{ 264} 265 266void lj4_printer::end_page(int) 267{ 268 putchar('\f'); 269 cur_hpos = -1; 270} 271 272void lj4_printer::end_of_line() 273{ 274 cur_hpos = -1; // force absolute motion 275} 276 277inline 278int is_unprintable(unsigned char c) 279{ 280 return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27); 281} 282 283void 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 327int 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 346void 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 544void 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 564void 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 579font *lj4_printer::make_font(const char *nm) 580{ 581 return lj4_font::load_lj4_font(nm); 582} 583 584printer *make_printer() 585{ 586 return new lj4_printer(user_paper_size); 587} 588 589static 590int 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 601static void usage(FILE *stream); 602 603extern "C" int optopt, optind; 604 605int 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 704static 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