/* Copyright (C) 1994-2020 Free Software Foundation, Inc. Written by Francisco Andrés Verdú with many ideas taken from the other groff drivers. This file is part of groff. groff is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. groff is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* TODO - Add X command to include bitmaps */ #include #include "driver.h" #include "lbp.h" #include "charset.h" #include "paper.h" #include "nonposix.h" extern "C" const char *Version_string; static int user_papersize = -1; static int orientation = -1; // custom paper format static double user_paperlength = 0; static double user_paperwidth = 0; static int ncopies = 1; #define DEFAULT_LINEWIDTH_FACTOR 40 // 0.04em static int linewidth_factor = DEFAULT_LINEWIDTH_FACTOR; static int set_papersize(const char *paperformat); class lbp_font : public font { public: ~lbp_font(); void handle_unknown_font_command(const char *command, const char *arg, const char *filename, int lineno); static lbp_font *load_lbp_font(const char *); char *lbpname; char is_scalable; private: lbp_font(const char *); }; class lbp_printer : public printer { public: lbp_printer(int, double, double); ~lbp_printer(); void set_char(glyph *, font *, const environment *, int, const char *name); void draw(int code, int *p, int np, const environment *env); void begin_page(int); void end_page(int page_length); font *make_font(const char *); void end_of_line(); private: void set_line_thickness(int size,const environment *env); void vdmstart(); void vdmflush(); // the name vdmend was already used in lbp.h void setfillmode(int mode); void polygon( int hpos,int vpos,int np,int *p); char *font_name(const lbp_font *f, const int siz); int fill_pattern; int fill_mode; int cur_hpos; int cur_vpos; lbp_font *cur_font; int cur_size; unsigned short cur_symbol_set; int line_thickness; int req_linethickness; // requested line thickness // custom paper format int papersize; int paperlength; int paperwidth; }; lbp_font::lbp_font(const char *nm) : font(nm) { } lbp_font::~lbp_font() { } lbp_font *lbp_font::load_lbp_font(const char *s) { lbp_font *f = new lbp_font(s); f->lbpname = NULL; f->is_scalable = 1; // Default is that fonts are scalable if (!f->load()) { delete f; return 0; } return f; } void lbp_font::handle_unknown_font_command(const char *command, const char *arg, const char *filename, int lineno) { if (strcmp(command, "lbpname") == 0) { if (arg == 0) fatal_with_file_and_line(filename, lineno, "'%1' command requires an argument", command); this->lbpname = new char[strlen(arg) + 1]; strcpy(this->lbpname, arg); // we recognize bitmapped fonts by the first character of its name if (arg[0] == 'N') this->is_scalable = 0; // fprintf(stderr, "Loading font \"%s\" \n", arg); } // fprintf(stderr, "Loading font %s \"%s\" in %s at %d\n", // command, arg, filename, lineno); } static void wp54charset() { unsigned int i; lbpputs("\033[714;100;29;0;32;120.}"); for (i = 0; i < sizeof(symset); i++) lbpputc(symset[i]); lbpputs("\033[100;0 D"); return; } lbp_printer::lbp_printer(int ps, double pw, double pl) : fill_pattern(1), fill_mode(0), cur_hpos(-1), cur_font(0), cur_size(0), cur_symbol_set(0), req_linethickness(-1) { SET_BINARY(fileno(stdout)); lbpinit(stdout); lbpputs("\033c\033;\033[2&z\033[7 I\033[?32h\033[?33h\033[11h"); wp54charset(); // Define the new symbol set lbpputs("\033[7 I\033[?32h\033[?33h\033[11h"); // Paper format handling if (orientation < 0) orientation = 0; // Default orientation is portrait papersize = 14; // Default paper format is A4 if (font::papersize) { papersize = set_papersize(font::papersize); paperlength = font::paperlength; paperwidth = font::paperwidth; } if (ps >= 0) { papersize = ps; paperlength = int(pl * font::res + 0.5); paperwidth = int(pw * font::res + 0.5); } if (papersize < 80) // standard paper lbpprintf("\033[%dp", (papersize | orientation)); else // Custom paper lbpprintf("\033[%d;%d;%dp", (papersize | orientation), paperlength, paperwidth); // Number of copies lbpprintf("\033[%dv\n", ncopies); lbpputs("\033[0u\033[1u\033P1y Grolbp\033\\"); lbpmoveabs(0, 0); lbpputs("\033[0t\033[2t"); lbpputs("\033('$2\033)' 1"); // Primary symbol set IBML // Secondary symbol set IBMR1 cur_symbol_set = 0; } lbp_printer::~lbp_printer() { lbpputs("\033P1y\033\\"); lbpputs("\033c\033<"); } inline void lbp_printer::set_line_thickness(int size,const environment *env) { if (size == 0) line_thickness = 1; else { if (size < 0) // line_thickness = // (env->size * (font::res/72)) * (linewidth_factor/1000) // we ought to check for overflow line_thickness = env->size * linewidth_factor * font::res / 72000; else // size > 0 line_thickness = size; } // else from if (size == 0) if (line_thickness < 1) line_thickness = 1; if (vdminited()) vdmlinewidth(line_thickness); req_linethickness = size; // an size requested /* fprintf(stderr, "thickness: %d == %d, size %d, %d \n", size, line_thickness, env->size,req_linethickness); */ return; } // lbp_printer::set_line_thickness void lbp_printer::begin_page(int) { } void lbp_printer::end_page(int) { if (vdminited()) vdmflush(); lbpputc('\f'); cur_hpos = -1; } void lbp_printer::end_of_line() { cur_hpos = -1; // force absolute motion } char *lbp_printer::font_name(const lbp_font *f, const int siz) { static char bfont_name[255]; // The resulting font name char type, // Italic, Roman, Bold ori, // Normal or Rotated *nam; // The font name without other data. int cpi; // The font size in characters per inch // (bitmapped fonts are monospaced). /* Bitmap font selection is ugly in this printer, so don't expect this function to be elegant. */ bfont_name[0] = 0x00; if (orientation) // Landscape ori = 'R'; else // Portrait ori = 'N'; type = f->lbpname[strlen(f->lbpname) - 1]; nam = new char[strlen(f->lbpname) - 2]; strncpy(nam, &(f->lbpname[1]), strlen(f->lbpname) - 2); nam[strlen(f->lbpname) - 2] = 0x00; // fprintf(stderr, "Bitmap font '%s' %d %c %c \n", nam, siz, type, ori); /* Since these fonts are available only at certain sizes, 10 and 17 cpi for courier, 12 and 17 cpi for elite, we adjust the resulting size. */ cpi = 17; // Fortunately there are only two bitmapped fonts shipped with the printer. if (!strcasecmp(nam, "courier")) { // Courier font if (siz >= 12) cpi = 10; else cpi = 17; } if (!strcasecmp(nam, "elite")) { if (siz >= 10) cpi = 12; else cpi = 17; } // Now that we have all the data, let's generate the font name. if ((type != 'B') && (type != 'I')) // Roman font sprintf(bfont_name, "%c%s%d", ori, nam, cpi); else sprintf(bfont_name, "%c%s%d%c", ori, nam, cpi, type); return bfont_name; } void lbp_printer::set_char(glyph *g, font *f, const environment *env, int w, const char *) { int code = f->get_code(g); unsigned char ch = code & 0xff; unsigned short symbol_set = code >> 8; if (f != cur_font) { lbp_font *psf = (lbp_font *)f; // fprintf(stderr, "Loading font %s \"%d\" \n", psf->lbpname, env->size); if (psf->is_scalable) { // Scalable font selection is different from bitmaped lbpprintf("\033Pz%s.IBML\033\\\033[%d C", psf->lbpname, (int)((env->size * font::res) / 72)); } else // bitmapped font lbpprintf("\033Pz%s.IBML\033\\\n", font_name(psf, env->size)); lbpputs("\033)' 1"); // Select IBML and IBMR1 symbol set cur_font = psf; cur_symbol_set = 0; // Update the line thickness if needed if ((req_linethickness < 0 ) && (env->size != cur_size)) set_line_thickness(req_linethickness,env); cur_size = env->size; } if (symbol_set != cur_symbol_set) { if (cur_symbol_set == 3) // if current symbol set is Symbol we must restore the font lbpprintf("\033Pz%s.IBML\033\\\033[%d C", cur_font->lbpname, (int)((env->size * font::res) / 72)); switch (symbol_set) { case 0: lbpputs("\033('$2\033)' 1"); // Select IBML and IBMR1 symbol sets break; case 1: lbpputs("\033(d\033)' 1"); // Select wp54 symbol set break; case 2: lbpputs("\033('$2\033)'!0"); // Select IBMP symbol set break; case 3: lbpprintf("\033PzSymbol.SYML\033\\\033[%d C", (int)((env->size * font::res) / 72)); lbpputs("\033(\"!!0\033)\"!!1"); // Select symbol font break; case 4: lbpputs("\033)\"! 1\033(\"!$2"); // Select PS symbol set break; } cur_symbol_set = symbol_set; } if (env->size != cur_size) { if (!cur_font->is_scalable) lbpprintf("\033Pz%s.IBML\033\\\n", font_name(cur_font, env->size)); else lbpprintf("\033[%d C", (int)((env->size * font::res) / 72)); cur_size = env->size; // Update the line thickness if needed if (req_linethickness < 0 ) set_line_thickness(req_linethickness,env); } if ((env->hpos != cur_hpos) || (env->vpos != cur_vpos)) { // lbpmoveabs(env->hpos - ((5 * 300) / 16), env->vpos); lbpmoveabs(env->hpos - 64, env->vpos - 64); cur_vpos = env->vpos; cur_hpos = env->hpos; } if ((ch & 0x7F) < 32) lbpputs("\033[1.v"); lbpputc(ch); cur_hpos += w; } void lbp_printer::vdmstart() { FILE *f; static int changed_origin = 0; errno = 0; f = tmpfile(); // f = fopen("/tmp/gtmp","w+"); if (f == NULL) perror("Opening temporary file"); vdminit(f); if (!changed_origin) { // we should change the origin only one time changed_origin = 1; vdmorigin(-63, 0); } vdmlinewidth(line_thickness); } void lbp_printer::vdmflush() { char buffer[1024]; int bytes_read = 1; vdmend(); fflush(lbpoutput); /* let's copy the vdm code to the output */ rewind(vdmoutput); do { bytes_read = fread(buffer, 1, sizeof(buffer), vdmoutput); bytes_read = fwrite(buffer, 1, bytes_read, lbpoutput); } while (bytes_read == sizeof(buffer)); fclose(vdmoutput); // This will also delete the file, // since it is created by tmpfile() vdmoutput = NULL; } inline void lbp_printer::setfillmode(int mode) { if (mode != fill_mode) { if (mode != 1) vdmsetfillmode(mode, 1, 0); else // To get black, we must use white inverted. vdmsetfillmode(mode, 1, 1); fill_mode = mode; } } inline void lbp_printer::polygon(int hpos, int vpos, int np, int *p) { int *points, i; points = new int[np + 2]; points[0] = hpos; points[1] = vpos; // fprintf(stderr, "Polygon (%d,%d) ", points[0], points[1]); for (i = 0; i < np; i++) points[i + 2] = p[i]; // for (i = 0; i < np; i++) fprintf(stderr, " %d ", p[i]); // fprintf(stderr, "\n"); vdmpolygon((np /2) + 1, points); } void lbp_printer::draw(int code, int *p, int np, const environment *env) { if ((req_linethickness < 0 ) && (env->size != cur_size)) set_line_thickness(req_linethickness,env); switch (code) { case 't': if (np == 0) line_thickness = 1; else { // troff gratuitously adds an extra 0 if (np != 1 && np != 2) { error("0 or 1 argument required for thickness"); break; } set_line_thickness(p[0],env); } break; case 'l': // Line if (np != 2) { error("2 arguments required for line"); break; } if (!vdminited()) vdmstart(); vdmline(env->hpos, env->vpos, p[0], p[1]); /* fprintf(stderr, "\nline: %d,%d - %d,%d thickness %d == %d\n", env->hpos - 64,env->vpos -64, env->hpos - 64 + p[0], env->vpos -64 + p[1], env->size, line_thickness);*/ break; case 'R': // Rule if (np != 2) { error("2 arguments required for Rule"); break; } if (vdminited()) { setfillmode(fill_pattern); // Solid Rule vdmrectangle(env->hpos, env->vpos, p[0], p[1]); } else { lbpruleabs(env->hpos - 64, env->vpos -64, p[0], p[1]); cur_vpos = p[1]; cur_hpos = p[0]; } // fprintf(stderr, "\nrule: thickness %d == %d\n", // env->size, line_thickness); break; case 'P': // Filled Polygon if (!vdminited()) vdmstart(); setfillmode(fill_pattern); polygon(env->hpos, env->vpos, np, p); break; case 'p': // Empty Polygon if (!vdminited()) vdmstart(); setfillmode(0); polygon(env->hpos, env->vpos, np, p); break; case 'C': // Filled Circle if (!vdminited()) vdmstart(); // fprintf(stderr, "Circle (%d,%d) Fill %d\n", // env->hpos, env->vpos, fill_pattern); setfillmode(fill_pattern); vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2); break; case 'c': // Empty Circle if (!vdminited()) vdmstart(); setfillmode(0); vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2); break; case 'E': // Filled Ellipse if (!vdminited()) vdmstart(); setfillmode(fill_pattern); vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0); break; case 'e': // Empty Ellipse if (!vdminited()) vdmstart(); setfillmode(0); vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0); break; case 'a': // Arc if (!vdminited()) vdmstart(); setfillmode(0); // VDM draws arcs clockwise and pic counterclockwise // We must compensate for that, exchanging the starting and // ending points vdmvarc(env->hpos + p[0], env->vpos+p[1], int(sqrt(double((p[0]*p[0]) + (p[1]*p[1])))), p[2], p[3], (-p[0]), (-p[1]), 1, 2); break; case '~': // Spline if (!vdminited()) vdmstart(); setfillmode(0); vdmspline(np/2, env->hpos, env->vpos, p); break; case 'f': if (np != 1 && np != 2) { error("1 argument required for fill"); break; } // fprintf(stderr, "Fill %d\n", p[0]); if ((p[0] == 1) || (p[0] >= 1000)) { // Black fill_pattern = 1; break; } if (p[0] == 0) { // White fill_pattern = 0; break; } if ((p[0] > 1) && (p[0] < 1000)) { if (p[0] >= 990) fill_pattern = -23; else if (p[0] >= 700) fill_pattern = -28; else if (p[0] >= 500) fill_pattern = -27; else if (p[0] >= 400) fill_pattern = -26; else if (p[0] >= 300) fill_pattern = -25; else if (p[0] >= 200) fill_pattern = -22; else if (p[0] >= 100) fill_pattern = -24; else fill_pattern = -21; } break; case 'F': // not implemented yet break; default: error("unrecognised drawing command '%1'", char(code)); break; } return; } font *lbp_printer::make_font(const char *nm) { return lbp_font::load_lbp_font(nm); } printer *make_printer() { return new lbp_printer(user_papersize, user_paperwidth, user_paperlength); } static struct { const char *name; int code; } lbp_papersizes[] = {{ "A4", 14 }, { "letter", 30 }, { "legal", 32 }, { "executive", 40 }, }; static int set_papersize(const char *paperformat) { unsigned int i; // First, test for a standard (i.e. supported directly by the printer) // paper format. for (i = 0 ; i < sizeof(lbp_papersizes) / sizeof(lbp_papersizes[0]); i++) { if (strcasecmp(lbp_papersizes[i].name,paperformat) == 0) return lbp_papersizes[i].code; } // Otherwise, we assume a custom paper format. return 82; // XXX: magic number } static void handle_unknown_desc_command(const char *command, const char *arg, const char *filename, int lineno) { // orientation command if (strcasecmp(command, "orientation") == 0) { // We give priority to command-line options if (orientation > 0) return; if (arg == 0) error_with_file_and_line(filename, lineno, "'orientation' command requires an argument"); else { if (strcasecmp(arg, "portrait") == 0) orientation = 0; else { if (strcasecmp(arg, "landscape") == 0) orientation = 1; else error_with_file_and_line(filename, lineno, "invalid argument to 'orientation' command"); } } } } static struct option long_options[] = { { "orientation", required_argument, NULL, 'o' }, { "version", no_argument, NULL, 'v' }, { "copies", required_argument, NULL, 'c' }, { "landscape", no_argument, NULL, 'l' }, { "papersize", required_argument, NULL, 'p' }, { "linewidth", required_argument, NULL, 'w' }, { "fontdir", required_argument, NULL, 'F' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, 0, 0 } }; static void usage(FILE *stream) { fprintf(stream, "usage: %s [-l] [-c num-copies] [-F font-directory] [-o orientation]" " [-p paper-format] [-w width] [file ...]\n" "usage: %s {-v | --version}\n" "usage: %s {-h | --help}\n", program_name, program_name, program_name); if (stdout == stream) { fputs( "\n" "Translate the output of troff(1) into a CaPSL and VDM format suitable" "\n" "for Canon LBP-4 and LBP-8 printers. See the grolbp(1) manual page.\n", stream); exit(EXIT_SUCCESS); } } int main(int argc, char **argv) { if (program_name == NULL) program_name = strsave(argv[0]); font::set_unknown_desc_command_handler(handle_unknown_desc_command); // command line parsing int c; int option_index = 0; while ((c = getopt_long(argc, argv, "c:F:hI:lo:p:vw:", long_options, &option_index)) != EOF) { switch (c) { case 'F': font::command_line_font_dir(optarg); break; case 'I': // ignore include path arguments break; case 'p': { const char *s; if (!font::scan_papersize(optarg, &s, &user_paperlength, &user_paperwidth)) error("ignoring invalid paper format '%1'", optarg); else user_papersize = set_papersize(s); break; } case 'l': orientation = 1; break; case 'v': printf("GNU grolbp (groff) version %s\n", Version_string); exit(EXIT_SUCCESS); break; case 'o': if (strcasecmp(optarg, "portrait") == 0) orientation = 0; else { if (strcasecmp(optarg, "landscape") == 0) orientation = 1; else error("unknown orientation '%1'", optarg); } break; case 'c': { char *ptr; long n = strtol(optarg, &ptr, 10); if ((n <= 0) && (ptr == optarg)) error("argument for -c must be a positive integer"); else if (n <= 0 || n > 32767) error("out of range argument for -c"); else ncopies = unsigned(n); break; } case 'w': { char *ptr; long n = strtol(optarg, &ptr, 10); if (n == 0 && ptr == optarg) error("argument for -w must be a non-negative integer"); else if (n < 0 || n > INT_MAX) error("out of range argument for -w"); else linewidth_factor = int(n); break; } case 'h': usage(stdout); break; case '?': usage(stderr); exit(EXIT_FAILURE); break; default: assert(0 == "unhandled getopt_long return value"); } } if (optind >= argc) do_file("-"); while (optind < argc) do_file(argv[optind++]); if (lbpoutput) lbpputs("\033c\033<"); return 0; } // Local Variables: // fill-column: 72 // mode: C++ // End: // vim: set cindent noexpandtab shiftwidth=2 textwidth=72: