diff options
Diffstat (limited to '')
76 files changed, 33654 insertions, 0 deletions
diff --git a/src/devices/grodvi/dvi.cpp b/src/devices/grodvi/dvi.cpp new file mode 100644 index 0000000..f9e8a57 --- /dev/null +++ b/src/devices/grodvi/dvi.cpp @@ -0,0 +1,988 @@ +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +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 <http://www.gnu.org/licenses/>. */ + +#include <assert.h> + +#include "driver.h" +#include "nonposix.h" +#include "paper.h" + +extern "C" const char *Version_string; + +#define DEFAULT_LINEWIDTH 40 +static int linewidth = DEFAULT_LINEWIDTH; + +static int draw_flag = 1; + +static int landscape_flag = 0; +static double user_paper_length = 0; +static double user_paper_width = 0; + +/* These values were chosen because: + +(MULTIPLIER*SIZESCALE)/(RES*UNITWIDTH) == 1/(2^20 * 72.27) + +and 57816 is an exact multiple of both 72.27*SIZESCALE and 72. + +The width in the groff font file is the product of MULTIPLIER and the +width in the tfm file. */ + +#define RES 57816 +#define RES_7227 (RES/7227) +#define UNITWIDTH 131072 +#define SIZESCALE 100 +#define MULTIPLIER 1 + +class dvi_font : public font { + dvi_font(const char *); +public: + int checksum; + int design_size; + ~dvi_font(); + void handle_unknown_font_command(const char *command, const char *arg, + const char *filename, int lineno); + static dvi_font *load_dvi_font(const char *); +}; + +dvi_font *dvi_font::load_dvi_font(const char *s) +{ + dvi_font *f = new dvi_font(s); + if (!f->load()) { + delete f; + return 0; + } + return f; +} + +dvi_font::dvi_font(const char *nm) +: font(nm), checksum(0), design_size(0) +{ +} + +dvi_font::~dvi_font() +{ +} + +void dvi_font::handle_unknown_font_command(const char *command, + const char *arg, + const char *filename, int lineno) +{ + char *ptr; + if (strcmp(command, "checksum") == 0) { + if (arg == 0) + fatal_with_file_and_line(filename, lineno, + "'checksum' command requires an argument"); + checksum = int(strtol(arg, &ptr, 10)); + if (checksum == 0 && ptr == arg) { + fatal_with_file_and_line(filename, lineno, "bad checksum"); + } + } + else if (strcmp(command, "designsize") == 0) { + if (arg == 0) + fatal_with_file_and_line(filename, lineno, + "'designsize' command requires an argument"); + design_size = int(strtol(arg, &ptr, 10)); + if (design_size == 0 && ptr == arg) { + fatal_with_file_and_line(filename, lineno, "bad design size"); + } + } +} + +#define FONTS_MAX 256 + +struct output_font { + dvi_font *f; + int point_size; + output_font() : f(0) { } +}; + +class dvi_printer : public printer { + FILE *fp; + int max_drift; + int byte_count; + int last_bop; + int page_count; + int cur_h; + int cur_v; + int end_h; + int max_h; + int max_v; + output_font output_font_table[FONTS_MAX]; + font *cur_font; + int cur_point_size; + color cur_color; + int pushed; + int pushed_h; + int pushed_v; + int have_pushed; + void preamble(); + void postamble(); + void define_font(int); + void set_font(int); + void possibly_begin_line(); + void set_color(color *); +protected: + enum { + id_byte = 2, + set1 = 128, + put1 = 133, + put_rule = 137, + bop = 139, + eop = 140, + push = 141, + pop = 142, + right1 = 143, + down1 = 157, + fnt_num_0 = 171, + fnt1 = 235, + xxx1 = 239, + fnt_def1 = 243, + pre = 247, + post = 248, + post_post = 249, + filler = 223 + }; + int line_thickness; + + void out1(int); + void out2(int); + void out3(int); + void out4(int); + void moveto(int, int); + void out_string(const char *); + void out_signed(unsigned char, int); + void out_unsigned(unsigned char, int); + void do_special(const char *); +public: + dvi_printer(); + ~dvi_printer(); + font *make_font(const char *); + void begin_page(int); + void end_page(int); + void set_char(glyph *, font *, const environment *, int, const char *); + void special(char *, const environment *, char); + void end_of_line(); + void draw(int, int *, int, const environment *); +}; + + +class draw_dvi_printer : public dvi_printer { + int output_pen_size; + void set_line_thickness(const environment *); + void fill_next(const environment *); +public: + draw_dvi_printer(); + ~draw_dvi_printer(); + void draw(int code, int *p, int np, const environment *env); + void end_page(int); +}; + +dvi_printer::dvi_printer() +: fp(stdout), byte_count(0), last_bop(-1), page_count(0), max_h(0), max_v(0), + cur_font(0), cur_point_size(-1), pushed(0), line_thickness(-1) +{ + if (font::res != RES) + fatal("resolution must be %1", RES); + if (font::unitwidth != UNITWIDTH) + fatal("unitwidth must be %1", UNITWIDTH); + if (font::hor != 1) + fatal("hor must be equal to 1"); + if (font::vert != 1) + fatal("vert must be equal to 1"); + if (font::sizescale != SIZESCALE) + fatal("sizescale must be equal to %1", SIZESCALE); + max_drift = font::res/1000; // this is fairly arbitrary + preamble(); +} + +dvi_printer::~dvi_printer() +{ + postamble(); +} + + +draw_dvi_printer::draw_dvi_printer() +: output_pen_size(-1) +{ +} + +draw_dvi_printer::~draw_dvi_printer() +{ +} + + +void dvi_printer::out1(int n) +{ + byte_count += 1; + putc(n & 0xff, fp); +} + +void dvi_printer::out2(int n) +{ + byte_count += 2; + putc((n >> 8) & 0xff, fp); + putc(n & 0xff, fp); +} + +void dvi_printer::out3(int n) +{ + byte_count += 3; + putc((n >> 16) & 0xff, fp); + putc((n >> 8) & 0xff, fp); + putc(n & 0xff, fp); +} + +void dvi_printer::out4(int n) +{ + byte_count += 4; + putc((n >> 24) & 0xff, fp); + putc((n >> 16) & 0xff, fp); + putc((n >> 8) & 0xff, fp); + putc(n & 0xff, fp); +} + +void dvi_printer::out_string(const char *s) +{ + out1(strlen(s)); + while (*s != 0) + out1(*s++); +} + + +void dvi_printer::end_of_line() +{ + if (pushed) { + out1(pop); + pushed = 0; + cur_h = pushed_h; + cur_v = pushed_v; + } +} + +void dvi_printer::possibly_begin_line() +{ + if (!pushed) { + have_pushed = pushed = 1; + pushed_h = cur_h; + pushed_v = cur_v; + out1(push); + } +} + +int scale(int x, int z) +{ + int sw; + int a, b, c, d; + int alpha, beta; + alpha = 16*z; beta = 16; + while (z >= 040000000L) { + z /= 2; beta /= 2; + } + d = x & 255; + c = (x >> 8) & 255; + b = (x >> 16) & 255; + a = (x >> 24) & 255; + sw = (((((d * z) / 0400) + (c * z)) / 0400) + (b * z)) / beta; + if (a == 255) + sw -= alpha; + else + assert(a == 0); + return sw; +} + +void dvi_printer::set_color(color *col) +{ + cur_color = *col; + char buf[256]; + unsigned int components[4]; + color_scheme cs = col->get_components(components); + switch (cs) { + case DEFAULT: + sprintf(buf, "color gray 0"); + break; + case RGB: + sprintf(buf, "color rgb %.3g %.3g %.3g", + double(Red) / double(color::MAX_COLOR_VAL), + double(Green) / double(color::MAX_COLOR_VAL), + double(Blue) / double(color::MAX_COLOR_VAL)); + break; + case CMY: + col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black); + // fall through + case CMYK: + sprintf(buf, "color cmyk %.3g %.3g %.3g %.3g", + double(Cyan) / double(color::MAX_COLOR_VAL), + double(Magenta) / double(color::MAX_COLOR_VAL), + double(Yellow) / double(color::MAX_COLOR_VAL), + double(Black) / double(color::MAX_COLOR_VAL)); + break; + case GRAY: + sprintf(buf, "color gray %.3g", + double(Gray) / double(color::MAX_COLOR_VAL)); + break; + } + do_special(buf); +} + +void dvi_printer::set_char(glyph *g, font *f, const environment *env, + int w, const char *) +{ + if (*env->col != cur_color) + set_color(env->col); + int code = f->get_code(g); + if (env->size != cur_point_size || f != cur_font) { + cur_font = f; + cur_point_size = env->size; + int i; + for (i = 0;; i++) { + if (i >= FONTS_MAX) { + fatal("too many output fonts required"); + } + if (output_font_table[i].f == 0) { + output_font_table[i].f = (dvi_font *)cur_font; + output_font_table[i].point_size = cur_point_size; + define_font(i); + } + if (output_font_table[i].f == cur_font + && output_font_table[i].point_size == cur_point_size) + break; + } + set_font(i); + } + int distance = env->hpos - cur_h; + if (env->hpos != end_h && distance != 0) { + out_signed(right1, distance); + cur_h = env->hpos; + } + else if (distance > max_drift) { + out_signed(right1, distance - max_drift); + cur_h = env->hpos - max_drift; + } + else if (distance < -max_drift) { + out_signed(right1, distance + max_drift); + cur_h = env->hpos + max_drift; + } + if (env->vpos != cur_v) { + out_signed(down1, env->vpos - cur_v); + cur_v = env->vpos; + } + possibly_begin_line(); + end_h = env->hpos + w; + cur_h += scale(f->get_width(g, UNITWIDTH) / MULTIPLIER, + cur_point_size * RES_7227); + if (cur_h > max_h) + max_h = cur_h; + if (cur_v > max_v) + max_v = cur_v; + if (code >= 0 && code <= 127) + out1(code); + else + out_unsigned(set1, code); +} + +void dvi_printer::define_font(int i) +{ + out_unsigned(fnt_def1, i); + dvi_font *f = output_font_table[i].f; + out4(f->checksum); + out4(output_font_table[i].point_size*RES_7227); + out4(int((double(f->design_size)/(1<<20))*RES_7227*100 + .5)); + const char *nm = f->get_internal_name(); + out1(0); + out_string(nm); +} + +void dvi_printer::set_font(int i) +{ + if (i >= 0 && i <= 63) + out1(fnt_num_0 + i); + else + out_unsigned(fnt1, i); +} + +void dvi_printer::out_signed(unsigned char base, int param) +{ + if (-128 <= param && param < 128) { + out1(base); + out1(param); + } + else if (-32768 <= param && param < 32768) { + out1(base+1); + out2(param); + } + else if (-(1 << 23) <= param && param < (1 << 23)) { + out1(base+2); + out3(param); + } + else { + out1(base+3); + out4(param); + } +} + +void dvi_printer::out_unsigned(unsigned char base, int param) +{ + if (param >= 0) { + if (param < 256) { + out1(base); + out1(param); + } + else if (param < 65536) { + out1(base+1); + out2(param); + } + else if (param < (1 << 24)) { + out1(base+2); + out3(param); + } + else { + out1(base+3); + out4(param); + } + } + else { + out1(base+3); + out4(param); + } +} + +void dvi_printer::preamble() +{ + out1(pre); + out1(id_byte); + out4(254000); + out4(font::res); + out4(1000); + out1(0); +} + +void dvi_printer::postamble() +{ + int tem = byte_count; + out1(post); + out4(last_bop); + out4(254000); + out4(font::res); + out4(1000); + out4(max_v); + out4(max_h); + out2(have_pushed); // stack depth + out2(page_count); + int i; + for (i = 0; i < FONTS_MAX && output_font_table[i].f != 0; i++) + define_font(i); + out1(post_post); + out4(tem); + out1(id_byte); + for (i = 0; i < 4 || byte_count % 4 != 0; i++) + out1(filler); +} + +void dvi_printer::begin_page(int i) +{ + page_count++; + int tem = byte_count; + out1(bop); + out4(i); + for (int j = 1; j < 10; j++) + out4(0); + out4(last_bop); + last_bop = tem; + // By convention position (0,0) in a dvi file is placed at (1in, 1in). + cur_h = font::res; + cur_v = font::res; + end_h = 0; + if (page_count == 1) { + char buf[256]; + // at least dvips uses this + double length = user_paper_length ? user_paper_length : + double(font::paperlength) / font::res; + double width = user_paper_width ? user_paper_width : + double(font::paperwidth) / font::res; + if (width > 0 && length > 0) { + sprintf(buf, "papersize=%.3fin,%.3fin", + landscape_flag ? length : width, + landscape_flag ? width : length); + do_special(buf); + } + } + if (cur_color != default_color) + set_color(&cur_color); +} + +void dvi_printer::end_page(int) +{ + set_color(&default_color); + if (pushed) + end_of_line(); + out1(eop); + cur_font = 0; +} + +void draw_dvi_printer::end_page(int len) +{ + dvi_printer::end_page(len); + output_pen_size = -1; +} + +void dvi_printer::do_special(const char *s) +{ + int len = strlen(s); + if (len == 0) + return; + possibly_begin_line(); + out_unsigned(xxx1, len); + while (*s) + out1(*s++); +} + +void dvi_printer::special(char *arg, const environment *env, char type) +{ + if (type != 'p') + return; + moveto(env->hpos, env->vpos); + do_special(arg); +} + +void dvi_printer::moveto(int h, int v) +{ + if (h != cur_h) { + out_signed(right1, h - cur_h); + cur_h = h; + if (cur_h > max_h) + max_h = cur_h; + } + if (v != cur_v) { + out_signed(down1, v - cur_v); + cur_v = v; + if (cur_v > max_v) + max_v = cur_v; + } + end_h = 0; +} + +void dvi_printer::draw(int code, int *p, int np, const environment *env) +{ + if (code == 'l') { + int x = 0, y = 0; + int height = 0, width = 0; + int thickness; + if (line_thickness < 0) + thickness = env->size*RES_7227*linewidth/1000; + else if (line_thickness > 0) + thickness = line_thickness; + else + thickness = 1; + if (np != 2) { + error("2 arguments required for line"); + } + else if (p[0] == 0) { + // vertical rule + if (p[1] > 0) { + x = env->hpos - thickness/2; + y = env->vpos + p[1] + thickness/2; + height = p[1] + thickness; + width = thickness; + } + else if (p[1] < 0) { + x = env->hpos - thickness/2; + y = env->vpos + thickness/2; + height = thickness - p[1]; + width = thickness; + } + } + else if (p[1] == 0) { + if (p[0] > 0) { + x = env->hpos - thickness/2; + y = env->vpos + thickness/2; + height = thickness; + width = p[0] + thickness; + } + else if (p[0] < 0) { + x = env->hpos - p[0] - thickness/2; + y = env->vpos + thickness/2; + height = thickness; + width = thickness - p[0]; + } + } + if (height != 0) { + moveto(x, y); + out1(put_rule); + out4(height); + out4(width); + } + } + else if (code == '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"); + else + line_thickness = p[0]; + } + } + else if (code == 'R') { + if (np != 2) + error("2 arguments required for rule"); + else if (p[0] != 0 || p[1] != 0) { + int dh = p[0]; + int dv = p[1]; + int oh = env->hpos; + int ov = env->vpos; + if (dv > 0) { + ov += dv; + dv = -dv; + } + if (dh < 0) { + oh += dh; + dh = -dh; + } + moveto(oh, ov); + out1(put_rule); + out4(-dv); + out4(dh); + } + } +} + +// XXX Will this overflow? + +inline int milliinches(int n) +{ + return (n*1000 + font::res/2)/font::res; +} + +void draw_dvi_printer::set_line_thickness(const environment *env) +{ + int desired_pen_size + = milliinches(line_thickness < 0 + // Will this overflow? + ? env->size*RES_7227*linewidth/1000 + : line_thickness); + if (desired_pen_size != output_pen_size) { + char buf[256]; + sprintf(buf, "pn %d", desired_pen_size); + do_special(buf); + output_pen_size = desired_pen_size; + } +} + +void draw_dvi_printer::fill_next(const environment *env) +{ + unsigned int g; + if (env->fill->is_default()) + g = 0; + else { + // currently, only BW support + env->fill->get_gray(&g); + } + char buf[256]; + sprintf(buf, "sh %.3g", 1 - double(g) / double(color::MAX_COLOR_VAL)); + do_special(buf); +} + +void draw_dvi_printer::draw(int code, int *p, int np, const environment *env) +{ + char buf[1024]; + int fill_flag = 0; + switch (code) { + case 'C': + fill_flag = 1; + // fall through + case 'c': + { + // troff adds an extra argument to C + if (np != 1 && !(code == 'C' && np == 2)) { + error("1 argument required for circle"); + break; + } + moveto(env->hpos+p[0]/2, env->vpos); + if (fill_flag) + fill_next(env); + else + set_line_thickness(env); + int rad; + rad = milliinches(p[0]/2); + sprintf(buf, "%s 0 0 %d %d 0 6.28319", + (fill_flag ? "ia" : "ar"), + rad, + rad); + do_special(buf); + break; + } + case 'l': + if (np != 2) { + error("2 arguments required for line"); + break; + } + moveto(env->hpos, env->vpos); + set_line_thickness(env); + do_special("pa 0 0"); + sprintf(buf, "pa %d %d", milliinches(p[0]), milliinches(p[1])); + do_special(buf); + do_special("fp"); + break; + case 'E': + fill_flag = 1; + // fall through + case 'e': + if (np != 2) { + error("2 arguments required for ellipse"); + break; + } + moveto(env->hpos+p[0]/2, env->vpos); + if (fill_flag) + fill_next(env); + else + set_line_thickness(env); + sprintf(buf, "%s 0 0 %d %d 0 6.28319", + (fill_flag ? "ia" : "ar"), + milliinches(p[0]/2), + milliinches(p[1]/2)); + do_special(buf); + break; + case 'P': + fill_flag = 1; + // fall through + case 'p': + { + if (np & 1) { + error("even number of arguments required for polygon"); + break; + } + if (np == 0) { + error("no arguments for polygon"); + break; + } + moveto(env->hpos, env->vpos); + if (fill_flag) + fill_next(env); + else + set_line_thickness(env); + do_special("pa 0 0"); + int h = 0, v = 0; + for (int i = 0; i < np; i += 2) { + h += p[i]; + v += p[i+1]; + sprintf(buf, "pa %d %d", milliinches(h), milliinches(v)); + do_special(buf); + } + do_special("pa 0 0"); + do_special(fill_flag ? "ip" : "fp"); + break; + } + case '~': + { + if (np & 1) { + error("even number of arguments required for spline"); + break; + } + if (np == 0) { + error("no arguments for spline"); + break; + } + moveto(env->hpos, env->vpos); + set_line_thickness(env); + do_special("pa 0 0"); + int h = 0, v = 0; + for (int i = 0; i < np; i += 2) { + h += p[i]; + v += p[i+1]; + sprintf(buf, "pa %d %d", milliinches(h), milliinches(v)); + do_special(buf); + } + do_special("sp"); + break; + } + case 'a': + { + if (np != 4) { + error("4 arguments required for arc"); + break; + } + set_line_thickness(env); + double c[2]; + if (adjust_arc_center(p, c)) { + int rad = milliinches(int(sqrt(c[0]*c[0] + c[1]*c[1]) + .5)); + moveto(env->hpos + int(c[0]), env->vpos + int(c[1])); + double start = atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0]); + double end = atan2(-c[1], -c[0]); + if (end - start < 0) + start -= 2 * 3.14159265358; + sprintf(buf, "ar 0 0 %d %d %f %f", rad, rad, start, end); + do_special(buf); + } + else { + moveto(env->hpos, env->vpos); + do_special("pa 0 0"); + sprintf(buf, + "pa %d %d", + milliinches(p[0] + p[2]), + milliinches(p[1] + p[3])); + do_special(buf); + do_special("fp"); + } + break; + } + 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; + } + line_thickness = p[0]; + } + break; + } + case 'R': + { + if (np != 2) { + error("2 arguments required for rule"); + break; + } + int dh = p[0]; + if (dh == 0) + break; + int dv = p[1]; + if (dv == 0) + break; + int oh = env->hpos; + int ov = env->vpos; + if (dv > 0) { + ov += dv; + dv = -dv; + } + if (dh < 0) { + oh += dh; + dh = -dh; + } + moveto(oh, ov); + out1(put_rule); + out4(-dv); + out4(dh); + break; + } + default: + error("unrecognised drawing command '%1'", char(code)); + break; + } +} + +font *dvi_printer::make_font(const char *nm) +{ + return dvi_font::load_dvi_font(nm); +} + +printer *make_printer() +{ + if (draw_flag) + return new draw_dvi_printer; + else + return new dvi_printer; +} + +static void usage(FILE *stream); + +int main(int argc, char **argv) +{ + setlocale(LC_NUMERIC, "C"); + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + int c; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((c = getopt_long(argc, argv, "dF:I:lp:vw:", long_options, NULL)) + != EOF) + switch(c) { + case 'd': + draw_flag = 0; + break; + case 'l': + landscape_flag = 1; + break; + case 'F': + font::command_line_font_dir(optarg); + break; + case 'I': + // ignore include search path + break; + case 'p': + if (!font::scan_papersize(optarg, 0, + &user_paper_length, &user_paper_width)) + error("ignoring invalid paper format '%1'", optarg); + break; + case 'v': + { + printf("GNU grodvi (groff) version %s\n", Version_string); + exit(0); + break; + } + case 'w': + if (sscanf(optarg, "%d", &linewidth) != 1 + || linewidth < 0 || linewidth > 1000) { + error("invalid line width '%1' ignored", optarg); + linewidth = DEFAULT_LINEWIDTH; + } + break; + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + SET_BINARY(fileno(stdout)); + if (optind >= argc) + do_file("-"); + else { + for (int i = optind; i < argc; i++) + do_file(argv[i]); + } + return 0; +} + +static void usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-dl] [-F dir] [-p paper-format] [-w n] [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s --help\n", + program_name, program_name, program_name); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/devices/grodvi/grodvi.1.man b/src/devices/grodvi/grodvi.1.man new file mode 100644 index 0000000..05cbe0d --- /dev/null +++ b/src/devices/grodvi/grodvi.1.man @@ -0,0 +1,633 @@ +.TH grodvi @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +grodvi \- +.I groff +output driver for TeX DVI format +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1989-2020, 2022 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_grodvi_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X +.el .ds tx TeX +. +.\" This macro definition is poor style from a portability standpoint, +.\" but it's a good test and demonstration of the standard font +.\" repertoire for the devices where it has any effect at all, and so +.\" should be retained. +.de FT +. if '\\*(.T'dvi' .ft \\$1 +.. +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY grodvi +.RB [ \-dl ] +.RB [ \-F\~\c +.IR dir ] +.RB [ \-p\~\c +.IR paper-format ] +.RB [ \-w\~\c +.IR n ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY grodvi +.B \-\-help +.YS +. +. +.SY grodvi +.B \-v +. +.SY grodvi +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU +.I roff +DVI output driver translates the output of +.MR @g@troff @MAN1EXT@ +into \*[tx] DVI format. +. +Normally, +.I grodvi +is invoked by +.MR groff @MAN1EXT@ +when the latter is given the +.RB \[lq] \-T\~dvi \[rq] +option. +. +(In this installation, +.B @DEVICE@ +is the default output device.) +. +Use +.IR groff 's +.B \-P +option to pass any options shown above to +.IR grodvi . +. +If no +.I file +arguments are given, +or if +.I file +is \[lq]\-\[rq], +.I grodvi +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.P +The DVI file generated by +.I grodvi +can interpreted by any correctly written DVI driver. +. +.I troff \" generic +drawing primitives are implemented using +.I tpic +version\~2 specials. +. +If the driver does not support these, +.B \[rs]D +escape sequences will not produce any output. +. +. +.P +Encapsulated PostScript (EPS) files can be easily included; +use the +.B PSPIC +macro. +. +.I pspic.tmac +is loaded automatically by +.IR dvi.tmac . +. +See +.MR groff_tmac @MAN5EXT@ . +. +. +.P +The default color used by the +.B \[rs]m +and +.B \[rs]M +escape sequences is black. +. +Currently, +the stroke color for +.B \[rs]D +drawing escape sequences is black; +fill color values are translated to gray. +. +. +.P +In +.IR groff , +as in AT&T +.IR troff , \" AT&T +the +.B \[rs]N +escape sequence can be used to access any glyph in the current font by +its position in the corresponding TFM file. +. +. +.P +By design, +the DVI format doesn't care about the physical dimensions of the output +medium. +. +Instead, +.I grodvi +emits the equivalent to \*[tx]'s +.BI \%\[rs]special{\:\%papersize= width , length } +on the first page; +.I dvips +(or another DVI driver) +then sets the page size accordingly. +. +If either the page width or length is not positive, +no +.B \%papersize +special is output. +. +. +.P +A device control escape sequence +.BI \[rs]X\[aq] anything \[aq] +is translated to the same DVI file instructions as would be produced by +.BI \%\[rs]special{ anything } +in \*[tx]; +.I anything +cannot contain a newline. +. +. +.\" ==================================================================== +.SS Typefaces +.\" ==================================================================== +. +.I grodvi +supports the standard four styles: +.B R +(roman), +.B I +.RI ( italic ), +.B B +.RB ( bold ), +and +.B BI +(\f[BI]bold-italic\f[]). +. +Fonts are grouped into families +.B T +and +.B H +having members in each style. +. +\[lq]CM\[rq] abbreviates \[lq]Computer Modern\[rq]. +. +. +.RS +.TP +.B TR +.FT TR +CM Roman (cmr10) +.FT +. +.TQ +.B TI +.FT TI +CM Text Italic (cmti10) +.FT +. +.TQ +.B TB +.FT TB +CM Bold Extended Roman (cmbx10) +.FT +. +.TQ +.B TBI +.FT TBI +CM Bold Extended Text Italic (cmbxti10) +.FT +. +.TQ +.B HR +.FT HR +CM Sans Serif (cmss10) +.FT +. +.TQ +.B HI +.FT HI +CM Slanted Sans Serif (cmssi10) +.FT +. +.TQ +.B HB +.FT HB +CM Sans Serif Bold Extended (cmssbx10) +.FT +. +.TQ +.B HBI +.FT HBI +CM Slanted Sans Serif Bold Extended (cmssbxo10) +.FT +.RE +. +. +.LP +The following fonts are not members of a family. +. +. +.RS +.TP +.B CW +.FT CW +CM Typewriter Text (cmtt10) +.FT +. +.TQ +.B CWI +.FT CWI +CM Italic Typewriter Text (cmitt10) +.FT +.RE +. +. +.P +Special fonts include +.B MI +(cmmi10), +.B S +(cmsy10), +.B EX +(cmex10), +.B SC +(cmtex10, +only for +.BR CW ), +and, +perhaps surprisingly, +.BR TR , +.BR TI , +and +.BR CW , +.\" See font/devdvi/generate/Makefile for details. +because \*[tx] places some glyphs in text fonts that +.I troff \" generic +generally does not. +. +For italic fonts, +.B CWI +is used instead of +.BR CW . +. +. +.P +Finally, +the symbol fonts of the American Mathematical Society are available as +special fonts +.B SA +(msam10) and +.B SB +(msbm10). +. +They are are not mounted by default. +. +. +.br +.ne 2v +.P +The +.I @g@troff +option +.B \-mec +loads the +.I ec.tmac +macro file, +employing the EC and TC fonts instead of CM. +. +These are designed similarly to the Computer Modern fonts; +further, +they provide Euro +.B \[rs][Eu] +and per mille +.B \[rs][%0] +glyphs. +. +.I ec.tmac +must be loaded before any language-specific macro files because it does +not set up the codes necessary for automatic hyphenation. +. +. +.\" ==================================================================== +.SS "Font description files" +.\" ==================================================================== +. +Use +.MR tfmtodit @MAN1EXT@ +to create +.I groff +font description files from TFM +(\*[tx] font metrics) +files. +. +The font description file should contain the following additional +directives, +which +.I tfmtodit +generates automatically. +. +. +.TP +.BI internalname\~ name +The name of the TFM file +(without the +.I .tfm +extension) is +.IR name . +. +. +.TP +.BI checksum\~ n +The checksum in the TFM file is +.IR n . +. +. +.TP +.BI designsize\~ n +The design size in the TFM file is +.IR n . +. +. +.\" ==================================================================== +.SS "Drawing commands" +.\" ==================================================================== +. +.I grodvi +supports an additional drawing command. +. +. +.TP +.BI \[rs]D\[aq]R\~ "dh dv" \[aq] +Draw a rule +(solid black rectangle) +with one corner at the drawing position, +and the diagonally opposite corner at the drawing position +.RI +( dh , dv ), +which becomes the new drawing position afterward. +. +This command produces a rule in the DVI file and so can be printed even +with a driver that does not support +.I tpic +specials, +unlike the other +.B \[rs]D +commands. +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-d +Do not use +.I tpic +specials to implement drawing commands. +. +Horizontal and vertical lines are implemented by rules. +. +Other drawing commands are ignored. +. +. +.TP +.BI \-F\~ dir +Prepend directory +.RI dir /dev name +to the search path for font and device description files; +.I name +is the name of the device, +usually +.BR dvi . +. +. +.TP +.B \-l +Use landscape orientation rather than portrait. +. +. +.TP +.BI \-p\~ paper-format +Set physical dimensions of output medium, +overriding the +.BR \%papersize , +.BR \%paperlength , +and +.B \%paperwidth +directives in the +.I DESC +file. +. +.I paper-format +can be any argument accepted by the +.B \%papersize +directive; +see +.MR groff_font @MAN5EXT@ . +. +. +.TP +.BI \-w\~ n +Draw rules (lines) with a thickness of +.IR n \~thousandths +of an em. +. +The default thickness is +.B 40 +(0.04\~em). +. +. +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +lists directories in which to search for +.IR devdvi , +.IR grodvi 's +directory of device and font description files. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @FONTDIR@/\:\%devdvi/\:DESC +describes the +.B dvi +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devdvi/ F +describes the font known +.RI as\~ F +on device +.BR dvi . +. +. +.TP +.I @MACRODIR@/\:dvi\:.tmac +defines font mappings, +special characters, +and colors for use with the +.B dvi +output device. +. +It is automatically loaded by +.I \%troffrc +when the +.B dvi +output device is selected. +. +. +.TP +.I @MACRODIR@/\:ec\:.tmac +configures the +.B dvi +output device to use +the EC and TC font families instead of CM +(Computer Modern). +. +. +.\" ==================================================================== +.SH Bugs +.\" ==================================================================== +. +DVI files produced by +.I grodvi +use a different resolution +(57,816 units per inch) +from those produced by \*[tx]. +. +Incorrectly written drivers which assume the resolution used by \*[tx], +rather than using the resolution specified in the DVI file, +will not work with +.IR grodvi . +. +. +.LP +When using the +.B \-d +option with boxed tables, +vertical and horizontal lines can sometimes protrude by one pixel. +. +This is a consequence of the way \*[tx] requires that the heights +and widths of rules be rounded. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.UR https://\:texfaq\:.org/\:FAQ\-\:ECfonts +\[lq]What are the EC fonts?\[rq] +.UE ; +\*[tx] FAQ: Frequently Asked Question List for \*[tx] +. +. +.P +.MR tfmtodit @MAN1EXT@ , +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ , +.MR groff_out @MAN5EXT@ , +.MR groff_font @MAN5EXT@ , +.MR groff_char @MAN7EXT@ , +.MR groff_tmac @MAN5EXT@ +. +. +.\" Clean up. +.rm FT +.rm tx +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_grodvi_1_man_C] +.do rr *groff_grodvi_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/grodvi/grodvi.am b/src/devices/grodvi/grodvi.am new file mode 100644 index 0000000..ce93359 --- /dev/null +++ b/src/devices/grodvi/grodvi.am @@ -0,0 +1,32 @@ +# Copyright (C) 2014-2020 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +bin_PROGRAMS += grodvi +grodvi_SOURCES = src/devices/grodvi/dvi.cpp +grodvi_LDADD = \ + libdriver.a \ + libgroff.a \ + lib/libgnu.a $(LIBM) +man1_MANS += src/devices/grodvi/grodvi.1 +EXTRA_DIST += src/devices/grodvi/grodvi.1.man + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/grohtml/grohtml.1.man b/src/devices/grohtml/grohtml.1.man new file mode 100644 index 0000000..2243b47 --- /dev/null +++ b/src/devices/grohtml/grohtml.1.man @@ -0,0 +1,731 @@ +.TH grohtml @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +grohtml, post\-grohtml, pre\-grohtml \- +.I groff +output driver for HTML +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1999-2022 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_grohtml_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY pre\-grohtml +.RB [ \-epV ] +.RB [ \-a +.IR anti-aliasing-text-bits ] +.RB [ \-D +.IR image-directory ] +.RB [ \-F +.IR font-directory ] +.RB [ \-g +.IR anti-aliasing-graphic-bits ] +.RB [ \-i +.IR resolution ] +.RB [ \-I +.IR image-stem ] +.RB [ \-o +.IR image-vertical-offset ] +.RB [ \-x +.IR html-dialect ] +.I troff-command +.I troff-argument +\&.\|.\|. +.YS +. +. +.SY pre\-grohtml +.B \-\-help +.YS +. +. +.SY pre\-grohtml +.B \-v +. +.SY pre\-grohtml +.B \-\-version +.YS +. +. +.SY post\-grohtml +.RB [ \-bCGhlnrVy ] +.RB [ \-F +.IR font-directory ] +.RB [ \-j +.IR output-stem ] +.RB [ \-s +.IR base-point-size ] +.RB [ \-S +.IR heading-level ] +.RB [ \-x +.IR html-dialect ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY post\-grohtml +.B \-\-help +.YS +. +. +.SY post\-grohtml +.B \-v +. +.SY post\-grohtml +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU +.I roff +system's HTML support consists of a preprocessor, +.IR \%pre\-grohtml , +and an output driver, +.IR \%post\-grohtml ; +together, +they translate +.MR roff @MAN7EXT@ +documents to HTML. +. +Because a preprocessor is (uniquely) required for this output driver, +users should invoke +.I \%grohtml +via the +.MR groff @MAN1EXT@ +command with the +.B \-Thtml +or +.B \-Txhtml +options. +. +(In this installation, +.B @DEVICE@ +is the default output device.) +. +Use +.IR groff 's +.B \-P +option to pass any options shown above to +.IR \%grohtml . +. +If no operands are given, +or if +.I file +is +.RB \[lq] \- \[rq], +.I \%grohtml +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.P +.I \%grohtml +invokes +.I groff +twice. +. +In the first pass, +the preprocessor +.I \%pre\-grohtml +renders +pictures, +equations, +and tables as images in PostScript format using the +.B ps +output device. +. +In the second pass, +the output driver +.I \%post\-grohtml +translates the output of +.MR @g@troff @MAN1EXT@ +to HTML. +. +. +.P +.I \%grohtml +writes output encoded in \%UTF-8 and has built-in HTML entities for all +non-composite Unicode characters. +. +In spite of this, +.I groff +may issue warnings about unknown special characters if they can't be +found during the first pass. +. +Such warnings can be safely ignored unless the special characters +appear inside a table or equation. +. +. +.\" ==================================================================== +.SS Typefaces +.\" ==================================================================== +. +.I \%grohtml +supports the standard four styles: +.B R +(roman), +.B I +.RI ( italic ), +.B B +.RB ( bold ), +and +.B BI +(\f[BI]bold-italic\f[]). +. +Fonts are grouped into families +.B T +and +.B C +having members in each style. +. +. +.RS +.TP +.B TR +Times roman +. +.TQ +.B TI +Times italic +. +.TQ +.B TB +Times bold +. +.TQ +.B TBI +Times bold-italic +. +.TQ +.B CR +Courier roman +. +.TQ +.B CI +Courier italic +. +.TQ +.B CB +Courier bold +. +.TQ +.B CBI +Courier bold-italic +.RE +. +. +.P +A special font, +.BR S , +is also provided to accommodate +.I roff +documents that expect it to always be available. +. +. +.\" ==================================================================== +.SS "Font description files" +.\" ==================================================================== +. +The font description files used with +.I \%grohtml +expose the same glyph repertoire in their +.B charset +sections. +. +See +.MR groff_font @MAN5EXT@ . +. +. +.\" ==================================================================== +.SS Dependencies +.\" ==================================================================== +. +.I \%pre\-grohtml +generates an image whenever an +.I @g@eqn +equation, +.I @g@tbl +table, +or +.I @g@pic +picture is encountered in the input. +. +.I \%grohtml +therefore may run several commands as part of its operation. +. +These include the \%Netpbm tools +.IR \%pnmcrop , +.IR \%pnmcut , +and +.IR \%pnmtopng ; +\%Ghostscript +.RI ( gs ); +and the \%PSUtils tool +.IR \%psselect . +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.BI \-a \~anti-aliasing-text-bits +Number of bits of antialiasing information to be used by text when +generating PNG images. +. +The default +.RB is\~ 4 +but +.BR 0 , +.BR 1 , +and +.B 2 +are also valid. +. +Your system's version of +.I gs +must support the +.B \%\-dTextAlphaBits +option in order to exploit antialiasing. +.\" XXX: How antiquated are the ones that don't? Get rid of this? +. +A value +.RB of\~ 0 +stops +.I \%grohtml +from issuing antialiasing commands to +.IR gs . +. +. +.TP +.B \-b +Initialize the background color to white. +. +. +.TP +.B \-C +Suppress output of \[lq]CreationDate:\[rq] HTML comment. +. +. +.TP +.BI \-D \~image-directory +Instruct +.I \%grohtml +to place all image files into directory +.IR image-directory . +. +. +.TP +.B \-e +Direct +.I @g@eqn +to produce MathML. +. +. +.IP +This option should not be manually specified; +it is synthesized by +.I groff +depending on whether it was given the +.B \-Thtml +or +.B \-Txhtml +option. +. +. +.TP +.BI \-F \~font-directory +Prepend directory +.RI font-directory /dev name +to the search path for font and device description files; +.I name +is the name of the device, +usually +.BR html . +. +. +.TP +.BI \-g \~anti-aliasing-graphic-bits +Number of bits of antialiasing information to be used by graphics when +generating PNG images. +. +The default +.RB is\~ 4 +but +.BR 0 , +.BR 1 , +and +.B 2 +are also valid. +. +Your system's version of +.I gs +must support the +.B \%\-dGraphicAlphaBits +option in order to exploit antialiasing. +.\" XXX: How antiquated are the ones that don't? Get rid of this? +. +A value +.RB of\~ 0 +stops +.I \%grohtml +from issuing antialiasing commands to +.IR gs . +. +. +.TP +.B \-G +Suppress output of \[lq]Creator:\[rq] HTML comment. +. +. +.TP +.B \-h +Generate section headings by using HTML +.B B +elements and increasing the font size, +rather than HTML +.B H +elements. +. +. +.TP +.BI \-i \~resolution +Set the image resolution in pixels per inch; +the default +.RB is\~ 100 . +. +. +.TP +.BI \-I \~image-stem +Determine the image file name stem. +. +If omitted, +.I \%grohtml +uses +.IR \%grohtml\- XXXXX +(where +.I XXXXX +is the process ID). +. +A dash is appended to the stem to separate it from the following image +number. +. +. +.TP +.BI \-j \~output-stem +Instruct +.I \%grohtml +to split the HTML output into multiple files. +. +Output is written to a new file at each section heading +(but see option +.B \-S +below) +named +.IR output-stem\- n .html . +. +. +.TP +.B \-l +Turn off the production of automatic section links at the top of the +document. +. +. +.TP +.B \-n +Generate simple heading anchors whenever a section/number heading is +found. +. +Without the option the anchor value is the textual heading. +. +This can cause problems when a heading contains a \[lq]?\[rq] on older +versions of some browsers. +. +This feature is automatically enabled if a heading contains an image. +. +. +.TP +.BI \-o \~image-vertical-offset +Specify the vertical offset of images in points. +. +. +.TP +.B \-p +Display page rendering progress to the standard error stream. +. +.I \%grohtml +displays a page number only when an image is required. +. +. +.TP +.B \-r +Turn off the automatic header and footer line +(HTML rule). +. +. +.TP +.BI \-s \~base-type-size +Set the document's base type size in points. +. +When this size is used in the source, +it corresponds to the HTML base type size. +. +Every increase of two points in the source will produce a +.RB \[lq] big \[rq] +element, +and conversely when a decrease of two points is seen, +a +.RB \[lq] small \[rq] +element is emitted. +. +. +.TP +.BI \-S \~heading-level +When splitting HTML output +(see option +.B \-j +above), +split at each nested heading level defined by +.IR heading-level , +or higher). +. +The default is +.BR 1 . +. +. +.TP +.B \-V +Create an XHTML or HTML validator button at the bottom of each page of +the document. +. +. +.TP +.BI \-x \~html-dialect +Select HTML dialect. +. +Currently, +.I html-dialect +should be either the +.RB digit\~ 4 +or the +.RB letter\~ x , +which indicates whether +.I \%grohtml +should generate HTML\~4 or XHTML, +respectively. +. +. +.IP +This option should not be manually specified; +it is synthesized by +.I groff +depending on whether it was given the +.B \-Thtml +or +.B \-Txhtml +option. +. +. +.TP +.B \-y +Produce a right-aligned +.I groff +signature at the end of the document +(only if +.B \-V +is also specified). +. +. +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +lists directories in which to search for +.IR devhtml , +.IR grohtml 's +directory of device and font description files. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.TP +.I SOURCE_DATE_EPOCH +A timestamp +(expressed as seconds since the Unix epoch) +to use as the output creation timestamp in place of the current time. +. +The time is converted to human-readable form using +.MR ctime 3 +and recorded in an HTML comment. +. +. +.TP +.I TZ +The time zone to use when converting the current time +(or value of +.IR SOURCE_DATE_EPOCH ) +to human-readable form; +see +.MR tzset 3 . +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @FONTDIR@/\:\%devhtml/\:DESC +describes the +.B html +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devhtml/ F +describes the font known +.RI as\~ F +on device +.BR html . +. +. +.TP +.I @MACRODIR@/\:html\:.tmac +defines font mappings, +special characters, +and colors for use with the +.B html +output device. +. +It is automatically loaded by +.I \%troffrc +when either of the +.B html +or +.B xhtml +output devices is selected. +. +. +.TP +.I @MACRODIR@/\:html\-end\:.tmac +finalizes setup of the +.B html +output device. +. +It is automatically loaded by +.I \%troffrc\-end +when either of the +.B html +or +.B xhtml +output devices is selected. +. +. +.P +.I \%grohtml +uses temporary files. +. +See +.MR groff @MAN1EXT@ +for details about where such files are created. +. +. +.\" ==================================================================== +.SH Bugs +.\" ==================================================================== +. +.I \%grohtml +is still beta code. +. +. +.PP +.I \%grohtml +does not truly support hyphenation, +but you can fool it into hyphenating long input lines, +which can appear in HTML output with a hyphenated word followed by a +space but no line break. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.\" IR afmtodit (@MAN1EXT@), +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ , +.\" IR psbb (1), \" XXX: what is this? +.\" IR groff_out (@MAN5EXT@), +.\" IR groff_char (@MAN7EXT@), +.MR groff_font @MAN5EXT@ +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_grohtml_1_man_C] +.do rr *groff_grohtml_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/grohtml/grohtml.am b/src/devices/grohtml/grohtml.am new file mode 100644 index 0000000..a87cad2 --- /dev/null +++ b/src/devices/grohtml/grohtml.am @@ -0,0 +1,40 @@ +# Copyright (C) 2014-2020 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +bin_PROGRAMS += post-grohtml +post_grohtml_SOURCES = \ + src/devices/grohtml/post-html.cpp \ + src/devices/grohtml/html-table.cpp \ + src/devices/grohtml/html-text.cpp \ + src/devices/grohtml/output.cpp \ + src/devices/grohtml/html.h \ + src/devices/grohtml/html-text.h \ + src/devices/grohtml/html-table.h + +post_grohtml_LDADD = $(LIBM) \ + libdriver.a \ + libgroff.a \ + lib/libgnu.a +man1_MANS += src/devices/grohtml/grohtml.1 +EXTRA_DIST += src/devices/grohtml/grohtml.1.man + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/grohtml/html-table.cpp b/src/devices/grohtml/html-table.cpp new file mode 100644 index 0000000..3d7dfcd --- /dev/null +++ b/src/devices/grohtml/html-table.cpp @@ -0,0 +1,848 @@ +// -*- C++ -*- +/* Copyright (C) 2002-2020 Free Software Foundation, Inc. + * + * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.cpp + * + * html-table.h + * + * provides the methods necessary to handle indentation and tab + * positions using html tables. + */ + +/* +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 <http://www.gnu.org/licenses/>. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" +#include "html-table.h" +#include "ctype.h" +#include "html.h" +#include "html-text.h" + +#if !defined(TRUE) +# define TRUE (1==1) +#endif +#if !defined(FALSE) +# define FALSE (1==0) +#endif + +extern html_dialect dialect; + + +tabs::tabs () + : tab(NULL) +{ +} + +tabs::~tabs () +{ + delete_list(); +} + +/* + * delete_list - frees the tab list and sets tab to NULL. + */ + +void tabs::delete_list (void) +{ + tab_position *p = tab; + tab_position *q; + + while (p != NULL) { + q = p; + p = p->next; + delete q; + } + tab = NULL; +} + +void tabs::clear (void) +{ + delete_list(); +} + +/* + * compatible - returns TRUE if the tab stops in, s, do + * not conflict with the current tab stops. + * The new tab stops are _not_ placed into + * this class. + */ + +int tabs::compatible (const char *s) +{ + char align; + int total=0; + tab_position *last = tab; + + if (last == NULL) + return FALSE; // no tab stops defined + + // move over tag name + while ((*s != (char)0) && !isspace(*s)) + s++; + + while (*s != (char)0 && last != NULL) { + // move over white space + while ((*s != (char)0) && isspace(*s)) + s++; + // collect alignment + align = *s; + // move over alignment + s++; + // move over white space + while ((*s != (char)0) && isspace(*s)) + s++; + // collect tab position + total = atoi(s); + // move over tab position + while ((*s != (char)0) && !isspace(*s)) + s++; + if (last->alignment != align || last->position != total) + return FALSE; + + last = last->next; + } + return TRUE; +} + +/* + * init - scans the string, s, and initializes the tab stops. + */ + +void tabs::init (const char *s) +{ + char align; + int total=0; + tab_position *last = NULL; + + clear(); // remove any tab stops + + // move over tag name + while ((*s != (char)0) && !isspace(*s)) + s++; + + while (*s != (char)0) { + // move over white space + while ((*s != (char)0) && isspace(*s)) + s++; + // collect alignment + align = *s; + // move over alignment + s++; + // move over white space + while ((*s != (char)0) && isspace(*s)) + s++; + // collect tab position + total = atoi(s); + // move over tab position + while ((*s != (char)0) && !isspace(*s)) + s++; + if (last == NULL) { + tab = new tab_position; + last = tab; + } else { + last->next = new tab_position; + last = last->next; + } + last->alignment = align; + last->position = total; + last->next = NULL; + } +} + +/* + * check_init - define tab stops using, s, providing none already exist. + */ + +void tabs::check_init (const char *s) +{ + if (tab == NULL) + init(s); +} + +/* + * find_tab - returns the tab number corresponding to the position, pos. + */ + +int tabs::find_tab (int pos) +{ + tab_position *p; + int i=0; + + for (p = tab; p != NULL; p = p->next) { + i++; + if (p->position == pos) + return i; + } + return 0; +} + +/* + * get_tab_pos - returns the, nth, tab position + */ + +int tabs::get_tab_pos (int n) +{ + tab_position *p; + + n--; + for (p = tab; (p != NULL) && (n>0); p = p->next) { + n--; + if (n == 0) + return p->position; + } + return 0; +} + +char tabs::get_tab_align (int n) +{ + tab_position *p; + + n--; + for (p = tab; (p != NULL) && (n>0); p = p->next) { + n--; + if (n == 0) + return p->alignment; + } + return 'L'; +} + +/* + * dump_tab - display tab positions + */ + +void tabs::dump_tabs (void) +{ + int i=1; + tab_position *p; + + for (p = tab; p != NULL; p = p->next) { + printf("tab %d is %d\n", i, p->position); + i++; + } +} + +/* + * html_table - methods + */ + +html_table::html_table (simple_output *op, int linelen) + : out(op), columns(NULL), linelength(linelen), last_col(NULL), start_space(FALSE) +{ + tab_stops = new tabs(); +} + +html_table::~html_table () +{ + cols *c; + if (tab_stops != NULL) + delete tab_stops; + + c = columns; + while (columns != NULL) { + columns = columns->next; + delete c; + c = columns; + } +} + +/* + * remove_cols - remove a list of columns as defined by, c. + */ + +void html_table::remove_cols (cols *c) +{ + cols *p; + + while (c != NULL) { + p = c; + c = c->next; + delete p; + } +} + +/* + * set_linelength - sets the line length value in this table. + * It also adds an extra blank column to the + * table should linelen exceed the last column. + */ + +void html_table::set_linelength (int linelen) +{ + cols *p = NULL; + cols *c; + linelength = linelen; + + for (c = columns; c != NULL; c = c->next) { + if (c->right > linelength) { + c->right = linelength; + remove_cols(c->next); + c->next = NULL; + return; + } + p = c; + } + if (p != NULL && p->right > 0 && linelength > p->right) + add_column(p->no+1, p->right, linelength, 'L'); +} + +/* + * get_effective_linelength - + */ + +int html_table::get_effective_linelength (void) +{ + if (columns != NULL) + return linelength - columns->left; + else + return linelength; +} + +/* + * add_indent - adds the indent to a table. + */ + +void html_table::add_indent (int indent) +{ + if (columns != NULL && columns->left > indent) + add_column(0, indent, columns->left, 'L'); +} + +/* + * emit_table_header - emits the html header for this table. + */ + +void html_table::emit_table_header (int space) +{ + if (columns == NULL) + return; + + // dump_table(); + + last_col = NULL; + if (linelength > 0) { + out->nl(); + out->nl(); + + out->put_string("<table width=\"100%\"") + .put_string(" border=\"0\" rules=\"none\" frame=\"void\"\n") + .put_string(" cellspacing=\"0\" cellpadding=\"0\""); + out->put_string(">") + .nl(); + if (dialect == xhtml) + emit_colspan(); + out->put_string("<tr valign=\"top\" align=\"left\""); + if (space) { + out->put_string(" style=\"margin-top: "); + out->put_string(STYLE_VERTICAL_SPACE); + out->put_string("\""); + } + out->put_string(">").nl(); + } +} + +/* + * get_right - returns the right most position of this column. + */ + +int html_table::get_right (cols *c) +{ + if (c != NULL && c->right > 0) + return c->right; + if (c->next != NULL) + return c->left; + return linelength; +} + +/* + * set_space - assigns start_space. Used to determine the + * vertical alignment when generating the next table row. + */ + +void html_table::set_space (int space) +{ + start_space = space; +} + +/* + * emit_colspan - emits a series of colspan entries defining the + * table columns. + */ + +void html_table::emit_colspan (void) +{ + cols *b = columns; + cols *c = columns; + int width = 0; + + out->put_string("<colgroup>"); + while (c != NULL) { + if (b != NULL && b != c && is_gap(b)) + /* + * blank column for gap + */ + out->put_string("<col width=\"") + .put_number(is_gap(b)) + .put_string("%\" class=\"center\"></col>") + .nl(); + + width = (get_right(c)*100 + get_effective_linelength()/2) + / get_effective_linelength() + - (c->left*100 + get_effective_linelength()/2) + /get_effective_linelength(); + switch (c->alignment) { + case 'C': + out->put_string("<col width=\"") + .put_number(width) + .put_string("%\" class=\"center\"></col>") + .nl(); + break; + case 'R': + out->put_string("<col width=\"") + .put_number(width) + .put_string("%\" class=\"right\"></col>") + .nl(); + break; + default: + out->put_string("<col width=\"") + .put_number(width) + .put_string("%\"></col>") + .nl(); + } + b = c; + c = c->next; + } + out->put_string("</colgroup>").nl(); +} + +/* + * emit_td - writes out a <td> tag with a corresponding width + * if the dialect is html4. + */ + +void html_table::emit_td (int percentage, const char *s) +{ + if (percentage) { + if (dialect == html4) { + out->put_string("<td width=\"") + .put_number(percentage) + .put_string("%\""); + if (s != NULL) + out->put_string(s); + out->nl(); + } + else { + out->put_string("<td"); + if (s != NULL) + out->put_string(s); + out->nl(); + } + } +} + +/* + * emit_col - moves onto column, n. + */ + +void html_table::emit_col (int n) +{ + cols *c = columns; + cols *b = columns; + int width = 0; + + // must be a different row + if (last_col != NULL && n <= last_col->no) + emit_new_row(); + + while (c != NULL && c->no < n) + c = c->next; + + // can we find column, n? + if (c != NULL && c->no == n) { + // shutdown previous column + if (last_col != NULL) + out->put_string("</td>").nl(); + + // find previous column + if (last_col == NULL) + b = columns; + else + b = last_col; + + // have we a gap? + if (last_col != NULL) { + emit_td(is_gap(b), "></td>"); + b = b->next; + } + + // move across to column n + while (b != c) { + // we compute the difference after converting positions + // to avoid rounding errors + width = (get_right(b)*100 + get_effective_linelength()/2) + / get_effective_linelength() + - (b->left*100 + get_effective_linelength()/2) + /get_effective_linelength(); + emit_td(width, "></td>"); + // have we a gap? + emit_td(is_gap(b), "></td>"); + b = b->next; + } + width = (get_right(b)*100 + get_effective_linelength()/2) + / get_effective_linelength() + - (b->left*100 + get_effective_linelength()/2) + /get_effective_linelength(); + switch (b->alignment) { + case 'C': + emit_td(width, " align=center>"); + break; + case 'R': + emit_td(width, " align=right>"); + break; + default: + emit_td(width); + } + // remember column, b + last_col = b; + } +} + +/* + * finish_row - + */ + +void html_table::finish_row (void) +{ + int n = 0; + cols *c; + + if (last_col != NULL) { + for (c = last_col->next; c != NULL; c = c->next) + n = c->no; + + if (n > 0) + emit_col(n); +#if 1 + if (last_col != NULL) { + out->put_string("</td>"); + last_col = NULL; + } +#endif + out->put_string("</tr>").nl(); + } +} + +/* + * emit_new_row - move to the next row. + */ + +void html_table::emit_new_row (void) +{ + finish_row(); + + out->put_string("<tr valign=\"top\" align=\"left\""); + if (start_space) { + out->put_string(" style=\"margin-top: "); + out->put_string(STYLE_VERTICAL_SPACE); + out->put_string("\""); + } + out->put_string(">").nl(); + start_space = FALSE; + last_col = NULL; +} + +void html_table::emit_finish_table (void) +{ + finish_row(); + out->put_string("</table>"); +} + +/* + * add_column - adds a column. It returns FALSE if hstart..hend + * crosses into a different columns. + */ + +int html_table::add_column (int coln, int hstart, int hend, char align) +{ + cols *c = get_column(coln); + + if (c == NULL) + return insert_column(coln, hstart, hend, align); + else + return modify_column(c, hstart, hend, align); +} + +/* + * get_column - returns the column, coln. + */ + +cols *html_table::get_column (int coln) +{ + cols *c = columns; + + while (c != NULL && coln != c->no) + c = c->next; + + if (c != NULL && coln == c->no) + return c; + else + return NULL; +} + +/* + * insert_column - inserts a column, coln. + * It returns TRUE if it does not bump into + * another column. + */ + +int html_table::insert_column (int coln, int hstart, int hend, char align) +{ + cols *c = columns; + cols *l = columns; + cols *n = NULL; + + while (c != NULL && c->no < coln) { + l = c; + c = c->next; + } + if (l != NULL && l->no>coln && hend > l->left) + return FALSE; // new column bumps into previous one + + l = NULL; + c = columns; + while (c != NULL && c->no < coln) { + l = c; + c = c->next; + } + + if ((l != NULL) && (hstart < l->right)) + return FALSE; // new column bumps into previous one + + if ((l != NULL) && (l->next != NULL) && + (l->next->left < hend)) + return FALSE; // new column bumps into next one + + n = new cols; + if (l == NULL) { + n->next = columns; + columns = n; + } else { + n->next = l->next; + l->next = n; + } + n->left = hstart; + n->right = hend; + n->no = coln; + n->alignment = align; + return TRUE; +} + +/* + * modify_column - given a column, c, modify the width to + * contain hstart..hend. + * It returns TRUE if it does not clash with + * the next or previous column. + */ + +int html_table::modify_column (cols *c, int hstart, int hend, char align) +{ + cols *l = columns; + + while (l != NULL && l->next != c) + l = l->next; + + if ((l != NULL) && (hstart < l->right)) + return FALSE; // new column bumps into previous one + + if ((c->next != NULL) && (c->next->left < hend)) + return FALSE; // new column bumps into next one + + if (c->left > hstart) + c->left = hstart; + + if (c->right < hend) + c->right = hend; + + c->alignment = align; + + return TRUE; +} + +/* + * find_tab_column - finds the column number for position, pos. + * It searches through the list tab stops. + */ + +int html_table::find_tab_column (int pos) +{ + // remember the first column is reserved for untabbed glyphs + return tab_stops->find_tab(pos)+1; +} + +/* + * find_column - find the column number for position, pos. + * It searches through the list of columns. + */ + +int html_table::find_column (int pos) +{ + int p=0; + cols *c; + + for (c = columns; c != NULL; c = c->next) { + if (c->left > pos) + return p; + p = c->no; + } + return p; +} + +/* + * no_columns - returns the number of table columns (rather than tabs) + */ + +int html_table::no_columns (void) +{ + int n=0; + cols *c; + + for (c = columns; c != NULL; c = c->next) + n++; + return n; +} + +/* + * is_gap - returns the gap between column, c, and the next column. + */ + +int html_table::is_gap (cols *c) +{ + if (c == NULL || c->right <= 0 || c->next == NULL) + return 0; + else + // we compute the difference after converting positions + // to avoid rounding errors + return (c->next->left*100 + get_effective_linelength()/2) + / get_effective_linelength() + - (c->right*100 + get_effective_linelength()/2) + / get_effective_linelength(); +} + +/* + * no_gaps - returns the number of table gaps between the columns + */ + +int html_table::no_gaps (void) +{ + int n=0; + cols *c; + + for (c = columns; c != NULL; c = c->next) + if (is_gap(c)) + n++; + return n; +} + +/* + * get_tab_pos - returns the, nth, tab position + */ + +int html_table::get_tab_pos (int n) +{ + return tab_stops->get_tab_pos(n); +} + +char html_table::get_tab_align (int n) +{ + return tab_stops->get_tab_align(n); +} + + +void html_table::dump_table (void) +{ + if (columns != NULL) { + cols *c; + for (c = columns; c != NULL; c = c->next) { + printf("column %d %d..%d %c\n", c->no, c->left, c->right, c->alignment); + } + } else + tab_stops->dump_tabs(); +} + +/* + * html_indent - creates an indent with indentation, ind, given + * a line length of linelength. + */ + +html_indent::html_indent (simple_output *op, int ind, int pageoffset, int linelength) +{ + table = new html_table(op, linelength); + + table->add_column(1, ind+pageoffset, linelength, 'L'); + table->add_indent(pageoffset); + in = ind; + pg = pageoffset; + ll = linelength; +} + +html_indent::~html_indent (void) +{ + end(); + delete table; +} + +void html_indent::begin (int space) +{ + if (in + pg == 0) { + if (space) { + table->out->put_string(" style=\"margin-top: "); + table->out->put_string(STYLE_VERTICAL_SPACE); + table->out->put_string("\""); + } + } + else { + // + // we use exactly the same mechanism for calculating + // indentation as html_table::emit_col + // + table->out->put_string(" style=\"margin-left:") + .put_number(((in + pg) * 100 + ll/2) / ll - + (ll/2)/ll) + .put_string("%;"); + + if (space) { + table->out->put_string(" margin-top: "); + table->out->put_string(STYLE_VERTICAL_SPACE); + } + table->out->put_string("\""); + } +} + +void html_indent::end (void) +{ +} + +/* + * get_reg - collects the registers as supplied during initialization. + */ + +void html_indent::get_reg (int *ind, int *pageoffset, int *linelength) +{ + *ind = in; + *pageoffset = pg; + *linelength = ll; +} diff --git a/src/devices/grohtml/html-table.h b/src/devices/grohtml/html-table.h new file mode 100644 index 0000000..7ed27a9 --- /dev/null +++ b/src/devices/grohtml/html-table.h @@ -0,0 +1,133 @@ +// -*- C++ -*- +/* Copyright (C) 2002-2020 Free Software Foundation, Inc. + * + * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.h + * + * html-table.h + * + * provides the methods necessary to handle indentation and tab + * positions using html tables. + */ + +/* +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 <http://www.gnu.org/licenses/>. */ + +#include "html.h" + +#if !defined(HTML_TABLE_H) +#define HTML_TABLE_H + +typedef struct tab_position { + char alignment; + int position; + struct tab_position *next; +} tab_position; + + +class tabs { +public: + tabs (); + ~tabs (); + void clear (void); + int compatible (const char *s); + void init (const char *s); + void check_init (const char *s); + int find_tab (int pos); + int get_tab_pos (int n); + char get_tab_align (int n); + void dump_tabs (void); + +private: + void delete_list (void); + tab_position *tab; +}; + +/* + * define a column + */ + +typedef struct cols { + int left, right; + int no; + char alignment; + struct cols *next; +} cols; + +class html_table { +public: + html_table (simple_output *op, int linelen); + ~html_table (void); + int add_column (int coln, int hstart, int hend, char align); + cols *get_column (int coln); + int insert_column (int coln, int hstart, int hend, char align); + int modify_column (cols *c, int hstart, int hend, char align); + int find_tab_column (int pos); + int find_column (int pos); + int get_tab_pos (int n); + char get_tab_align (int n); + void set_linelength (int linelen); + int no_columns (void); + int no_gaps (void); + int is_gap (cols *c); + void dump_table (void); + void emit_table_header (int space); + void emit_col (int n); + void emit_new_row (void); + void emit_finish_table (void); + int get_right (cols *c); + void add_indent (int indent); + void finish_row (void); + int get_effective_linelength (void); + void set_space (int space); + void emit_colspan (void); + void emit_td (int percentage, const char *s = ">"); + + tabs *tab_stops; /* tab stop positions */ + simple_output *out; +private: + cols *columns; /* column entries */ + int linelength; + cols *last_col; /* last column started */ + int start_space; /* have we seen a '.sp' tag? */ + + void remove_cols (cols *c); +}; + +/* + * the indentation wrapper. + * Builds an indentation from a html-table. + * This table is only emitted if the paragraph is emitted. + */ + +class html_indent { +public: + html_indent (simple_output *op, int ind, int pageoffset, int linelength); + ~html_indent (void); + void begin (int space); // called if we need to use the indent + void get_reg (int *ind, int *pageoffset, int *linelength); + + // the indent is shutdown when it is deleted + +private: + void end (void); + int is_used; + int pg; // values of the registers as passed via initialization + int ll; + int in; + html_table *table; +}; + +#endif diff --git a/src/devices/grohtml/html-text.cpp b/src/devices/grohtml/html-text.cpp new file mode 100644 index 0000000..b07cbe7 --- /dev/null +++ b/src/devices/grohtml/html-text.cpp @@ -0,0 +1,1056 @@ +// -*- C++ -*- +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + * + * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.cpp + * + * html-text.cpp + * + * provide a troff like state machine interface which + * generates html text. + */ + +/* +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 <http://www.gnu.org/licenses/>. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" + +#if !defined(TRUE) +# define TRUE (1==1) +#endif +#if !defined(FALSE) +# define FALSE (1==0) +#endif + + +#include "html-text.h" + +html_text::html_text (simple_output *op, html_dialect d) : + stackptr(NULL), lastptr(NULL), out(op), dialect(d), + space_emitted(TRUE), current_indentation(-1), + pageoffset(-1), linelength(-1), blank_para(TRUE), + start_space(FALSE) +{ +} + +html_text::~html_text () +{ + flush_text(); +} + + +#if defined(DEBUGGING) +static int debugStack = FALSE; + + +/* + * turnDebug - flip the debugStack boolean and return the new value. + */ + +static int turnDebug (void) +{ + debugStack = 1-debugStack; + return debugStack; +} + +/* + * dump_stack_element - display an element of the html stack, p. + */ + +void html_text::dump_stack_element (tag_definition *p) +{ + fprintf(stderr, " | "); + switch (p->type) { + + case P_TAG: if (p->indent == NULL) { + fprintf(stderr, "<P %s>", (char *)p->arg1); break; + } else { + fprintf(stderr, "<P %s [TABLE]>", (char *)p->arg1); break; + } + case I_TAG: fprintf(stderr, "<I>"); break; + case B_TAG: fprintf(stderr, "<B>"); break; + case SUB_TAG: fprintf(stderr, "<SUB>"); break; + case SUP_TAG: fprintf(stderr, "<SUP>"); break; + case TT_TAG: fprintf(stderr, "<TT>"); break; + case PRE_TAG: if (p->indent == NULL) { + fprintf(stderr, "<PRE>"); break; + } else { + fprintf(stderr, "<PRE [TABLE]>"); break; + } + case SMALL_TAG: fprintf(stderr, "<SMALL>"); break; + case BIG_TAG: fprintf(stderr, "<BIG>"); break; + case BREAK_TAG: fprintf(stderr, "<BREAK>"); break; + case COLOR_TAG: { + if (p->col.is_default()) + fprintf(stderr, "<COLOR (default)>"); + else { + unsigned int r, g, b; + + p->col.get_rgb(&r, &g, &b); + fprintf(stderr, "<COLOR %x %x %x>", r/0x101, g/0x101, b/0x101); + } + break; + } + default: fprintf(stderr, "unknown tag"); + } + if (p->text_emitted) + fprintf(stderr, "[t] "); +} + +/* + * dump_stack - debugging function only. + */ + +void html_text::dump_stack (void) +{ + if (debugStack) { + tag_definition *p = stackptr; + + while (p != NULL) { + dump_stack_element(p); + p = p->next; + } + } + fprintf(stderr, "\n"); + fflush(stderr); +} +#else +void html_text::dump_stack (void) {} +#endif + + +/* + * end_tag - shuts down the tag. + */ + +void html_text::end_tag (tag_definition *t) +{ + switch (t->type) { + + case I_TAG: out->put_string("</i>"); break; + case B_TAG: out->put_string("</b>"); break; + case P_TAG: if (t->indent == NULL) { + out->put_string("</p>"); + } else { + delete t->indent; + t->indent = NULL; + out->put_string("</p>"); + } + out->enable_newlines(FALSE); + blank_para = TRUE; break; + case SUB_TAG: out->put_string("</sub>"); break; + case SUP_TAG: out->put_string("</sup>"); break; + case TT_TAG: out->put_string("</tt>"); break; + case PRE_TAG: out->put_string("</pre>"); out->enable_newlines(TRUE); + blank_para = TRUE; + if (t->indent != NULL) + delete t->indent; + t->indent = NULL; + break; + case SMALL_TAG: if (! is_in_pre ()) + out->put_string("</small>"); + break; + case BIG_TAG: if (! is_in_pre ()) + out->put_string("</big>"); + break; + case COLOR_TAG: if (! is_in_pre ()) + out->put_string("</font>"); + break; + + default: + error("unrecognised tag"); + } +} + +/* + * issue_tag - writes out an html tag with argument. + * space == 0 if no space is requested + * space == 1 if a space is requested + * space == 2 if tag should not have a space style + */ + +void html_text::issue_tag (const char *tagname, const char *arg, + int space) +{ + if ((arg == 0) || (strlen(arg) == 0)) + out->put_string(tagname); + else { + out->put_string(tagname); + out->put_string(" "); + out->put_string(arg); + } + if (space == TRUE) { + out->put_string(" style=\"margin-top: "); + out->put_string(STYLE_VERTICAL_SPACE); + out->put_string("\""); + } +#if 0 + if (space == TRUE || space == FALSE) + out->put_string(" valign=\"top\""); +#endif + out->put_string(">"); +} + +/* + * issue_color_begin - writes out an html color tag. + */ + +void html_text::issue_color_begin (color *c) +{ + char buf[(INT_HEXDIGITS * 3) + 1]; + unsigned int r, g, b; + + out->put_string("<font color=\"#"); + if (c->is_default()) + sprintf(buf, "000000"); + else { + c->get_rgb(&r, &g, &b); + // we have to scale 0..0xFFFF to 0..0xFF + sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101); + } + out->put_string(buf); + out->put_string("\">"); +} + +/* + * start_tag - starts a tag. + */ + +void html_text::start_tag (tag_definition *t) +{ + switch (t->type) { + + case I_TAG: issue_tag("<i", (char *)t->arg1); break; + case B_TAG: issue_tag("<b", (char *)t->arg1); break; + case P_TAG: if (t->indent != NULL) { + out->nl(); +#if defined(DEBUGGING) + out->simple_comment("INDENTATION"); +#endif + out->put_string("\n<p"); + t->indent->begin(start_space); + issue_tag("", (char *)t->arg1); + } else { + out->nl(); + issue_tag("\n<p", (char *)t->arg1, start_space); + } + + out->enable_newlines(TRUE); break; + case SUB_TAG: issue_tag("<sub", (char *)t->arg1); break; + case SUP_TAG: issue_tag("<sup", (char *)t->arg1); break; + case TT_TAG: issue_tag("<tt", (char *)t->arg1); break; + case PRE_TAG: out->enable_newlines(TRUE); + out->nl(); out->put_string("<pre"); + if (t->indent == NULL) + issue_tag("", (char *)t->arg1, start_space); + else { + t->indent->begin(start_space); + issue_tag("", (char *)t->arg1); + } + out->enable_newlines(FALSE); break; + case SMALL_TAG: if (! is_in_pre ()) + issue_tag("<small", (char *)t->arg1); + break; + case BIG_TAG: if (! is_in_pre ()) + issue_tag("<big", (char *)t->arg1); + break; + case BREAK_TAG: break; + case COLOR_TAG: if (! is_in_pre ()) + issue_color_begin(&t->col); + break; + + default: + error("unrecognised tag"); + } +} + +/* + * flush_text - flushes html tags which are outstanding on the html stack. + */ + +void html_text::flush_text (void) +{ + int notext=TRUE; + tag_definition *p=stackptr; + + while (stackptr != 0) { + notext = (notext && (! stackptr->text_emitted)); + if (! notext) { + end_tag(stackptr); + } + p = stackptr; + stackptr = stackptr->next; + delete p; + } + lastptr = NULL; +} + +/* + * is_present - returns TRUE if tag is already present on the stack. + */ + +int html_text::is_present (HTML_TAG t) +{ + tag_definition *p=stackptr; + + while (p != NULL) { + if (t == p->type) + return TRUE; + p = p->next; + } + return FALSE; +} + +/* + * uses_indent - returns TRUE if the current paragraph is using a + * html table to effect an indent. + */ + +int html_text::uses_indent (void) +{ + tag_definition *p = stackptr; + + while (p != NULL) { + if (p->indent != NULL) + return TRUE; + p = p->next; + } + return FALSE; +} + +extern void stop(); + +/* + * do_push - places, tag_definition, p, onto the stack + */ + +void html_text::do_push (tag_definition *p) +{ + HTML_TAG t = p->type; + +#if defined(DEBUGGING) + if (t == PRE_TAG) + stop(); + debugStack = TRUE; + fprintf(stderr, "\nentering do_push ("); + dump_stack_element(p); + fprintf(stderr, ")\n"); + dump_stack(); + fprintf(stderr, ")\n"); + fflush(stderr); +#endif + + /* + * if t is a P_TAG or PRE_TAG make sure it goes on the end of the stack. + */ + + if (((t == P_TAG) || (t == PRE_TAG)) && (lastptr != NULL)) { + /* + * store, p, at the end + */ + lastptr->next = p; + lastptr = p; + p->next = NULL; + } else { + p->next = stackptr; + if (stackptr == NULL) + lastptr = p; + stackptr = p; + } + +#if defined(DEBUGGING) + dump_stack(); + fprintf(stderr, "exiting do_push\n"); +#endif +} + +/* + * push_para - adds a new entry onto the html paragraph stack. + */ + +void html_text::push_para (HTML_TAG t, void *arg, html_indent *in) +{ + tag_definition *p= new tag_definition; + + p->type = t; + p->arg1 = arg; + p->text_emitted = FALSE; + p->indent = in; + + if (t == PRE_TAG && is_present(PRE_TAG)) + fatal("cannot have multiple PRE_TAGs"); + + do_push(p); +} + +void html_text::push_para (HTML_TAG t) +{ + push_para(t, (void *)"", NULL); +} + +void html_text::push_para (color *c) +{ + tag_definition *p = new tag_definition; + + p->type = COLOR_TAG; + p->arg1 = NULL; + p->col = *c; + p->text_emitted = FALSE; + p->indent = NULL; + + do_push(p); +} + +/* + * do_italic - changes to italic + */ + +void html_text::do_italic (void) +{ + if (! is_present(I_TAG)) + push_para(I_TAG); +} + +/* + * do_bold - changes to bold. + */ + +void html_text::do_bold (void) +{ + if (! is_present(B_TAG)) + push_para(B_TAG); +} + +/* + * do_tt - changes to teletype. + */ + +void html_text::do_tt (void) +{ + if ((! is_present(TT_TAG)) && (! is_present(PRE_TAG))) + push_para(TT_TAG); +} + +/* + * do_pre - changes to preformated text. + */ + +void html_text::do_pre (void) +{ + done_tt(); + if (is_present(P_TAG)) { + html_indent *i = remove_indent(P_TAG); + int space = retrieve_para_space(); + (void)done_para(); + if (! is_present(PRE_TAG)) + push_para(PRE_TAG, NULL, i); + start_space = space; + } else if (! is_present(PRE_TAG)) + push_para(PRE_TAG, NULL, NULL); + dump_stack(); +} + +/* + * is_in_pre - returns TRUE if we are currently within a preformatted + * <pre> block. + */ + +int html_text::is_in_pre (void) +{ + return is_present(PRE_TAG); +} + +/* + * do_color - initiates a new color tag. + */ + +void html_text::do_color (color *c) +{ + shutdown(COLOR_TAG); // shutdown a previous color tag, if present + push_para(c); +} + +/* + * done_color - shutdown an outstanding color tag, if it exists. + */ + +void html_text::done_color (void) +{ + shutdown(COLOR_TAG); +} + +/* + * shutdown - shuts down an html tag. + */ + +char *html_text::shutdown (HTML_TAG t) +{ + char *arg=NULL; + + if (is_present(t)) { + tag_definition *p =stackptr; + tag_definition *temp =NULL; + int notext =TRUE; + + dump_stack(); + while ((stackptr != NULL) && (stackptr->type != t)) { + notext = (notext && (! stackptr->text_emitted)); + if (! notext) { + end_tag(stackptr); + } + + /* + * pop tag + */ + p = stackptr; + stackptr = stackptr->next; + if (stackptr == NULL) + lastptr = NULL; + + /* + * push tag onto temp stack + */ + p->next = temp; + temp = p; + } + + /* + * and examine stackptr + */ + if ((stackptr != NULL) && (stackptr->type == t)) { + if (stackptr->text_emitted) { + end_tag(stackptr); + } + if (t == P_TAG) { + arg = (char *)stackptr->arg1; + } + p = stackptr; + stackptr = stackptr->next; + if (stackptr == NULL) + lastptr = NULL; + if (p->indent != NULL) + delete p->indent; + delete p; + } + + /* + * and restore unaffected tags + */ + while (temp != NULL) { + if (temp->type == COLOR_TAG) + push_para(&temp->col); + else + push_para(temp->type, temp->arg1, temp->indent); + p = temp; + temp = temp->next; + delete p; + } + } + return arg; +} + +/* + * done_bold - shuts downs a bold tag. + */ + +void html_text::done_bold (void) +{ + shutdown(B_TAG); +} + +/* + * done_italic - shuts downs an italic tag. + */ + +void html_text::done_italic (void) +{ + shutdown(I_TAG); +} + +/* + * done_sup - shuts downs a sup tag. + */ + +void html_text::done_sup (void) +{ + shutdown(SUP_TAG); +} + +/* + * done_sub - shuts downs a sub tag. + */ + +void html_text::done_sub (void) +{ + shutdown(SUB_TAG); +} + +/* + * done_tt - shuts downs a tt tag. + */ + +void html_text::done_tt (void) +{ + shutdown(TT_TAG); +} + +/* + * done_pre - shuts downs a pre tag. + */ + +void html_text::done_pre (void) +{ + shutdown(PRE_TAG); +} + +/* + * done_small - shuts downs a small tag. + */ + +void html_text::done_small (void) +{ + shutdown(SMALL_TAG); +} + +/* + * done_big - shuts downs a big tag. + */ + +void html_text::done_big (void) +{ + shutdown(BIG_TAG); +} + +/* + * check_emit_text - ensures that all previous tags have been emitted (in order) + * before the text is written. + */ + +void html_text::check_emit_text (tag_definition *t) +{ + if ((t != NULL) && (! t->text_emitted)) { + check_emit_text(t->next); + t->text_emitted = TRUE; + start_tag(t); + } +} + +/* + * do_emittext - tells the class that text was written during the current tag. + */ + +void html_text::do_emittext (const char *s, int length) +{ + if ((! is_present(P_TAG)) && (! is_present(PRE_TAG))) + do_para("", FALSE); + + if (is_present(BREAK_TAG)) { + int text = remove_break(); + check_emit_text(stackptr); + if (text) { + if (is_present(PRE_TAG)) + out->nl(); + else if (dialect == xhtml) + out->put_string("<br/>").nl(); + else + out->put_string("<br>").nl(); + } + } else + check_emit_text(stackptr); + + out->put_string(s, length); + space_emitted = FALSE; + blank_para = FALSE; +} + +/* + * do_para - starts a new paragraph + */ + +void html_text::do_para (const char *arg, html_indent *in, int space) +{ + if (! is_present(P_TAG)) { + if (is_present(PRE_TAG)) { + html_indent *i = remove_indent(PRE_TAG); + done_pre(); + if ((arg == NULL || (strcmp(arg, "") == 0)) && + (i == in || in == NULL)) + in = i; + else + delete i; + } + remove_sub_sup(); + push_para(P_TAG, (void *)arg, in); + start_space = space; + } +} + +void html_text::do_para (const char *arg, int space) +{ + do_para(arg, NULL, space); +} + +void html_text::do_para (simple_output *op, const char *arg1, + int indentation_value, int page_offset, + int line_length, int space) +{ + html_indent *ind; + + if (indentation_value == 0) + ind = NULL; + else + ind = new html_indent(op, indentation_value, page_offset, line_length); + do_para(arg1, ind, space); +} + +/* + * done_para - shuts down a paragraph tag. + */ + +char *html_text::done_para (void) +{ + char *result; + space_emitted = TRUE; + result = shutdown(P_TAG); + start_space = FALSE; + return result; +} + +/* + * remove_indent - returns the indent associated with, tag. + * The indent associated with tag is set to NULL. + */ + +html_indent *html_text::remove_indent (HTML_TAG tag) +{ + tag_definition *p=stackptr; + + while (p != NULL) { + if (tag == p->type) { + html_indent *i = p->indent; + p->indent = NULL; + return i; + } + p = p->next; + } + return NULL; +} + +/* + * remove_para_space - removes the leading space to a paragraph + * (effectively this trims off a leading '.sp' tag). + */ + +void html_text::remove_para_space (void) +{ + start_space = FALSE; +} + +/* + * do_space - issues an end of paragraph + */ + +void html_text::do_space (void) +{ + if (is_in_pre()) { + do_emittext("", 0); + out->force_nl(); + space_emitted = TRUE; + } else { + html_indent *i = remove_indent(P_TAG); + + do_para(done_para(), i, TRUE); + space_emitted = TRUE; + } +} + +/* + * do_break - issue a break tag. + */ + +void html_text::do_break (void) +{ + if (! is_present(PRE_TAG)) + if (emitted_text()) + if (! is_present(BREAK_TAG)) + push_para(BREAK_TAG); + + space_emitted = TRUE; +} + +/* + * do_newline - issue a newline providing that we are inside a <pre> tag. + */ + +void html_text::do_newline (void) +{ + if (is_present(PRE_TAG)) { + do_emittext("\n", 1); + space_emitted = TRUE; + } +} + +/* + * emitted_text - returns FALSE if white space has just been written. + */ + +int html_text::emitted_text (void) +{ + return !space_emitted; +} + +/* + * ever_emitted_text - returns TRUE if we have ever emitted text in this + * paragraph. + */ + +int html_text::ever_emitted_text (void) +{ + return !blank_para; +} + +/* + * starts_with_space - returns TRUE if we started this paragraph with a .sp + */ + +int html_text::starts_with_space (void) +{ + return start_space; +} + +/* + * retrieve_para_space - returns TRUE, if the paragraph starts with + * a space and text has not yet been emitted. + * If TRUE is returned, then the, start_space, + * variable is set to FALSE. + */ + +int html_text::retrieve_para_space (void) +{ + if (start_space && blank_para) { + start_space = FALSE; + return TRUE; + } + else + return FALSE; +} + +/* + * emit_space - writes a space providing that text was written beforehand. + */ + +void html_text::emit_space (void) +{ + if (is_present(PRE_TAG)) + do_emittext(" ", 1); + else + out->space_or_newline(); + + space_emitted = TRUE; +} + +/* + * remove_def - removes a definition, t, from the stack. + */ + +void html_text::remove_def (tag_definition *t) +{ + tag_definition *p = stackptr; + tag_definition *l = 0; + + while ((p != 0) && (p != t)) { + l = p; + p = p->next; + } + if ((p != 0) && (p == t)) { + if (p == stackptr) { + stackptr = stackptr->next; + if (stackptr == NULL) + lastptr = NULL; + } else if (l == 0) { + error("stack list pointers are wrong"); + } else { + l->next = p->next; + if (l->next == NULL) + lastptr = l; + } + delete p; + } +} + +/* + * remove_tag - removes a tag from the stack. + */ + +void html_text::remove_tag (HTML_TAG tag) +{ + tag_definition *p = stackptr; + + while ((p != 0) && (p->type != tag)) { + p = p->next; + } + if ((p != 0) && (p->type == tag)) + remove_def(p); +} + +/* + * remove_sub_sup - removes a sub or sup tag, should either exist + * on the stack. + */ + +void html_text::remove_sub_sup (void) +{ + if (is_present(SUB_TAG)) { + remove_tag(SUB_TAG); + } + if (is_present(SUP_TAG)) { + remove_tag(SUP_TAG); + } + if (is_present(PRE_TAG)) { + remove_tag(PRE_TAG); + } +} + +/* + * remove_break - break tags are not balanced thus remove it once it has been emitted. + * It returns TRUE if text was emitted before the <br> was issued. + */ + +int html_text::remove_break (void) +{ + tag_definition *p = stackptr; + tag_definition *l = 0; + tag_definition *q = 0; + + while ((p != 0) && (p->type != BREAK_TAG)) { + l = p; + p = p->next; + } + if ((p != 0) && (p->type == BREAK_TAG)) { + if (p == stackptr) { + stackptr = stackptr->next; + if (stackptr == NULL) + lastptr = NULL; + q = stackptr; + } else if (l == 0) + error("stack list pointers are wrong"); + else { + l->next = p->next; + q = p->next; + if (l->next == NULL) + lastptr = l; + } + delete p; + } + /* + * now determine whether text was issued before <br> + */ + while (q != 0) { + if (q->text_emitted) + return TRUE; + else + q = q->next; + } + return FALSE; +} + +/* + * remove_para_align - removes a paragraph which has a text + * argument. If the paragraph has no text + * argument then it is left alone. + */ + +void html_text::remove_para_align (void) +{ + if (is_present(P_TAG)) { + tag_definition *p=stackptr; + + while (p != NULL) { + if (p->type == P_TAG && p->arg1 != NULL) { + html_indent *i = remove_indent(P_TAG); + int space = retrieve_para_space(); + done_para(); + do_para("", i, space); + return; + } + p = p->next; + } + } +} + +/* + * get_alignment - returns the alignment for the paragraph. + * If no alignment was given then we return "". + */ + +char *html_text::get_alignment (void) +{ + if (is_present(P_TAG)) { + tag_definition *p=stackptr; + + while (p != NULL) { + if (p->type == P_TAG && p->arg1 != NULL) + return (char *)p->arg1; + p = p->next; + } + } + return (char *)""; +} + +/* + * do_small - potentially inserts a <small> tag into the html stream. + * However we check for a <big> tag, if present then we terminate it. + * Otherwise a <small> tag is inserted. + */ + +void html_text::do_small (void) +{ + if (is_present(BIG_TAG)) + done_big(); + else + push_para(SMALL_TAG); +} + +/* + * do_big - is the mirror image of do_small. + */ + +void html_text::do_big (void) +{ + if (is_present(SMALL_TAG)) + done_small(); + else + push_para(BIG_TAG); +} + +/* + * do_sup - save a superscript tag on the stack of tags. + */ + +void html_text::do_sup (void) +{ + push_para(SUP_TAG); +} + +/* + * do_sub - save a subscript tag on the stack of tags. + */ + +void html_text::do_sub (void) +{ + push_para(SUB_TAG); +} diff --git a/src/devices/grohtml/html-text.h b/src/devices/grohtml/html-text.h new file mode 100644 index 0000000..ee58601 --- /dev/null +++ b/src/devices/grohtml/html-text.h @@ -0,0 +1,138 @@ +// -*- C++ -*- +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + * + * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.h + * + * html-text.h + * + * provides a state machine interface which generates html text. + */ + +/* +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 <http://www.gnu.org/licenses/>. */ + +#include "html.h" +#include "html-table.h" + +#define STYLE_VERTICAL_SPACE "1em" + +/* + * supported html dialects. + */ + +typedef enum {xhtml, html4} html_dialect; + +/* + * html tags + */ + +typedef enum {I_TAG, B_TAG, P_TAG, SUB_TAG, SUP_TAG, TT_TAG, + PRE_TAG, SMALL_TAG, BIG_TAG, BREAK_TAG, + COLOR_TAG} HTML_TAG; + +typedef struct tag_definition { + HTML_TAG type; + void *arg1; + int text_emitted; + color col; + html_indent *indent; + tag_definition *next; +} tag_definition ; + +/* + * the state of the current paragraph. + * It allows post-html.cpp to request font changes, paragraph start/end + * and emits balanced tags with a small amount of peephole optimization. + */ + +class html_text { +public: + html_text (simple_output *op, html_dialect d); + ~html_text (void); + void flush_text (void); + void do_emittext (const char *s, int length); + void do_italic (void); + void do_bold (void); + void do_roman (void); + void do_tt (void); + void do_pre (void); + void do_small (void); + void do_big (void); + void do_para (const char *arg, int space); // used for no indentation + void do_para (simple_output *op, const char *arg1, + int indentation, int pageoffset, int linelength, + int space); + void do_sup (void); + void do_sub (void); + void do_space (void); + void do_break (void); + void do_newline (void); + void do_table (const char *arg); + void done_bold (void); + void done_italic (void); + char *done_para (void); + void done_sup (void); + void done_sub (void); + void done_tt (void); + void done_pre (void); + void done_small (void); + void done_big (void); + void do_color (color *c); + void done_color (void); + int emitted_text (void); + int ever_emitted_text (void); + int starts_with_space (void); + int retrieve_para_space (void); + void emit_space (void); + int is_in_pre (void); + int uses_indent (void); + void remove_tag (HTML_TAG tag); + void remove_sub_sup (void); + void remove_para_align (void); + void remove_para_space (void); + char *get_alignment (void); + +private: + tag_definition *stackptr; /* the current paragraph state */ + tag_definition *lastptr; /* the end of the stack */ + simple_output *out; + html_dialect dialect; /* which dialect of html? */ + int space_emitted; /* just emitted a space? */ + int current_indentation; /* current .in value */ + int pageoffset; /* .po value */ + int linelength; /* current line length */ + int blank_para; /* have we ever written text? */ + int start_space; /* does para start with a .sp */ + html_indent *indent; /* our indent class */ + + int is_present (HTML_TAG t); + void end_tag (tag_definition *t); + void start_tag (tag_definition *t); + void do_para (const char *arg, html_indent *in, int space); + void push_para (HTML_TAG t); + void push_para (HTML_TAG t, void *arg, html_indent *in); + void push_para (color *c); + void do_push (tag_definition *p); + char *shutdown (HTML_TAG t); + void check_emit_text (tag_definition *t); + int remove_break (void); + void issue_tag (const char *tagname, const char *arg, int space=2); + void issue_color_begin (color *c); + void remove_def (tag_definition *t); + html_indent *remove_indent (HTML_TAG tag); + void dump_stack_element (tag_definition *p); + void dump_stack (void); +}; diff --git a/src/devices/grohtml/html.h b/src/devices/grohtml/html.h new file mode 100644 index 0000000..4828646 --- /dev/null +++ b/src/devices/grohtml/html.h @@ -0,0 +1,97 @@ +// -*- C++ -*- +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +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 <http://www.gnu.org/licenses/>. */ + +#if !defined(HTML_H) +# define HTML_H + +const int INT_HEXDIGITS = 16; // enough for 64-bit ints + +/* + * class and structure needed to buffer words + */ + +struct word { + char *s; + word *next; + + word (const char *w, int n); + ~word (); +}; + +class word_list { +public: + word_list (); + int flush (FILE *f); + void add_word (const char *s, int n); + int get_length (void); + +private: + int length; + word *head; + word *tail; +}; + +class simple_output { +public: + simple_output(FILE *, int max_line_length); + simple_output &put_string(const char *, int); + simple_output &put_string(const char *s); + simple_output &put_string(const string &s); + simple_output &put_troffps_char (const char *s); + simple_output &put_translated_string(const char *s); + simple_output &put_number(int); + simple_output &put_float(double); + simple_output &put_symbol(const char *); + simple_output &put_literal_symbol(const char *); + simple_output &set_fixed_point(int); + simple_output &simple_comment(const char *); + simple_output &begin_comment(const char *); + simple_output &comment_arg(const char *); + simple_output &end_comment(); + simple_output &set_file(FILE *); + simple_output &include_file(FILE *); + simple_output ©_file(FILE *); + simple_output &end_line(); + simple_output &put_raw_char(char); + simple_output &special(const char *); + simple_output &enable_newlines(int); + simple_output &check_newline(int n); + simple_output &nl(void); + simple_output &force_nl(void); + simple_output &space_or_newline (void); + simple_output &begin_tag (void); + FILE *get_file(); +private: + FILE *fp; + int max_line_length; // not including newline + int col; + int fixed_point; + int newlines; // can we issue newlines automatically? + word_list last_word; + + void flush_last_word (void); + int check_space (const char *s, int n); +}; + +inline FILE *simple_output::get_file() +{ + return fp; +} + +#endif diff --git a/src/devices/grohtml/output.cpp b/src/devices/grohtml/output.cpp new file mode 100644 index 0000000..0ffeb58 --- /dev/null +++ b/src/devices/grohtml/output.cpp @@ -0,0 +1,363 @@ +// -*- C++ -*- +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + * + * Gaius Mulley (gaius@glam.ac.uk) wrote output.cpp + * but it owes a huge amount of ideas and raw code from + * James Clark (jjc@jclark.com) grops/ps.cpp. + * + * output.cpp + * + * provide the simple low level output routines needed by html.cpp + */ + +/* +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 <http://www.gnu.org/licenses/>. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" + +#include <time.h> +#include "html.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if !defined(TRUE) +# define TRUE (1==1) +#endif +#if !defined(FALSE) +# define FALSE (1==0) +#endif + + +#if defined(DEBUGGING) +# define FPUTC(X,Y) do { fputc((X),(Y)); fputc((X), stderr); fflush(stderr); } while (0) +# define FPUTS(X,Y) do { fputs((X),(Y)); fputs((X), stderr); fflush(stderr); } while (0) +# define PUTC(X,Y) do { putc((X),(Y)); putc((X), stderr); fflush(stderr); } while (0) +#else +# define FPUTC(X,Y) do { fputc((X),(Y)); } while (0) +# define FPUTS(X,Y) do { fputs((X),(Y)); } while (0) +# define PUTC(X,Y) do { putc((X),(Y)); } while (0) +#endif + + +/* + * word - initialise a word and set next to NULL + */ + +word::word (const char *w, int n) + : next(0) +{ + s = new char[n+1]; + strncpy(s, w, n); + s[n] = (char)0; +} + +/* + * destroy word and the string copy. + */ + +word::~word () +{ + delete[] s; +} + +/* + * word_list - create an empty word list. + */ + +word_list::word_list () + : length(0), head(0), tail(0) +{ +} + +/* + * flush - flush a word list to a FILE, f, and return the + * length of the buffered string. + */ + +int word_list::flush (FILE *f) +{ + word *t; + int len=length; + + while (head != 0) { + t = head; + head = head->next; + FPUTS(t->s, f); + delete t; + } + head = 0; + tail = 0; + length = 0; +#if defined(DEBUGGING) + fflush(f); // just for testing +#endif + return( len ); +} + +/* + * add_word - adds a word to the outstanding word list. + */ + +void word_list::add_word (const char *s, int n) +{ + if (head == 0) { + head = new word(s, n); + tail = head; + } else { + tail->next = new word(s, n); + tail = tail->next; + } + length += n; +} + +/* + * get_length - returns the number of characters buffered + */ + +int word_list::get_length (void) +{ + return( length ); +} + +/* + * the classes and methods for simple_output manipulation + */ + +simple_output::simple_output(FILE *f, int n) +: fp(f), max_line_length(n), col(0), fixed_point(0), newlines(0) +{ +} + +simple_output &simple_output::set_file(FILE *f) +{ + if (fp) + fflush(fp); + fp = f; + return *this; +} + +simple_output &simple_output::copy_file(FILE *infp) +{ + int c; + while ((c = getc(infp)) != EOF) + PUTC(c, fp); + return *this; +} + +simple_output &simple_output::end_line() +{ + flush_last_word(); + if (col != 0) { + PUTC('\n', fp); + col = 0; + } + return *this; +} + +simple_output &simple_output::special(const char *) +{ + return *this; +} + +simple_output &simple_output::simple_comment(const char *s) +{ + flush_last_word(); + if (col != 0) + PUTC('\n', fp); + FPUTS("<!-- ", fp); + FPUTS(s, fp); + FPUTS(" -->\n", fp); + col = 0; + return *this; +} + +simple_output &simple_output::begin_comment(const char *s) +{ + flush_last_word(); + if (col != 0) + PUTC('\n', fp); + col = 0; + put_string("<!--"); + space_or_newline(); + last_word.add_word(s, strlen(s)); + return *this; +} + +simple_output &simple_output::end_comment() +{ + flush_last_word(); + space_or_newline(); + put_string("-->").nl(); + return *this; +} + +/* + * check_newline - checks to see whether we are able to issue + * a newline and that one is needed. + */ + +simple_output &simple_output::check_newline(int n) +{ + if ((col + n + last_word.get_length() + 1 > max_line_length) && (newlines)) { + FPUTC('\n', fp); + col = last_word.flush(fp); + } + return *this; +} + +/* + * space_or_newline - will emit a newline or a space later on + * depending upon the current column. + */ + +simple_output &simple_output::space_or_newline (void) +{ + if ((col + last_word.get_length() + 1 > max_line_length) && (newlines)) { + FPUTC('\n', fp); + if (last_word.get_length() > 0) { + col = last_word.flush(fp); + } else { + col = 0; + } + } else { + if (last_word.get_length() != 0) { + if (col > 0) { + FPUTC(' ', fp); + col++; + } + col += last_word.flush(fp); + } + } + return *this; +} + +/* + * force_nl - forces a newline. + */ + +simple_output &simple_output::force_nl (void) +{ + space_or_newline(); + col += last_word.flush(fp); + FPUTC('\n', fp); + col = 0; + return *this ; +} + +/* + * nl - writes a newline providing that we + * are not in the first column. + */ + +simple_output &simple_output::nl (void) +{ + space_or_newline(); + col += last_word.flush(fp); + FPUTC('\n', fp); + col = 0; + return *this ; +} + +simple_output &simple_output::set_fixed_point(int n) +{ + assert(n >= 0 && n <= 10); + fixed_point = n; + return *this; +} + +simple_output &simple_output::put_raw_char(char c) +{ + col += last_word.flush(fp); + PUTC(c, fp); + col++; + return *this; +} + +simple_output &simple_output::put_string(const char *s, int n) +{ + last_word.add_word(s, n); + return *this; +} + +simple_output &simple_output::put_string(const char *s) +{ + last_word.add_word(s, strlen(s)); + return *this; +} + +simple_output &simple_output::put_string(const string &s) +{ + last_word.add_word(s.contents(), s.length()); + return *this; +} + +simple_output &simple_output::put_number(int n) +{ + char buf[1 + INT_DIGITS + 1]; + sprintf(buf, "%d", n); + put_string(buf); + return *this; +} + +simple_output &simple_output::put_float(double d) +{ + char buf[128]; + + sprintf(buf, "%.4f", d); + put_string(buf); + return *this; +} + +simple_output &simple_output::enable_newlines (int auto_newlines) +{ + check_newline(0); + newlines = auto_newlines; + check_newline(0); + return *this; +} + +/* + * flush_last_word - flushes the last word and adjusts the + * col position. It will insert a newline + * before the last word if allowed and if + * necessary. + */ + +void simple_output::flush_last_word (void) +{ + int len=last_word.get_length(); + + if (len > 0) { + if (newlines) { + if (col + len + 1 > max_line_length) { + FPUTS("\n", fp); + col = 0; + } else { + FPUTS(" ", fp); + col++; + } + len += last_word.flush(fp); + } else { + FPUTS(" ", fp); + col++; + col += last_word.flush(fp); + } + } +} diff --git a/src/devices/grohtml/post-html.cpp b/src/devices/grohtml/post-html.cpp new file mode 100644 index 0000000..4e02b5c --- /dev/null +++ b/src/devices/grohtml/post-html.cpp @@ -0,0 +1,5684 @@ +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + * + * Gaius Mulley (gaius@glam.ac.uk) wrote post-html.cpp + * but it owes a huge amount of ideas and raw code from + * James Clark (jjc@jclark.com) grops/ps.cpp. + */ + +/* +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 <http://www.gnu.org/licenses/>. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" +#include "html.h" +#include "html-text.h" +#include "html-table.h" +#include "curtime.h" + +#include <time.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdio.h> +#include <fcntl.h> +#include <string.h> + +extern "C" const char *Version_string; + +#if !defined(TRUE) +# define TRUE (1==1) +#endif +#if !defined(FALSE) +# define FALSE (1==0) +#endif + +#define MAX_LINE_LENGTH 60 /* maximum characters we want in a line */ +#define SIZE_INCREMENT 2 /* font size increment <big> = +2 */ +#define CENTER_TOLERANCE 2 /* how many pixels off center do we allow */ +#define ANCHOR_TEMPLATE "heading" /* if simple anchor is set we use this */ +#define UNICODE_DESC_START 0x80 /* all character entities above this are */ + /* either encoded by their glyph names or if */ + /* there is no name then we use &#nnn; */ +typedef enum {CENTERED, LEFT, RIGHT, INLINE} TAG_ALIGNMENT; +typedef enum {col_tag, tab_tag, tab0_tag, none} colType; + +#undef DEBUG_TABLES +// #define DEBUG_TABLES + +/* + * prototypes + */ + +const char *get_html_translation (font *f, const string &name); +static const char *get_html_entity(unsigned int code); +int char_translate_to_html (font *f, char *buf, int buflen, unsigned char ch, int b, int and_single); + + +static int auto_links = TRUE; /* by default we enable automatic links at */ + /* top of the document. */ +static int auto_rule = TRUE; /* by default we enable an automatic rule */ + /* at the top and bottom of the document */ +static int simple_anchors = FALSE; /* default to anchors with heading text */ +static int manufacture_headings = FALSE; /* default is to use the Hn html headings, */ + /* rather than manufacture our own. */ +static int do_write_creator_comment = TRUE; /* write Creator HTML comment */ +static int do_write_date_comment = TRUE; /* write CreationDate HTML comment */ +static color *default_background = 0; /* has user requested initial bg color? */ +static string job_name; /* if set then the output is split into */ + /* multiple files with 'job_name'-%d.html */ +static int multiple_files = FALSE; /* must we the output be divided into */ + /* multiple html files, one for each */ + /* heading? */ +static int base_point_size = 0; /* which troff font size maps onto html */ + /* size 3? */ +static int split_level = 2; /* what heading level to split at? */ +static string head_info; /* user supplied information to be placed */ + /* into <head> </head> */ +static int valid_flag = FALSE; /* has user requested a valid flag at the */ + /* end of each page? */ +static int groff_sig = FALSE; /* "This document was produced using" */ +html_dialect dialect = html4; /* which html dialect should grohtml output */ + + +/* + * start with a few favorites + */ + +void stop () {} + +static int min (int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static int max (int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +/* + * is_intersection - returns TRUE if range a1..a2 intersects with + * b1..b2 + */ + +static int is_intersection (int a1, int a2, int b1, int b2) +{ + // easier to prove NOT outside limits + return ! ((a1 > b2) || (a2 < b1)); +} + +/* + * is_digit - returns TRUE if character, ch, is a digit. + */ + +static int is_digit (char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + +/* + * the classes and methods for maintaining a list of files. + */ + +struct file { + FILE *fp; + file *next; + int new_output_file; + int require_links; + string output_file_name; + + file (FILE *f); +}; + +/* + * file - initialize all fields to null pointers + */ + +file::file (FILE *f) + : fp(f), next(0), new_output_file(FALSE), + require_links(FALSE), output_file_name("") +{ +} + +class files { +public: + files (); + FILE *get_file (void); + void start_of_list (void); + void move_next (void); + void add_new_file (FILE *f); + void set_file_name (string name); + void set_links_required (void); + int are_links_required (void); + int is_new_output_file (void); + string file_name (void); + string next_file_name (void); +private: + file *head; + file *tail; + file *ptr; +}; + +/* + * files - create an empty list of files. + */ + +files::files () + : head(0), tail(0), ptr(0) +{ +} + +/* + * get_file - returns the FILE associated with ptr. + */ + +FILE *files::get_file (void) +{ + if (ptr) + return ptr->fp; + else + return 0; +} + +/* + * start_of_list - reset the ptr to the start of the list. + */ + +void files::start_of_list (void) +{ + ptr = head; +} + +/* + * move_next - moves the ptr to the next element on the list. + */ + +void files::move_next (void) +{ + if (ptr != 0) + ptr = ptr->next; +} + +/* + * add_new_file - adds a new file, f, to the list. + */ + +void files::add_new_file (FILE *f) +{ + if (0 /* nullptr */ == head) { + head = new file(f); + tail = head; + } else { + tail->next = new file(f); + tail = tail->next; + } + ptr = tail; +} + +/* + * set_file_name - sets the final file name to contain the html + * data to name. + */ + +void files::set_file_name (string name) +{ + if (ptr != 0) { + ptr->output_file_name = name; + ptr->new_output_file = TRUE; + } +} + +/* + * set_links_required - issue links when processing this component + * of the file. + */ + +void files::set_links_required (void) +{ + if (ptr != 0) + ptr->require_links = TRUE; +} + +/* + * are_links_required - returns TRUE if this section of the file + * requires that links should be issued. + */ + +int files::are_links_required (void) +{ + if (ptr != 0) + return ptr->require_links; + return FALSE; +} + +/* + * is_new_output_file - returns TRUE if this component of the file + * is the start of a new output file. + */ + +int files::is_new_output_file (void) +{ + if (ptr != 0) + return ptr->new_output_file; + return FALSE; +} + +/* + * file_name - returns the name of the file. + */ + +string files::file_name (void) +{ + if (ptr != 0) + return ptr->output_file_name; + return string(""); +} + +/* + * next_file_name - returns the name of the next file. + */ + +string files::next_file_name (void) +{ + if (ptr != 0 && ptr->next != 0) + return ptr->next->output_file_name; + return string(""); +} + +/* + * the class and methods for styles + */ + +struct style { + font *f; + int point_size; + int font_no; + int height; + int slant; + color col; + style (); + style (font *, int, int, int, int, color); + int operator == (const style &) const; + int operator != (const style &) const; +}; + +style::style() + : f(0), point_size(-1) +{ +} + +style::style(font *p, int sz, int h, int sl, int no, color c) + : f(p), point_size(sz), font_no(no), height(h), slant(sl), col(c) +{ +} + +int style::operator==(const style &s) const +{ + return (f == s.f && point_size == s.point_size + && height == s.height && slant == s.slant && col == s.col); +} + +int style::operator!=(const style &s) const +{ + return !(*this == s); +} + +/* + * the class and methods for retaining ascii text + */ + +struct char_block { + enum { SIZE = 256 }; + char *buffer; + int used; + char_block *next; + + char_block(); + char_block(int length); + ~char_block(); +}; + +char_block::char_block() +: buffer(0), used(0), next(0) +{ +} + +char_block::char_block(int length) +: used(0), next(0) +{ + buffer = new char[max(length, char_block::SIZE)]; + if (0 /* nullptr */ == buffer) + fatal("out of memory error"); +} + +char_block::~char_block() +{ + if (buffer != 0) + delete[] buffer; +} + +class char_buffer { +public: + char_buffer(); + ~char_buffer(); + char *add_string(const char *, unsigned int); + char *add_string(const string &); +private: + char_block *head; + char_block *tail; +}; + +char_buffer::char_buffer() +: head(0), tail(0) +{ +} + +char_buffer::~char_buffer() +{ + while (head != 0) { + char_block *temp = head; + head = head->next; + delete temp; + } +} + +char *char_buffer::add_string (const char *s, unsigned int length) +{ + int i = 0; + unsigned int old_used; + + if (0 /* nullptr */ == s|| length == 0) + return 0; + + if (0 /* nullptr */ == tail) { + tail = new char_block(length+1); + head = tail; + } else { + if (tail->used + length+1 > char_block::SIZE) { + tail->next = new char_block(length+1); + tail = tail->next; + } + } + + old_used = tail->used; + do { + tail->buffer[tail->used] = s[i]; + tail->used++; + i++; + length--; + } while (length>0); + + // add terminating nul character + + tail->buffer[tail->used] = '\0'; + tail->used++; + + // and return start of new string + + return &tail->buffer[old_used]; +} + +char *char_buffer::add_string (const string &s) +{ + return add_string(s.contents(), s.length()); +} + +/* + * the classes and methods for maintaining glyph positions. + */ + +class text_glob { +public: + void text_glob_html (style *s, char *str, int length, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + void text_glob_special (style *s, char *str, int length, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + void text_glob_line (style *s, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal, + int thickness); + void text_glob_auto_image(style *s, char *str, int length, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + void text_glob_tag (style *s, char *str, int length, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + + text_glob (void); + ~text_glob (void); + int is_a_line (void); + int is_a_tag (void); + int is_eol (void); + int is_auto_img (void); + int is_br (void); + int is_in (void); + int is_po (void); + int is_ti (void); + int is_ll (void); + int is_ce (void); + int is_tl (void); + int is_eo_tl (void); + int is_eol_ce (void); + int is_col (void); + int is_tab (void); + int is_tab0 (void); + int is_ta (void); + int is_tab_ts (void); + int is_tab_te (void); + int is_nf (void); + int is_fi (void); + int is_eo_h (void); + int get_arg (void); + int get_tab_args (char *align); + + void remember_table (html_table *t); + html_table *get_table (void); + + style text_style; + const char *text_string; + unsigned int text_length; + int minv, minh, maxv, maxh; + int is_tag; // is this a .br, .sp, .tl etc + int is_img_auto; // image created by eqn delim + int is_special; // text has come via 'x X html:' + int is_line; // is the command a <line>? + int thickness; // the thickness of a line + html_table *tab; // table description + +private: + text_glob (style *s, const char *str, int length, + int min_vertical , int min_horizontal, + int max_vertical , int max_horizontal, + bool is_troff_command, + bool is_auto_image, bool is_special_command, + bool is_a_line , int thickness); +}; + +text_glob::text_glob (style *s, const char *str, int length, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal, + bool is_troff_command, + bool is_auto_image, bool is_special_command, + bool is_a_line_flag, int line_thickness) + : text_style(*s), text_string(str), text_length(length), + minv(min_vertical), minh(min_horizontal), maxv(max_vertical), + maxh(max_horizontal), is_tag(is_troff_command), + is_img_auto(is_auto_image), is_special(is_special_command), + is_line(is_a_line_flag), thickness(line_thickness), tab(0) +{ +} + +text_glob::text_glob () + : text_string(0), text_length(0), minv(-1), minh(-1), maxv(-1), + maxh(-1), is_tag(FALSE), is_special(FALSE), is_line(FALSE), + thickness(0), tab(0) +{ +} + +text_glob::~text_glob () +{ + if (tab != 0) + delete tab; +} + +/* + * text_glob_html - used to place html text into the glob buffer. + */ + +void text_glob::text_glob_html (style *s, char *str, int length, + int min_vertical , int min_horizontal, + int max_vertical , int max_horizontal) +{ + text_glob *g = new text_glob(s, str, length, + min_vertical, min_horizontal, + max_vertical, max_horizontal, + FALSE, FALSE, FALSE, FALSE, 0); + *this = *g; + delete g; +} + +/* + * text_glob_html - used to place html specials into the glob buffer. + * This text is essentially html commands coming + * through from the macro sets, with special + * designated sequences of characters translated into + * html. See add_and_encode. + */ + +void text_glob::text_glob_special (style *s, char *str, int length, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal) +{ + text_glob *g = new text_glob(s, str, length, + min_vertical, min_horizontal, + max_vertical, max_horizontal, + FALSE, FALSE, TRUE, FALSE, 0); + *this = *g; + delete g; +} + +/* + * text_glob_line - record horizontal draw line commands. + */ + +void text_glob::text_glob_line (style *s, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal, + int thickness_value) +{ + text_glob *g = new text_glob(s, "", 0, + min_vertical, min_horizontal, + max_vertical, max_horizontal, + FALSE, FALSE, FALSE, TRUE, + thickness_value); + *this = *g; + delete g; +} + +/* + * text_glob_auto_image - record the presence of a .auto-image tag + * command. Used to mark that an image has been + * created automatically by a preprocessor and + * (pre-grohtml/troff) combination. Under some + * circumstances images may not be created. + * (consider .EQ + * delim $$ + * .EN + * .TS + * tab(!), center; + * l!l. + * $1 over x$!recripical of x + * .TE + * the first auto-image marker is created via + * .EQ/.EN pair and no image is created. The + * second auto-image marker occurs at $1 over + * x$ Currently this image will not be created + * as the whole of the table is created as an + * image. (Once html tables are handled by + * grohtml this will change. Shortly this will + * be the case). + */ + +void text_glob::text_glob_auto_image(style *s, char *str, int length, + int min_vertical, + int min_horizontal, + int max_vertical, + int max_horizontal) +{ + text_glob *g = new text_glob(s, str, length, + min_vertical, min_horizontal, + max_vertical, max_horizontal, + TRUE, TRUE, FALSE, FALSE, 0); + *this = *g; + delete g; +} + +/* + * text_glob_tag - records a troff tag. + */ + +void text_glob::text_glob_tag (style *s, char *str, int length, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal) +{ + text_glob *g = new text_glob(s, str, length, + min_vertical, min_horizontal, + max_vertical, max_horizontal, + TRUE, FALSE, FALSE, FALSE, 0); + *this = *g; + delete g; +} + +/* + * is_a_line - returns TRUE if glob should be converted into an <hr> + */ + +int text_glob::is_a_line (void) +{ + return is_line; +} + +/* + * is_a_tag - returns TRUE if glob contains a troff directive. + */ + +int text_glob::is_a_tag (void) +{ + return is_tag; +} + +/* + * is_eol - returns TRUE if glob contains the tag eol + */ + +int text_glob::is_eol (void) +{ + return is_tag && (strcmp(text_string, "devtag:.eol") == 0); +} + +/* + * is_eol_ce - returns TRUE if glob contains the tag eol.ce + */ + +int text_glob::is_eol_ce (void) +{ + return is_tag && (strcmp(text_string, "devtag:eol.ce") == 0); +} + +/* + * is_tl - returns TRUE if glob contains the tag .tl + */ + +int text_glob::is_tl (void) +{ + return is_tag && (strcmp(text_string, "devtag:.tl") == 0); +} + +/* + * is_eo_tl - returns TRUE if glob contains the tag eo.tl + */ + +int text_glob::is_eo_tl (void) +{ + return is_tag && (strcmp(text_string, "devtag:.eo.tl") == 0); +} + +/* + * is_nf - returns TRUE if glob contains the tag .fi 0 + */ + +int text_glob::is_nf (void) +{ + return is_tag && (strncmp(text_string, "devtag:.fi", + strlen("devtag:.fi")) == 0) && + (get_arg() == 0); +} + +/* + * is_fi - returns TRUE if glob contains the tag .fi 1 + */ + +int text_glob::is_fi (void) +{ + return (is_tag && (strncmp(text_string, "devtag:.fi", + strlen("devtag:.fi")) == 0) && + (get_arg() == 1)); +} + +/* + * is_eo_h - returns TRUE if glob contains the tag .eo.h + */ + +int text_glob::is_eo_h (void) +{ + return is_tag && (strcmp(text_string, "devtag:.eo.h") == 0); +} + +/* + * is_ce - returns TRUE if glob contains the tag .ce + */ + +int text_glob::is_ce (void) +{ + return is_tag && (strncmp(text_string, "devtag:.ce", + strlen("devtag:.ce")) == 0); +} + +/* + * is_in - returns TRUE if glob contains the tag .in + */ + +int text_glob::is_in (void) +{ + return is_tag && (strncmp(text_string, "devtag:.in ", + strlen("devtag:.in ")) == 0); +} + +/* + * is_po - returns TRUE if glob contains the tag .po + */ + +int text_glob::is_po (void) +{ + return is_tag && (strncmp(text_string, "devtag:.po ", + strlen("devtag:.po ")) == 0); +} + +/* + * is_ti - returns TRUE if glob contains the tag .ti + */ + +int text_glob::is_ti (void) +{ + return is_tag && (strncmp(text_string, "devtag:.ti ", + strlen("devtag:.ti ")) == 0); +} + +/* + * is_ll - returns TRUE if glob contains the tag .ll + */ + +int text_glob::is_ll (void) +{ + return is_tag && (strncmp(text_string, "devtag:.ll ", + strlen("devtag:.ll ")) == 0); +} + +/* + * is_col - returns TRUE if glob contains the tag .col + */ + +int text_glob::is_col (void) +{ + return is_tag && (strncmp(text_string, "devtag:.col", + strlen("devtag:.col")) == 0); +} + +/* + * is_tab_ts - returns TRUE if glob contains the tag .tab_ts + */ + +int text_glob::is_tab_ts (void) +{ + return is_tag && (strcmp(text_string, "devtag:.tab-ts") == 0); +} + +/* + * is_tab_te - returns TRUE if glob contains the tag .tab_te + */ + +int text_glob::is_tab_te (void) +{ + return is_tag && (strcmp(text_string, "devtag:.tab-te") == 0); +} + +/* + * is_ta - returns TRUE if glob contains the tag .ta + */ + +int text_glob::is_ta (void) +{ + return is_tag && (strncmp(text_string, "devtag:.ta ", + strlen("devtag:.ta ")) == 0); +} + +/* + * is_tab - returns TRUE if glob contains the tag tab + */ + +int text_glob::is_tab (void) +{ + return is_tag && (strncmp(text_string, "devtag:tab ", + strlen("devtag:tab ")) == 0); +} + +/* + * is_tab0 - returns TRUE if glob contains the tag tab0 + */ + +int text_glob::is_tab0 (void) +{ + return is_tag && (strncmp(text_string, "devtag:tab0", + strlen("devtag:tab0")) == 0); +} + +/* + * is_auto_img - returns TRUE if the glob contains an automatically + * generated image. + */ + +int text_glob::is_auto_img (void) +{ + return is_img_auto; +} + +/* + * is_br - returns TRUE if the glob is a tag containing a .br + * or an implied .br. Note that we do not include .nf or .fi + * as grohtml will place a .br after these commands if they + * should break the line. + */ + +int text_glob::is_br (void) +{ + return is_a_tag() && ((strcmp ("devtag:.br", text_string) == 0) || + (strncmp("devtag:.sp", text_string, + strlen("devtag:.sp")) == 0)); +} + +int text_glob::get_arg (void) +{ + if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) { + const char *p = text_string; + + while ((*p != (char)0) && (!isspace(*p))) + p++; + while ((*p != (char)0) && (isspace(*p))) + p++; + if (*p == (char)0) + return -1; + return atoi(p); + } + return -1; +} + +/* + * get_tab_args - returns the tab position and alignment of the tab tag + */ + +int text_glob::get_tab_args (char *align) +{ + if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) { + const char *p = text_string; + + // firstly the alignment C|R|L + while ((*p != (char)0) && (!isspace(*p))) + p++; + while ((*p != (char)0) && (isspace(*p))) + p++; + *align = *p; + // now the int value + while ((*p != (char)0) && (!isspace(*p))) + p++; + while ((*p != (char)0) && (isspace(*p))) + p++; + if (*p == (char)0) + return -1; + return atoi(p); + } + return -1; +} + +/* + * remember_table - saves table, t, in the text_glob. + */ + +void text_glob::remember_table (html_table *t) +{ + if (tab != 0) + delete tab; + tab = t; +} + +/* + * get_table - returns the stored table description. + */ + +html_table *text_glob::get_table (void) +{ + return tab; +} + +/* + * the class and methods used to construct ordered double linked + * lists. In a previous implementation we used templates via + * #include "ordered-list.h", but this does assume that all C++ + * compilers can handle this feature. Pragmatically it is safer to + * assume this is not the case. + */ + +struct element_list { + element_list *right; + element_list *left; + text_glob *datum; + int lineno; + int minv, minh, maxv, maxh; + + element_list (text_glob *d, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + element_list (); + ~element_list (); +}; + +element_list::element_list () + : right(0), left(0), datum(0), lineno(0), minv(-1), minh(-1), + maxv(-1), maxh(-1) +{ +} + +/* + * element_list - create a list element assigning the datum and region + * parameters. + */ + +element_list::element_list (text_glob *in, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal) + : right(0), left(0), datum(in), lineno(line_number), + minv(min_vertical), minh(min_horizontal), + maxv(max_vertical), maxh(max_horizontal) +{ +} + +element_list::~element_list () +{ + if (datum != 0) + delete datum; +} + +class list { +public: + list (); + ~list (); + int is_less (element_list *a, element_list *b); + void add (text_glob *in, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + void sub_move_right (void); + void move_right (void); + void move_left (void); + int is_empty (void); + int is_equal_to_tail (void); + int is_equal_to_head (void); + void start_from_head (void); + void start_from_tail (void); + void insert (text_glob *in); + void move_to (text_glob *in); + text_glob *move_right_get_data (void); + text_glob *move_left_get_data (void); + text_glob *get_data (void); +private: + element_list *head; + element_list *tail; + element_list *ptr; +}; + +/* + * list - construct an empty list. + */ + +list::list () + : head(0), tail(0), ptr(0) +{ +} + +/* + * ~list - destroy a complete list. + */ + +list::~list() +{ + element_list *temp=head; + + do { + temp = head; + if (temp != 0) { + head = head->right; + delete temp; + } + } while ((head != 0) && (head != tail)); +} + +/* + * is_less - returns TRUE if a is left of b if on the same line or + * if a is higher up the page than b. + */ + +int list::is_less (element_list *a, element_list *b) +{ + // was: + // if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) { + if (a->lineno < b->lineno) { + return TRUE; + } else if (a->lineno > b->lineno) { + return FALSE; + } else if (is_intersection(a->minv, a->maxv, b->minv, b->maxv)) { + return (a->minh < b->minh); + } else { + return (a->maxv < b->maxv); + } +} + +/* + * add - adds a datum to the list in the order specified by the + * region position. + */ + +void list::add (text_glob *in, int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal) +{ + // create a new list element with datum and position fields + // initialized + element_list *t = new element_list(in, line_number, + min_vertical, min_horizontal, + max_vertical, max_horizontal); + element_list *last; + +#if 0 + fprintf(stderr, "[%s %d,%d,%d,%d] ", + in->text_string, min_vertical, min_horizontal, + max_vertical, max_horizontal); + fflush(stderr); +#endif + + if (0 /* nullptr */ == head) { + head = t; + tail = t; + ptr = t; + t->left = t; + t->right = t; + } else { + last = tail; + + while ((last != head) && (is_less(t, last))) + last = last->left; + + if (is_less(t, last)) { + t->right = last; + last->left->right = t; + t->left = last->left; + last->left = t; + // now check for a new head + if (last == head) + head = t; + } else { + // add t beyond last + t->right = last->right; + t->left = last; + last->right->left = t; + last->right = t; + // now check for a new tail + if (last == tail) + tail = t; + } + } +} + +/* + * sub_move_right - removes the element which is currently pointed to + * by ptr from the list and moves ptr to the right. + */ + +void list::sub_move_right (void) +{ + element_list *t=ptr->right; + + if (head == tail) { + head = 0; + if (tail != 0) + delete tail; + + tail = 0; + ptr = 0; + } else { + if (head == ptr) + head = head->right; + if (tail == ptr) + tail = tail->left; + ptr->left->right = ptr->right; + ptr->right->left = ptr->left; + ptr = t; + } +} + +/* + * start_from_head - assigns ptr to the head. + */ + +void list::start_from_head (void) +{ + ptr = head; +} + +/* + * start_from_tail - assigns ptr to the tail. + */ + +void list::start_from_tail (void) +{ + ptr = tail; +} + +/* + * is_empty - returns TRUE if the list has no elements. + */ + +int list::is_empty (void) +{ + return 0 /* nullptr */ == head; +} + +/* + * is_equal_to_tail - returns TRUE if the ptr equals the tail. + */ + +int list::is_equal_to_tail (void) +{ + return ptr == tail; +} + +/* + * is_equal_to_head - returns TRUE if the ptr equals the head. + */ + +int list::is_equal_to_head (void) +{ + return ptr == head; +} + +/* + * move_left - moves the ptr left. + */ + +void list::move_left (void) +{ + ptr = ptr->left; +} + +/* + * move_right - moves the ptr right. + */ + +void list::move_right (void) +{ + ptr = ptr->right; +} + +/* + * get_datum - returns the datum referenced via ptr. + */ + +text_glob* list::get_data (void) +{ + return ptr->datum; +} + +/* + * move_right_get_data - returns the datum referenced via ptr and moves + * ptr right. + */ + +text_glob* list::move_right_get_data (void) +{ + ptr = ptr->right; + if (ptr == head) + return 0; + else + return ptr->datum; +} + +/* + * move_left_get_data - returns the datum referenced via ptr and moves + * ptr right. + */ + +text_glob* list::move_left_get_data (void) +{ + ptr = ptr->left; + if (ptr == tail) + return 0; + else + return ptr->datum; +} + +/* + * insert - inserts data after the current position. + */ + +void list::insert (text_glob *in) +{ + if (is_empty()) + fatal("list must not be empty if we are inserting data"); + else { + if (0 /* nullptr */ == ptr) + ptr = head; + + element_list *t = new element_list(in, ptr->lineno, + ptr->minv, ptr->minh, + ptr->maxv, ptr->maxh); + if (ptr == tail) + tail = t; + ptr->right->left = t; + t->right = ptr->right; + ptr->right = t; + t->left = ptr; + } +} + +/* + * move_to - moves the current position to the point where data, in, + * exists. This is an expensive method and should be used + * sparingly. + */ + +void list::move_to (text_glob *in) +{ + ptr = head; + while (ptr != tail && ptr->datum != in) + ptr = ptr->right; +} + +/* + * page class and methods + */ + +class page { +public: + page (void); + void add (style *s, const string &str, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + void add_tag (style *s, const string &str, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal); + void add_and_encode (style *s, const string &str, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal, + int is_tag); + void add_line (style *s, + int line_number, + int x1, int y1, int x2, int y2, + int thickness); + void insert_tag (const string &str); + void dump_page (void); // debugging method + + // and the data + + list glyphs; // position of glyphs and specials on page + char_buffer buffer; // all characters for this page +}; + +page::page() +{ +} + +/* + * insert_tag - inserts a tag after the current position. + */ + +void page::insert_tag (const string &str) +{ + if (str.length() > 0) { + text_glob *g=new text_glob(); + text_glob *f=glyphs.get_data(); + g->text_glob_tag(&f->text_style, buffer.add_string(str), + str.length(), f->minv, f->minh, f->maxv, f->maxh); + glyphs.insert(g); + } +} + +/* + * add - add html text to the list of glyphs. + */ + +void page::add (style *s, const string &str, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal) +{ + if (str.length() > 0) { + text_glob *g=new text_glob(); + g->text_glob_html(s, buffer.add_string(str), str.length(), + min_vertical, min_horizontal, + max_vertical, max_horizontal); + glyphs.add(g, line_number, min_vertical, min_horizontal, + max_vertical, max_horizontal); + } +} + +/* + * add_tag - adds a troff tag, for example: .tl .sp .br + */ + +void page::add_tag (style *s, const string &str, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal) +{ + if (str.length() > 0) { + text_glob *g; + + if (strncmp((str+'\0').contents(), "devtag:.auto-image", + strlen("devtag:.auto-image")) == 0) { + g = new text_glob(); + g->text_glob_auto_image(s, buffer.add_string(str), str.length(), + min_vertical, min_horizontal, + max_vertical, max_horizontal); + } else { + g = new text_glob(); + g->text_glob_tag(s, buffer.add_string(str), str.length(), + min_vertical, min_horizontal, + max_vertical, max_horizontal); + } + glyphs.add(g, line_number, min_vertical, min_horizontal, + max_vertical, max_horizontal); + } +} + +/* + * add_line - adds the <line> primitive providing that y1==y2 + */ + +void page::add_line (style *s, + int line_number, + int x_1, int y_1, int x_2, int y_2, + int thickness) +{ + if (y_1 == y_2) { + text_glob *g = new text_glob(); + g->text_glob_line(s, + min(y_1, y_2), min(x_1, x_2), + max(y_1, y_2), max(x_1, x_2), + thickness); + glyphs.add(g, line_number, + min(y_1, y_2), min(x_1, x_2), + max(y_1, y_2), max(x_1, x_2)); + } +} + +/* + * to_unicode - returns a unicode translation of int, ch. + */ + +static char *to_unicode (unsigned int ch) +{ + static char buf[30]; + + sprintf(buf, "&#%u;", ch); + return buf; +} + +/* + * add_and_encode - adds a special string to the page, it translates + * the string into html glyphs. The special string + * will have come from x X html: and can contain troff + * character encodings which appear as \[char]. A + * sequence of \\ represents \. + * So for example we can write: + * "cost = \[Po]3.00 file = \\foo\\bar" + * which is translated into: + * "cost = £3.00 file = \foo\bar" + */ + +void page::add_and_encode (style *s, const string &str, + int line_number, + int min_vertical, int min_horizontal, + int max_vertical, int max_horizontal, + int is_tag) +{ + string html_string; + const char *html_glyph; + int i = 0; + const int len = str.length(); + + if (0 /* nullptr */ == s->f) + return; + while (i < len) { + if ((i + 1 < len) && (str.substring(i, 2) == string("\\["))) { + // start of escape + i += 2; // move over \[ + int a = i; + while ((i < len) && (str[i] != ']')) + i++; + if (i > 0) { + string troff_charname = str.substring(a, i - a); + html_glyph = get_html_translation(s->f, troff_charname); + if (html_glyph) + html_string += html_glyph; + else { + glyph *g = name_to_glyph((troff_charname + '\0').contents()); + if (s->f->contains(g)) + html_string += s->f->get_code(g); + } + } + } + else + html_string += str[i]; + i++; + } + if (html_string.length() > 0) { + text_glob *g=new text_glob(); + if (is_tag) + g->text_glob_tag(s, buffer.add_string(html_string), + html_string.length(), + min_vertical, min_horizontal, + max_vertical, max_horizontal); + else + g->text_glob_special(s, buffer.add_string(html_string), + html_string.length(), + min_vertical, min_horizontal, + max_vertical, max_horizontal); + glyphs.add(g, line_number, min_vertical, + min_horizontal, max_vertical, max_horizontal); + } +} + +/* + * dump_page - dump the page contents for debugging purposes. + */ + +void page::dump_page(void) +{ +#if defined(DEBUG_TABLES) + text_glob *old_pos = glyphs.get_data(); + text_glob *g; + + printf("\n<!--\n"); + printf("\n\ndebugging start\n"); + glyphs.start_from_head(); + do { + g = glyphs.get_data(); + if (g->is_tab_ts()) { + printf("\n\n"); + if (g->get_table() != 0) + g->get_table()->dump_table(); + } + printf("%s ", g->text_string); + if (g->is_tab_te()) + printf("\n\n"); + glyphs.move_right(); + } while (! glyphs.is_equal_to_head()); + glyphs.move_to(old_pos); + printf("\ndebugging end\n\n"); + printf("\n-->\n"); + fflush(stdout); +#endif +} + +/* + * font classes and methods + */ + +class html_font : public font { + html_font(const char *); +public: + int encoding_index; + char *encoding; + char *reencoded_name; + ~html_font(); + static html_font *load_html_font(const char *); +}; + +html_font *html_font::load_html_font(const char *s) +{ + html_font *f = new html_font(s); + if (!f->load()) { + delete f; + return 0; + } + return f; +} + +html_font::html_font(const char *nm) +: font(nm) +{ +} + +html_font::~html_font() +{ +} + +/* + * a simple class to contain the header to this document + */ + +class title_desc { +public: + title_desc (); + ~title_desc (); + + int has_been_written; + int has_been_found; + int with_h1; + string text; +}; + + +title_desc::title_desc () + : has_been_written(FALSE), has_been_found(FALSE), with_h1(FALSE) +{ +} + +title_desc::~title_desc () +{ +} + +class header_desc { +public: + header_desc (); + ~header_desc (); + + int no_of_level_one_headings; // how many .SH or .NH 1 have we found? + int no_of_headings; // how many headings have we found? + char_buffer headings; // all the headings used in the document + list headers; // list of headers built from .NH and .SH + list header_filename; // in which file is this header? + int header_level; // current header level + int written_header; // have we written the header yet? + string header_buffer; // current header text + + void write_headings (FILE *f, int force); +}; + +header_desc::header_desc () + : no_of_level_one_headings(0), no_of_headings(0), header_level(2), + written_header(0) +{ +} + +header_desc::~header_desc () +{ +} + +/* + * write_headings - emits a list of links for the headings in this + * document + */ + +void header_desc::write_headings (FILE *f, int force) +{ + text_glob *g; + + if (auto_links || force) { + if (! headers.is_empty()) { + int h=1; + + headers.start_from_head(); + header_filename.start_from_head(); + if (dialect == xhtml) + fputs("<p>", f); + do { + g = headers.get_data(); + fputs("<a href=\"", f); + if (multiple_files && (! header_filename.is_empty())) { + text_glob *fn = header_filename.get_data(); + fputs(fn->text_string, f); + } + fputs("#", f); + if (simple_anchors) { + string buffer(ANCHOR_TEMPLATE); + + buffer += as_string(h); + buffer += '\0'; + fprintf(f, "%s", buffer.contents()); + } else + fputs(g->text_string, f); + h++; + fputs("\">", f); + fputs(g->text_string, f); + fputs("</a>", f); + if (dialect == xhtml) + fputs("<br/>\n", f); + else + fputs("<br>\n", f); + headers.move_right(); + if (multiple_files && (! header_filename.is_empty())) + header_filename.move_right(); + } while (! headers.is_equal_to_head()); + fputs("\n", f); + if (dialect == xhtml) + fputs("</p>\n", f); + } + } +} + +struct assert_pos { + assert_pos *next; + const char *val; + const char *id; +}; + +class assert_state { +public: + assert_state (); + ~assert_state (); + + void addx (const char *c, const char *i, const char *v, + const char *f, const char *l); + void addy (const char *c, const char *i, const char *v, + const char *f, const char *l); + void build(const char *c, const char *v, + const char *f, const char *l); + void check_br (int br); + void check_ce (int ce); + void check_fi (int fi); + void check_sp (int sp); + void reset (void); + +private: + int check_br_flag; + int check_ce_flag; + int check_fi_flag; + int check_sp_flag; + const char *val_br; + const char *val_ce; + const char *val_fi; + const char *val_sp; + const char *file_br; + const char *file_ce; + const char *file_fi; + const char *file_sp; + const char *line_br; + const char *line_ce; + const char *line_fi; + const char *line_sp; + + assert_pos *xhead; + assert_pos *yhead; + + void add (assert_pos **h, + const char *c, const char *i, const char *v, + const char *f, const char *l); + void compare(assert_pos *t, + const char *v, const char *f, const char *l); + void close (const char *c); + void set (const char *c, const char *v, + const char *f, const char *l); + void check_value (const char *s, int v, const char *name, + const char *f, const char *l, int *flag); + int check_value_error (int c, int v, const char *s, + const char *name, + const char *f, const char *l, int flag); +}; + +assert_state::assert_state () +{ + reset(); + val_br = 0; + val_ce = 0; + val_fi = 0; + val_sp = 0; + file_br = 0; + file_ce = 0; + file_fi = 0; + file_sp = 0; + line_br = 0; + line_ce = 0; + line_fi = 0; + line_sp = 0; + xhead = 0; + yhead = 0; +} + +assert_state::~assert_state () +{ + assert_pos *t; + + while (xhead != 0) { + t = xhead; + xhead = xhead->next; + delete[] (char *)t->val; + delete[] (char *)t->id; + delete t; + } + while (yhead != 0) { + t = yhead; + yhead = yhead->next; + delete[] (char *)t->val; + delete[] (char *)t->id; + delete t; + } +} + +void assert_state::reset (void) +{ + check_br_flag = 0; + check_ce_flag = 0; + check_fi_flag = 0; + check_sp_flag = 0; +} + +void assert_state::add (assert_pos **h, + const char *c, const char *i, const char *v, + const char *f, const char *l) +{ + assert_pos *t = *h; + + while (t != 0) { + if (strcmp(t->id, i) == 0) + break; + t = t->next; + } + if (t != 0 && v != 0 && (v[0] != '=')) + compare(t, v, f, l); + else { + if (0 /* nullptr */ == t) { + t = new assert_pos; + t->next = *h; + (*h) = t; + } + if (v == 0 || v[0] != '=') { + if (0 /* nullptr */ == f) + f = strsave("stdin"); + if (0 /* nullptr */ == l) + l = strsave("<none>"); + if (0 /* nullptr */ == v) + v = "no value at all"; + fprintf(stderr, "%s:%s:%s: error in assertion format of id=%s;" + " expected value prefixed with an '=', got %s\n", + program_name, f, l, i, v); + } + t->id = i; + t->val = v; + delete[] (char *)c; + delete[] (char *)f; + delete[] (char *)l; + } +} + +void assert_state::addx (const char *c, const char *i, const char *v, + const char *f, const char *l) +{ + add(&xhead, c, i, v, f, l); +} + +void assert_state::addy (const char *c, const char *i, const char *v, + const char *f, const char *l) +{ + add(&yhead, c, i, v, f, l); +} + +void assert_state::compare(assert_pos *t, + const char *v, const char *f, const char *l) +{ + const char *s=t->val; + + while ((*v) == '=') + v++; + while ((*s) == '=') + s++; + + if (strcmp(v, s) != 0) { + if (0 /* nullptr */ == f) + f = "stdin"; + if (0 /* nullptr */ == l) + l = "<none>"; + fprintf(stderr, "%s:%s: grohtml assertion failed at id%s: " + "expected %s, got %s\n", f, l, t->id, s, v); + } +} + +void assert_state::close (const char *c) +{ + if (strcmp(c, "sp") == 0) + check_sp_flag = 0; + else if (strcmp(c, "br") == 0) + check_br_flag = 0; + else if (strcmp(c, "fi") == 0) + check_fi_flag = 0; + else if (strcmp(c, "nf") == 0) + check_fi_flag = 0; + else if (strcmp(c, "ce") == 0) + check_ce_flag = 0; + else + fprintf(stderr, "internal error: unrecognised tag in grohtml " + "(%s)\n", c); +} + +const char *replace_negate_str (const char *before, char *after) +{ + if (before != 0) + delete[] (char *)before; + + if (strlen(after) > 0) { + int d = atoi(after); + + if (d < 0 || d > 1) { + fprintf(stderr, "expected nf/fi value of 0 or 1, got %d\n", d); + d = 0; + } + if (d == 0) + after[0] = '1'; + else + after[0] = '0'; + after[1] = (char)0; + } + return after; +} + +const char *replace_str (const char *before, const char *after) +{ + if (before != 0) + delete[] (char *)before; + return after; +} + +void assert_state::set (const char *c, const char *v, + const char *f, const char *l) +{ + if (0 /* nullptr */ == l) + l = "<none>"; + if (0 /* nullptr */ == f) + f = "stdin"; + + // fprintf(stderr, "%s:%s:setting %s to %s\n", f, l, c, v); + if (strcmp(c, "sp") == 0) { + check_sp_flag = 1; + val_sp = replace_str(val_sp, strsave(v)); + file_sp = replace_str(file_sp, strsave(f)); + line_sp = replace_str(line_sp, strsave(l)); + } else if (strcmp(c, "br") == 0) { + check_br_flag = 1; + val_br = replace_str(val_br, strsave(v)); + file_br = replace_str(file_br, strsave(f)); + line_br = replace_str(line_br, strsave(l)); + } else if (strcmp(c, "fi") == 0) { + check_fi_flag = 1; + val_fi = replace_str(val_fi, strsave(v)); + file_fi = replace_str(file_fi, strsave(f)); + line_fi = replace_str(line_fi, strsave(l)); + } else if (strcmp(c, "nf") == 0) { + check_fi_flag = 1; + val_fi = replace_negate_str(val_fi, strsave(v)); + file_fi = replace_str(file_fi, strsave(f)); + line_fi = replace_str(line_fi, strsave(l)); + } else if (strcmp(c, "ce") == 0) { + check_ce_flag = 1; + val_ce = replace_str(val_ce, strsave(v)); + file_ce = replace_str(file_ce, strsave(f)); + line_ce = replace_str(line_ce, strsave(l)); + } +} + +/* + * build - builds the troff state assertion. + * see tmac/www.tmac for cmd examples. + */ + +void assert_state::build (const char *c, const char *v, + const char *f, const char *l) +{ + if (c[0] == '{') + set(&c[1], v, f, l); + if (c[0] == '}') + close(&c[1]); +} + +int assert_state::check_value_error (int c, int v, const char *s, + const char *name, const char *f, + const char *l, int flag) +{ + if (! c) { + if (0 /* nullptr */ == f) + f = "stdin"; + if (0 /* nullptr */ == l) + l = "<none>"; + fprintf(stderr, "%s:%s:grohtml (troff state) assertion failed; " + "expected %s to be %s, got %d\n", f, l, name, s, v); + return 0; + } + return flag; +} + +void assert_state::check_value (const char *s, int v, const char *name, + const char *f, const char *l, int *flag) +{ + if (strncmp(s, "<=", 2) == 0) + *flag = check_value_error(v <= atoi(&s[2]), v, s, name, f, l, *flag); + else if (strncmp(s, ">=", 2) == 0) + *flag = check_value_error(v >= atoi(&s[2]), v, s, name, f, l, *flag); + else if (strncmp(s, "==", 2) == 0) + *flag = check_value_error(v == atoi(&s[2]), v, s, name, f, l, *flag); + else if (strncmp(s, "!=", 2) == 0) + *flag = check_value_error(v != atoi(&s[2]), v, s, name, f, l, *flag); + else if (strncmp(s, "<", 1) == 0) + *flag = check_value_error(v < atoi(&s[2]), v, s, name, f, l, *flag); + else if (strncmp(s, ">", 1) == 0) + *flag = check_value_error(v > atoi(&s[2]), v, s, name, f, l, *flag); + else if (strncmp(s, "=", 1) == 0) + *flag = check_value_error(v == atoi(&s[1]), v, s, name, f, l, *flag); + else + *flag = check_value_error(v == atoi(s), v, s, name, f, l, *flag); +} + +void assert_state::check_sp (int sp) +{ + if (check_sp_flag) + check_value(val_sp, sp, "sp", file_sp, line_sp, &check_sp_flag); +} + +void assert_state::check_fi (int fi) +{ + if (check_fi_flag) + check_value(val_fi, fi, "fi", file_fi, line_fi, &check_fi_flag); +} + +void assert_state::check_br (int br) +{ + if (check_br_flag) + check_value(val_br, br, "br", file_br, line_br, &check_br_flag); +} + +void assert_state::check_ce (int ce) +{ + if (check_ce_flag) + check_value(val_ce, ce, "ce", file_ce, line_ce, &check_ce_flag); +} + +class html_printer : public printer { + files file_list; + simple_output html; + int res; + glyph *space_glyph; + int space_width; + int no_of_printed_pages; + int paper_length; + string sbuf; + int sbuf_start_hpos; + int sbuf_vpos; + int sbuf_end_hpos; + int sbuf_prev_hpos; + int sbuf_kern; + style sbuf_style; + int last_sbuf_length; + int overstrike_detected; + style output_style; + int output_hpos; + int output_vpos; + int output_vpos_max; + int output_draw_point_size; + int line_thickness; + int output_line_thickness; + unsigned char output_space_code; + char *inside_font_style; + int page_number; + title_desc title; + header_desc header; + int header_indent; + int suppress_sub_sup; + int cutoff_heading; + page *page_contents; + html_text *current_paragraph; + html_indent *indent; + html_table *table; + int end_center; + int end_tempindent; + TAG_ALIGNMENT next_tag; + int fill_on; + int max_linelength; + int linelength; + int pageoffset; + int troff_indent; + int device_indent; + int temp_indent; + int pointsize; + int vertical_spacing; + int line_number; + color *background; + int seen_indent; + int next_indent; + int seen_pageoffset; + int next_pageoffset; + int seen_linelength; + int next_linelength; + int seen_center; + int next_center; + int seen_space; + int seen_break; + int current_column; + int row_space; + assert_state as; + + void flush_sbuf (); + void set_style (const style &); + void set_space_code (unsigned char c); + void do_exec (char *, const environment *); + void do_import (char *, const environment *); + void do_def (char *, const environment *); + void do_mdef (char *, const environment *); + void do_file (char *, const environment *); + void set_line_thickness (const environment *); + void terminate_current_font (void); + void flush_font (void); + void add_to_sbuf (glyph *g, const string &s); + void write_title (int in_head); + int sbuf_continuation (glyph *g, const char *name, + const environment *env, int w); + void flush_page (void); + void troff_tag (text_glob *g); + void flush_globs (void); + void emit_line (text_glob *g); + void emit_raw (text_glob *g); + void emit_html (text_glob *g); + void determine_space (text_glob *g); + void start_font (const char *name); + void end_font (const char *name); + int is_font_courier (font *f); + int is_line_start (int nf); + int is_courier_until_eol (void); + void start_size (int from, int to); + void do_font (text_glob *g); + void do_center (char *arg); + void do_check_center (void); + void do_break (void); + void do_space (char *arg); + void do_eol (void); + void do_eol_ce (void); + void do_title (void); + void do_fill (char *arg); + void do_heading (char *arg); + void write_header (void); + void determine_header_level (int level); + void do_linelength (char *arg); + void do_pageoffset (char *arg); + void do_indentation (char *arg); + void do_tempindent (char *arg); + void do_indentedparagraph (void); + void do_verticalspacing (char *arg); + void do_pointsize (char *arg); + void do_centered_image (void); + void do_left_image (void); + void do_right_image (void); + void do_auto_image (text_glob *g, + const char *filename); + void do_links (void); + void do_flush (void); + void do_job_name (char *name); + void do_head (char *name); + void insert_split_file (void); + int is_in_middle (int left, int right); + void do_sup_or_sub (text_glob *g); + int start_subscript (text_glob *g); + int end_subscript (text_glob *g); + int start_superscript (text_glob *g); + int end_superscript (text_glob *g); + void outstanding_eol (int n); + int is_bold (font *f); + font *make_bold (font *f); + int overstrike (glyph *g, const char *name, + const environment *env, int w); + void do_body (void); + int next_horiz_pos (text_glob *g, int nf); + void lookahead_for_tables (void); + void insert_tab_te (void); + text_glob *insert_tab_ts (text_glob *where); + void insert_tab0_foreach_tab (void); + void insert_tab_0 (text_glob *where); + void do_indent (int in, int pageoff, + int linelen); + void shutdown_table (void); + void do_tab_ts (text_glob *g); + void do_tab_te (void); + void do_col (char *s); + void do_tab (char *s); + void do_tab0 (void); + int calc_nf (text_glob *g, int nf); + void calc_po_in (text_glob *g, int nf); + void remove_tabs (void); + void remove_courier_tabs (void); + void update_min_max (colType type_of_col, + int *minimum, int *maximum, + text_glob *g); + void add_table_end (const char *); + void do_file_components (void); + void write_navigation (const string &top, + const string &prev, + const string &next, + const string ¤t); + void emit_link (const string &to, + const char *name); + int get_troff_indent (void); + void restore_troff_indent (void); + void handle_assertion (int minv, int minh, + int maxv, int maxh, + const char *s); + void handle_state_assertion (text_glob *g); + void do_end_para (text_glob *g); + int round_width (int x); + void handle_tag_within_title (text_glob *g); + void writeHeadMetaStyle (void); + void handle_valid_flag (int needs_para); + void do_math (text_glob *g); + void write_html_anchor (text_glob *h); + void write_xhtml_anchor (text_glob *h); + // ADD HERE + +public: + html_printer (); + ~html_printer (); + void set_char (glyph *g, font *f, const environment *env, + int w, const char *name); + void set_numbered_char(int num, const environment *env, int *widthp); + glyph *set_char_and_width(const char *nm, const environment *env, + int *widthp, font **f); + void draw (int code, int *p, int np, + const environment *env); + void begin_page (int); + void end_page (int); + void special (char *arg, const environment *env, char type); + void devtag (char *arg, const environment *env, char type); + font *make_font (const char *); + void end_of_line (); +}; + +printer *make_printer() +{ + return new html_printer; +} + +static void usage(FILE *stream); + +void html_printer::set_style(const style &sty) +{ + const char *fontname = sty.f->get_name(); + if (0 /* nullptr */ == fontname) + fatal("no internalname specified for font"); + +#if 0 + change_font(fontname, (font::res / (72 * font::sizescale)) + * sty.point_size); +#endif +} + +/* + * is_bold - returns TRUE if font, f, is bold. + */ + +int html_printer::is_bold (font *f) +{ + const char *fontname = f->get_name(); + return (strcmp(fontname, "B") == 0) || (strcmp(fontname, "BI") == 0); +} + +/* + * make_bold - if a bold style for f exists, return it. + */ + +font *html_printer::make_bold (font *f) +{ + const char *fontname = f->get_name(); + + if (strcmp(fontname, "B") == 0) + return f; + if (strcmp(fontname, "I") == 0) + return font::load_font("BI"); + if (strcmp(fontname, "BI") == 0) + return f; + return 0; +} + +void html_printer::end_of_line() +{ + flush_sbuf(); + line_number++; +} + +/* + * emit_line - writes out a horizontal rule. + */ + +void html_printer::emit_line (text_glob *) +{ + // --fixme-- needs to know the length in percentage + if (dialect == xhtml) + html.put_string("<hr/>"); + else + html.put_string("<hr>"); +} + +/* + * restore_troff_indent - is called when we have temporarily shutdown + * indentation (typically done when we have + * centered an image). + */ + +void html_printer::restore_troff_indent (void) +{ + troff_indent = next_indent; + if (troff_indent > 0) { + /* + * force device indentation + */ + device_indent = 0; + do_indent(get_troff_indent(), pageoffset, linelength); + } +} + +/* + * emit_raw - writes the raw html information directly to the device. + */ + +void html_printer::emit_raw (text_glob *g) +{ + do_font(g); + if (next_tag == INLINE) { + determine_space(g); + current_paragraph->do_emittext(g->text_string, g->text_length); + } else { + int space = current_paragraph->retrieve_para_space() || seen_space; + + current_paragraph->done_para(); + shutdown_table(); + switch (next_tag) { + + case CENTERED: + if (dialect == html4) + current_paragraph->do_para("align=\"center\"", space); + else + current_paragraph->do_para("class=\"center\"", space); + break; + case LEFT: + if (dialect == html4) + current_paragraph->do_para(&html, "align=\"left\"", + get_troff_indent(), pageoffset, + linelength, space); + else + current_paragraph->do_para(&html, "class=\"left\"", + get_troff_indent(), pageoffset, + linelength, space); + break; + case RIGHT: + if (dialect == html4) + current_paragraph->do_para(&html, "align=\"right\"", + get_troff_indent(), pageoffset, + linelength, space); + else + current_paragraph->do_para(&html, "class=\"right\"", + get_troff_indent(), pageoffset, + linelength, space); + break; + default: + fatal("unknown enumeration"); + } + current_paragraph->do_emittext(g->text_string, g->text_length); + current_paragraph->done_para(); + next_tag = INLINE; + suppress_sub_sup = TRUE; + seen_space = FALSE; + restore_troff_indent(); + } +} + +/* + * handle_tag_within_title - handle a limited number of tags within + * the context of a table. Those tags which + * set values rather than generate spaces + * and paragraphs. + */ + +void html_printer::handle_tag_within_title (text_glob *g) +{ + if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll() + || g->is_fi() || g->is_nf()) + troff_tag(g); +} + +/* + * do_center - handle the .ce commands from troff. + */ + +void html_printer::do_center (char *arg) +{ + next_center = atoi(arg); + seen_center = TRUE; +} + +/* + * do_centered_image - set a flag such that the next devtag is + * placed inside a centered paragraph. + */ + +void html_printer::do_centered_image (void) +{ + next_tag = CENTERED; +} + +/* + * do_right_image - set a flag such that the next devtag is + * placed inside a right aligned paragraph. + */ + +void html_printer::do_right_image (void) +{ + next_tag = RIGHT; +} + +/* + * do_left_image - set a flag such that the next devtag is + * placed inside a left aligned paragraph. + */ + +void html_printer::do_left_image (void) +{ + next_tag = LEFT; +} + +/* + * exists - returns TRUE if filename exists. + */ + +static int exists (const char *filename) +{ + FILE *fp = fopen(filename, "r"); + + if (fp == 0) { + return FALSE; + } else { + fclose(fp); + return TRUE; + } +} + +/* + * generate_img_src - returns a html image tag for the filename + * providing that the image exists. + */ + +static string &generate_img_src (const char *filename) +{ + string *s = new string(""); + + while (filename && (filename[0] == ' ')) { + filename++; + } + if (exists(filename)) { + *s += string("<img src=\"") + filename + "\" " + + "alt=\"Image " + filename + "\">"; + if (dialect == xhtml) + *s += "</img>"; + } + return *s; +} + +/* + * do_auto_image - tests whether the image, indicated by filename, + * is present, if so then it emits an html image tag. + * An image tag may be passed through from pic, eqn + * but the corresponding image might not be created. + * Consider .EQ delim $$ .EN or an empty .PS .PE. + */ + +void html_printer::do_auto_image (text_glob *g, const char *filename) +{ + string buffer = generate_img_src(filename); + + if (! buffer.empty()) { + /* + * utilize emit_raw by creating a new text_glob. + */ + text_glob h = *g; + + h.text_string = buffer.contents(); + h.text_length = buffer.length(); + emit_raw(&h); + } else + next_tag = INLINE; +} + +/* + * outstanding_eol - call do_eol, n, times. + */ + +void html_printer::outstanding_eol (int n) +{ + while (n > 0) { + do_eol(); + n--; + } +} + +/* + * do_title - handle the .tl commands from troff. + */ + +void html_printer::do_title (void) +{ + text_glob *t; + int removed_from_head; + + if (page_number == 1) { + int found_title_start = FALSE; + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.sub_move_right(); // move onto next word + do { + t = page_contents->glyphs.get_data(); + removed_from_head = FALSE; + if (t->is_auto_img()) { + string img = generate_img_src((char *)(t->text_string + 20)); + + if (! img.empty()) { + if (found_title_start) + title.text += " "; + found_title_start = TRUE; + title.has_been_found = TRUE; + title.text += img; + } + page_contents->glyphs.sub_move_right(); // move onto next word + removed_from_head = ((!page_contents->glyphs.is_empty()) && + (page_contents->glyphs + .is_equal_to_head())); + } else if (t->is_eo_tl()) { + // end of title found + title.has_been_found = TRUE; + return; + } else if (t->is_a_tag()) { + handle_tag_within_title(t); + page_contents->glyphs.sub_move_right(); // move onto next word + removed_from_head = ((!page_contents->glyphs.is_empty()) && + (page_contents->glyphs + .is_equal_to_head())); + } else if (found_title_start) { + title.text += " " + string(t->text_string, t->text_length); + page_contents->glyphs.sub_move_right(); // move onto next word + removed_from_head = ((!page_contents->glyphs.is_empty()) && + (page_contents->glyphs + .is_equal_to_head())); + } else { + title.text += string(t->text_string, t->text_length); + found_title_start = TRUE; + title.has_been_found = TRUE; + page_contents->glyphs.sub_move_right(); // move onto next word + removed_from_head = ((!page_contents->glyphs.is_empty()) && + (page_contents->glyphs + .is_equal_to_head())); + } + } while ((! page_contents->glyphs.is_equal_to_head()) || + (removed_from_head)); + } + } +} + +/* + * write_html_anchor - writes out an anchor. The style of the anchor + * dependent upon simple_anchor. + */ + +void html_printer::write_html_anchor (text_glob *h) +{ + if (dialect == html4) { + if (h != 0) { + html.put_string("<a name=\""); + if (simple_anchors) { + string buffer(ANCHOR_TEMPLATE); + + buffer += as_string(header.no_of_headings); + buffer += '\0'; + html.put_string(buffer.contents()); + } else + html.put_string(header.header_buffer); + html.put_string("\"></a>").nl(); + } + } +} + +/* + * write_xhtml_anchor - writes out an anchor. The style of the anchor + * dependent upon simple_anchor. + */ + +void html_printer::write_xhtml_anchor (text_glob *h) +{ + if (dialect == xhtml) { + if (h != 0) { + html.put_string(" id=\""); + if (simple_anchors) { + string buffer(ANCHOR_TEMPLATE); + + buffer += as_string(header.no_of_headings); + buffer += '\0'; + html.put_string(buffer.contents()); + } else + html.put_string(header.header_buffer); + html.put_string("\""); + } + } +} + +void html_printer::write_header (void) +{ + if (! header.header_buffer.empty()) { + text_glob *a = 0; + int space = current_paragraph->retrieve_para_space() || seen_space; + + if (header.header_level > 7) + header.header_level = 7; + + // firstly we must terminate any font and type faces + current_paragraph->done_para(); + suppress_sub_sup = TRUE; + + if (cutoff_heading+2 > header.header_level) { + // now we save the header so we can issue a list of links + header.no_of_headings++; + style st; + + a = new text_glob(); + a->text_glob_html(&st, + header.headings + .add_string(header.header_buffer), + header.header_buffer.length(), + header.no_of_headings, header.header_level, + header.no_of_headings, header.header_level); + + // and add this header to the header list + header.headers.add(a, + header.no_of_headings, + header.no_of_headings, header.no_of_headings, + header.no_of_headings, header.no_of_headings); + } + + html.nl().nl(); + + if (manufacture_headings) { + // line break before a header + if (!current_paragraph->emitted_text()) + current_paragraph->do_space(); + // user wants manufactured headings which look better than + // <Hn></Hn> + if (header.header_level<4) { + html.put_string("<b><font size=\"+1\">"); + html.put_string(header.header_buffer); + html.put_string("</font>").nl(); + write_html_anchor(a); + html.put_string("</b>").nl(); + } + else { + html.put_string("<b>"); + html.put_string(header.header_buffer).nl(); + write_html_anchor(a); + html.put_string("</b>").nl(); + } + } + else { + // and now we issue the real header + html.put_string("<h"); + html.put_number(header.header_level); + write_xhtml_anchor(a); + html.put_string(">"); + html.put_string(header.header_buffer).nl(); + write_html_anchor(a); + html.put_string("</h"); + html.put_number(header.header_level); + html.put_string(">").nl(); + } + + /* and now we save the file name in which this header will occur */ + + style st; // fake style to enable us to use the list data structure + + text_glob *h=new text_glob(); + h->text_glob_html(&st, + header.headings.add_string(file_list.file_name()), + file_list.file_name().length(), + header.no_of_headings, header.header_level, + header.no_of_headings, header.header_level); + + header.header_filename.add(h, + header.no_of_headings, + header.no_of_headings, + header.no_of_headings, + header.no_of_headings, + header.no_of_headings); + + current_paragraph->do_para(&html, "", get_troff_indent(), + pageoffset, linelength, space); + } +} + +void html_printer::determine_header_level (int level) +{ + if (level == 0) { + int i; + + for (i = 0; ((i<header.header_buffer.length()) + && ((header.header_buffer[i] == '.') + || is_digit(header.header_buffer[i]))) ; i++) { + if (header.header_buffer[i] == '.') { + level++; + } + } + } + header.header_level = level+1; + if (header.header_level >= 2 && header.header_level <= split_level) { + header.no_of_level_one_headings++; + insert_split_file(); + } +} + +/* + * do_heading - handle the .SH and .NH and equivalent commands from + * troff. + */ + +void html_printer::do_heading (char *arg) +{ + text_glob *g; + int level=atoi(arg); + int horiz; + + header.header_buffer.clear(); + page_contents->glyphs.move_right(); + if (! page_contents->glyphs.is_equal_to_head()) { + g = page_contents->glyphs.get_data(); + horiz = g->minh; + do { + if (g->is_auto_img()) { + string img=generate_img_src((char *)(g->text_string + 20)); + + if (! img.empty()) { + // we cannot use full heading anchors with images + simple_anchors = TRUE; + if (horiz < g->minh) + header.header_buffer += " "; + + header.header_buffer += img; + } + } + else if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() + || g->is_ll()) + troff_tag(g); + else if (g->is_fi()) + fill_on = 1; + else if (g->is_nf()) + fill_on = 0; + else if (! (g->is_a_line() || g->is_a_tag())) { + /* + * we ignore the other tag commands when constructing a heading + */ + if (horiz < g->minh) + header.header_buffer += " "; + + horiz = g->maxh; + header.header_buffer += string(g->text_string, g->text_length); + } + page_contents->glyphs.move_right(); + g = page_contents->glyphs.get_data(); + } while ((! page_contents->glyphs.is_equal_to_head()) && + (! g->is_eo_h())); + } + + determine_header_level(level); + write_header(); + + /* + * finally set the output font to uninitialized, thus forcing + * the new paragraph to start a new font block. + */ + + output_style.f = 0; + g = page_contents->glyphs.get_data(); + page_contents->glyphs.move_left(); // so that next time we use old g +} + +/* + * is_courier_until_eol - returns TRUE if we can see a whole line which + * is courier + */ + +int html_printer::is_courier_until_eol (void) +{ + text_glob *orig = page_contents->glyphs.get_data(); + int result = TRUE; + text_glob *g; + + if (! page_contents->glyphs.is_equal_to_tail()) { + page_contents->glyphs.move_right(); + do { + g = page_contents->glyphs.get_data(); + if (! g->is_a_tag() && (! is_font_courier(g->text_style.f))) + result = FALSE; + page_contents->glyphs.move_right(); + } while (result && + (! page_contents->glyphs.is_equal_to_head()) && + (! g->is_fi()) && (! g->is_eol())); + + /* + * now restore our previous position. + */ + while (page_contents->glyphs.get_data() != orig) + page_contents->glyphs.move_left(); + } + return result; +} + +/* + * do_linelength - handle the .ll command from troff. + */ + +void html_printer::do_linelength (char *arg) +{ + if (max_linelength == -1) + max_linelength = atoi(arg); + + next_linelength = atoi(arg); + seen_linelength = TRUE; +} + +/* + * do_pageoffset - handle the .po command from troff. + */ + +void html_printer::do_pageoffset (char *arg) +{ + next_pageoffset = atoi(arg); + seen_pageoffset = TRUE; +} + +/* + * get_troff_indent - returns the indent value. + */ + +int html_printer::get_troff_indent (void) +{ + if (end_tempindent > 0) + return temp_indent; + else + return troff_indent; +} + +/* + * do_indentation - handle the .in command from troff. + */ + +void html_printer::do_indentation (char *arg) +{ + next_indent = atoi(arg); + seen_indent = TRUE; +} + +/* + * do_tempindent - handle the .ti command from troff. + */ + +void html_printer::do_tempindent (char *arg) +{ + if (fill_on) { + /* + * we set the end_tempindent to 2 as the first .br + * activates the .ti and the second terminates it. + */ + end_tempindent = 2; + temp_indent = atoi(arg); + } +} + +/* + * shutdown_table - shuts down the current table. + */ + +void html_printer::shutdown_table (void) +{ + if (table != 0) { + current_paragraph->done_para(); + table->emit_finish_table(); + // don't delete this table as it will be deleted when we destroy the + // text_glob + table = 0; + } +} + +/* + * do_indent - remember the indent parameters and if + * indent is > pageoff and indent has changed + * then we start a html table to implement the indentation. + */ + +void html_printer::do_indent (int in, int pageoff, int linelen) +{ + if ((device_indent != -1) && + (pageoffset+device_indent != in+pageoff)) { + + int space = current_paragraph->retrieve_para_space() || seen_space; + current_paragraph->done_para(); + + device_indent = in; + pageoffset = pageoff; + if (linelen <= max_linelength) + linelength = linelen; + + current_paragraph->do_para(&html, "", device_indent, + pageoffset, max_linelength, space); + } +} + +/* + * do_verticalspacing - handle the .vs command from troff. + */ + +void html_printer::do_verticalspacing (char *arg) +{ + vertical_spacing = atoi(arg); +} + +/* + * do_pointsize - handle the .ps command from troff. + */ + +void html_printer::do_pointsize (char *arg) +{ + /* + * firstly check to see whether this point size is really associated + * with a .tl tag + */ + + if (! page_contents->glyphs.is_empty()) { + text_glob *g = page_contents->glyphs.get_data(); + text_glob *t = page_contents->glyphs.get_data(); + + while (t->is_a_tag() && (!page_contents->glyphs.is_equal_to_head())) + { + if (t->is_tl()) { + /* + * found title therefore ignore this .ps tag + */ + while (t != g) { + page_contents->glyphs.move_left(); + t = page_contents->glyphs.get_data(); + } + return; + } + page_contents->glyphs.move_right(); + t = page_contents->glyphs.get_data(); + } + /* + * move back to original position + */ + while (t != g) { + page_contents->glyphs.move_left(); + t = page_contents->glyphs.get_data(); + } + /* + * collect valid pointsize + */ + pointsize = atoi(arg); + } +} + +/* + * do_fill - records whether troff has requested that text be filled. + */ + +void html_printer::do_fill (char *arg) +{ + int on = atoi(arg); + + output_hpos = get_troff_indent()+pageoffset; + suppress_sub_sup = TRUE; + + if (fill_on != on) { + if (on) + current_paragraph->do_para("", seen_space); + fill_on = on; + } +} + +/* + * do_eol - handle the end of line + */ + +void html_printer::do_eol (void) +{ + if (! fill_on) { + if (current_paragraph->ever_emitted_text()) { + current_paragraph->do_newline(); + current_paragraph->do_break(); + } + } + output_hpos = get_troff_indent()+pageoffset; +} + +/* + * do_check_center - checks to see whether we have seen a '.ce' tag + * during the previous line. + */ + +void html_printer::do_check_center(void) +{ + if (seen_center) { + seen_center = FALSE; + if (next_center > 0) { + if (end_center == 0) { + int space = current_paragraph->retrieve_para_space() + || seen_space; + current_paragraph->done_para(); + suppress_sub_sup = TRUE; + if (dialect == html4) + current_paragraph->do_para("align=\"center\"", space); + else + current_paragraph->do_para("class=\"center\"", space); + } else + if ((strcmp("align=\"center\"", + current_paragraph->get_alignment()) != 0) && + (strcmp("class=\"center\"", + current_paragraph->get_alignment()) != 0)) { + /* + * different alignment, so shutdown paragraph and open + * a new one. + */ + int space = current_paragraph->retrieve_para_space() + || seen_space; + current_paragraph->done_para(); + suppress_sub_sup = TRUE; + if (dialect == html4) + current_paragraph->do_para("align=\"center\"", space); + else + current_paragraph->do_para("class=\"center\"", space); + } else + // same alignment; if we have emitted text, issue a break. + if (current_paragraph->emitted_text()) + current_paragraph->do_break(); + } else + /* + * next_center == 0 + */ + if (end_center > 0) { + seen_space = seen_space + || current_paragraph->retrieve_para_space(); + current_paragraph->done_para(); + suppress_sub_sup = TRUE; + current_paragraph->do_para("", seen_space); + } + end_center = next_center; + } +} + +/* + * do_eol_ce - handle end of line specifically for a .ce + */ + +void html_printer::do_eol_ce (void) +{ + if (end_center > 0) { + if (end_center > 1) + if (current_paragraph->emitted_text()) + current_paragraph->do_break(); + + end_center--; + if (end_center == 0) { + current_paragraph->done_para(); + suppress_sub_sup = TRUE; + } + } +} + +/* + * do_flush - flushes all output and tags. + */ + +void html_printer::do_flush (void) +{ + current_paragraph->done_para(); +} + +/* + * do_links - moves onto a new temporary file and sets auto_links to + * false. + */ + +void html_printer::do_links (void) +{ + html.end_line(); // flush line + auto_links = FALSE; // from now on only emit under user request + file_list.add_new_file(xtmpfile()); + file_list.set_links_required(); + html.set_file(file_list.get_file()); +} + +/* + * insert_split_file - + */ + +void html_printer::insert_split_file (void) +{ + if (multiple_files) { + current_paragraph->done_para(); // flush paragraph + html.end_line(); // flush line + html.set_file(file_list.get_file()); // flush current file + file_list.add_new_file(xtmpfile()); + string split_file = job_name; + + split_file += string("-"); + split_file += as_string(header.no_of_level_one_headings); + if (dialect == xhtml) + split_file += string(".xhtml"); + else + split_file += string(".html"); + split_file += '\0'; + + file_list.set_file_name(split_file); + html.set_file(file_list.get_file()); + } +} + +/* + * do_job_name - assigns the job_name to name. + */ + +void html_printer::do_job_name (char *name) +{ + if (! multiple_files) { + multiple_files = TRUE; + while (name != 0 && (*name != (char)0) && (*name == ' ')) + name++; + job_name = name; + } +} + +/* + * do_head - adds a string to head_info which is to be included into + * the <head> </head> section of the html document. + */ + +void html_printer::do_head (char *name) +{ + head_info += string(name); + head_info += '\n'; +} + +/* + * do_break - handles the ".br" request and also undoes an outstanding + * ".ti" command and calls indent if the indentation related + * registers have changed. + */ + +void html_printer::do_break (void) +{ + int seen_temp_indent = FALSE; + + current_paragraph->do_break(); + if (end_tempindent > 0) { + end_tempindent--; + if (end_tempindent > 0) + seen_temp_indent = TRUE; + } + if (seen_indent || seen_pageoffset || seen_linelength + || seen_temp_indent) { + if (seen_indent && (! seen_temp_indent)) + troff_indent = next_indent; + if (! seen_pageoffset) + next_pageoffset = pageoffset; + if (! seen_linelength) + next_linelength = linelength; + do_indent(get_troff_indent(), next_pageoffset, next_linelength); + } + seen_indent = seen_temp_indent; + seen_linelength = FALSE; + seen_pageoffset = FALSE; + do_check_center(); + output_hpos = get_troff_indent()+pageoffset; + suppress_sub_sup = TRUE; +} + +void html_printer::do_space (char *arg) +{ + int n = atoi(arg); + + seen_space = atoi(arg); + as.check_sp(seen_space); +#if 0 + if (n>0 && table) + table->set_space(TRUE); +#endif + + while (n>0) { + current_paragraph->do_space(); + n--; + } + suppress_sub_sup = TRUE; +} + +/* + * do_tab_ts - start a table, which will have already been defined. + */ + +void html_printer::do_tab_ts (text_glob *g) +{ + html_table *t = g->get_table(); + + if (t != 0) { + current_column = 0; + current_paragraph->done_pre(); + current_paragraph->done_para(); + current_paragraph->remove_para_space(); + +#if defined(DEBUG_TABLES) + html.simple_comment("TABS"); +#endif + + t->set_linelength(max_linelength); + t->add_indent(pageoffset); +#if 0 + t->emit_table_header(seen_space); +#else + t->emit_table_header(FALSE); + row_space = current_paragraph->retrieve_para_space() || seen_space; + seen_space = FALSE; +#endif + } + + table = t; +} + +/* + * do_tab_te - finish a table. + */ + +void html_printer::do_tab_te (void) +{ + if (table) { + current_paragraph->done_para(); + current_paragraph->remove_para_space(); + table->emit_finish_table(); + } + + table = 0; + restore_troff_indent(); +} + +/* + * do_tab - handle the "devtag:tab" tag + */ + +void html_printer::do_tab (char *s) +{ + if (table) { + while (isspace(*s)) + s++; + s++; + int col = table->find_column(atoi(s) + pageoffset + + get_troff_indent()); + if (col > 0) { + current_paragraph->done_para(); + table->emit_col(col); + } + } +} + +/* + * do_tab0 - handle the "devtag:tab0" tag + */ + +void html_printer::do_tab0 (void) +{ + if (table) { + int col = table->find_column(pageoffset+get_troff_indent()); + if (col > 0) { + current_paragraph->done_para(); + table->emit_col(col); + } + } +} + +/* + * do_col - start column, s. + */ + +void html_printer::do_col (char *s) +{ + if (table) { + if (atoi(s) < current_column) + row_space = seen_space; + + current_column = atoi(s); + current_paragraph->done_para(); + table->emit_col(current_column); + current_paragraph->do_para("", row_space); + } +} + +/* + * troff_tag - processes the troff tag and manipulates the troff + * state machine. + */ + +void html_printer::troff_tag (text_glob *g) +{ + /* + * firstly skip over devtag: + */ + char *t=(char *)g->text_string+strlen("devtag:"); + if (strncmp(g->text_string, "html</p>:", strlen("html</p>:")) == 0) { + do_end_para(g); + } else if (strncmp(g->text_string, "html<?p>:", strlen("html<?p>:")) + == 0) { + if (current_paragraph->emitted_text()) + html.put_string(g->text_string+9); + else + do_end_para(g); + } else if (strncmp(g->text_string, "math<?p>:", strlen("math<?p>:")) + == 0) { + do_math(g); + } else if (g->is_eol()) { + do_eol(); + } else if (g->is_eol_ce()) { + do_eol_ce(); + } else if (strncmp(t, ".sp", 3) == 0) { + char *a = (char *)t+3; + do_space(a); + } else if (strncmp(t, ".br", 3) == 0) { + seen_break = 1; + as.check_br(1); + do_break(); + } else if (strcmp(t, ".centered-image") == 0) { + do_centered_image(); + } else if (strcmp(t, ".right-image") == 0) { + do_right_image(); + } else if (strcmp(t, ".left-image") == 0) { + do_left_image(); + } else if (strncmp(t, ".auto-image", 11) == 0) { + char *a = (char *)t+11; + do_auto_image(g, a); + } else if (strncmp(t, ".ce", 3) == 0) { + char *a = (char *)t+3; + suppress_sub_sup = TRUE; + do_center(a); + } else if (g->is_tl()) { + suppress_sub_sup = TRUE; + title.with_h1 = TRUE; + do_title(); + } else if (strncmp(t, ".html-tl", 8) == 0) { + suppress_sub_sup = TRUE; + title.with_h1 = FALSE; + do_title(); + } else if (strncmp(t, ".fi", 3) == 0) { + char *a = (char *)t+3; + do_fill(a); + } else if ((strncmp(t, ".SH", 3) == 0) + || (strncmp(t, ".NH", 3) == 0)) { + char *a = (char *)t+3; + do_heading(a); + } else if (strncmp(t, ".ll", 3) == 0) { + char *a = (char *)t+3; + do_linelength(a); + } else if (strncmp(t, ".po", 3) == 0) { + char *a = (char *)t+3; + do_pageoffset(a); + } else if (strncmp(t, ".in", 3) == 0) { + char *a = (char *)t+3; + do_indentation(a); + } else if (strncmp(t, ".ti", 3) == 0) { + char *a = (char *)t+3; + do_tempindent(a); + } else if (strncmp(t, ".vs", 3) == 0) { + char *a = (char *)t+3; + do_verticalspacing(a); + } else if (strncmp(t, ".ps", 3) == 0) { + char *a = (char *)t+3; + do_pointsize(a); + } else if (strcmp(t, ".links") == 0) { + do_links(); + } else if (strncmp(t, ".job-name", 9) == 0) { + char *a = (char *)t+9; + do_job_name(a); + } else if (strncmp(t, ".head", 5) == 0) { + char *a = (char *)t+5; + do_head(a); + } else if (strcmp(t, ".no-auto-rule") == 0) { + auto_rule = FALSE; + } else if (strcmp(t, ".tab-ts") == 0) { + do_tab_ts(g); + } else if (strcmp(t, ".tab-te") == 0) { + do_tab_te(); + } else if (strncmp(t, ".col ", 5) == 0) { + char *a = (char *)t+4; + do_col(a); + } else if (strncmp(t, "tab ", 4) == 0) { + char *a = (char *)t+3; + do_tab(a); + } else if (strncmp(t, "tab0", 4) == 0) { + do_tab0(); + } +} + +/* + * do_math - prints out the equation + */ + +void html_printer::do_math (text_glob *g) +{ + do_font(g); + if (current_paragraph->emitted_text()) + html.put_string(g->text_string+9); + else + do_end_para(g); +} + +/* + * is_in_middle - returns TRUE if the positions left..right are in the + * center of the page. + */ + +int html_printer::is_in_middle (int left, int right) +{ + return( abs(abs(left-pageoffset) - abs(pageoffset+linelength-right)) + <= CENTER_TOLERANCE ); +} + +/* + * flush_globs - runs through the text glob list and emits html. + */ + +void html_printer::flush_globs (void) +{ + text_glob *g; + + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.start_from_head(); + do { + g = page_contents->glyphs.get_data(); +#if 0 + fprintf(stderr, "[%s:%d:%d:%d:%d]", + g->text_string, g->minv, g->minh, g->maxv, g->maxh) ; + fflush(stderr); +#endif + + handle_state_assertion(g); + + if (strcmp(g->text_string, "XXXXXXX") == 0) + stop(); + + if (g->is_a_tag()) + troff_tag(g); + else if (g->is_a_line()) + emit_line(g); + else { + as.check_sp(seen_space); + as.check_br(seen_break); + seen_break = 0; + seen_space = 0; + emit_html(g); + } + + as.check_fi(fill_on); + as.check_ce(end_center); + /* + * after processing the title (and removing it) the glyph list + * might be empty + */ + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.move_right(); + } + } while (! page_contents->glyphs.is_equal_to_head()); + } +} + +/* + * calc_nf - calculates the _no_ format flag, given the + * text glob, g. + */ + +int html_printer::calc_nf (text_glob *g, int nf) +{ + if (g != 0) { + if (g->is_fi()) { + as.check_fi(TRUE); + return FALSE; + } + if (g->is_nf()) { + as.check_fi(FALSE); + return TRUE; + } + } + as.check_fi(! nf); + return nf; +} + +/* + * calc_po_in - calculates the, in, po, registers + */ + +void html_printer::calc_po_in (text_glob *g, int nf) +{ + if (g->is_in()) + troff_indent = g->get_arg(); + else if (g->is_po()) + pageoffset = g->get_arg(); + else if (g->is_ti()) { + temp_indent = g->get_arg(); + end_tempindent = 2; + } else if (g->is_br() || (nf && g->is_eol())) { + if (end_tempindent > 0) + end_tempindent--; + } +} + +/* + * next_horiz_pos - returns the next horiz position. + * -1 is returned if it doesn't exist. + */ + +int html_printer::next_horiz_pos (text_glob *g, int nf) +{ + int next = -1; + + if ((g != 0) && (g->is_br() || (nf && g->is_eol()))) + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.move_right_get_data(); + if (0 /* nullptr */ == g) { + page_contents->glyphs.start_from_head(); + as.reset(); + } + else { + next = g->minh; + page_contents->glyphs.move_left(); + } + } + return next; +} + +/* + * insert_tab_ts - inserts a tab-ts before, where. + */ + +text_glob *html_printer::insert_tab_ts (text_glob *where) +{ + text_glob *start_of_table; + text_glob *old_pos = page_contents->glyphs.get_data(); + page_contents->glyphs.move_to(where); + page_contents->glyphs.move_left(); + // tab table start + page_contents->insert_tag(string("devtag:.tab-ts")); + page_contents->glyphs.move_right(); + start_of_table = page_contents->glyphs.get_data(); + page_contents->glyphs.move_to(old_pos); + return start_of_table; +} + +/* + * insert_tab_te - inserts a tab-te before the current position + * (it skips backwards over .sp/.br) + */ + +void html_printer::insert_tab_te (void) +{ + text_glob *g = page_contents->glyphs.get_data(); + page_contents->dump_page(); + while (page_contents->glyphs.get_data()->is_a_tag()) + page_contents->glyphs.move_left(); + // tab table end + page_contents->insert_tag(string("devtag:.tab-te")); + while (g != page_contents->glyphs.get_data()) + page_contents->glyphs.move_right(); + page_contents->dump_page(); +} + +/* + * insert_tab_0 - inserts a tab0 before, where. + */ + +void html_printer::insert_tab_0 (text_glob *where) +{ + text_glob *old_pos = page_contents->glyphs.get_data(); + + page_contents->glyphs.move_to(where); + page_contents->glyphs.move_left(); + // tab0 start of line + page_contents->insert_tag(string("devtag:tab0")); + page_contents->glyphs.move_right(); + page_contents->glyphs.move_to(old_pos); +} + +/* + * remove_tabs - removes the tabs tags on this line. + */ + +void html_printer::remove_tabs (void) +{ + text_glob *orig = page_contents->glyphs.get_data(); + text_glob *g; + + if (! page_contents->glyphs.is_equal_to_tail()) { + do { + g = page_contents->glyphs.get_data(); + if (g->is_tab()) { + page_contents->glyphs.sub_move_right(); + if (g == orig) + orig = page_contents->glyphs.get_data(); + } else + page_contents->glyphs.move_right(); + } while ((! page_contents->glyphs.is_equal_to_head()) && + (! g->is_eol())); + + /* + * now restore our previous position. + */ + while (page_contents->glyphs.get_data() != orig) + page_contents->glyphs.move_left(); + } +} + +void html_printer::remove_courier_tabs (void) +{ + text_glob *g; + int line_start = TRUE; + int nf = FALSE; + + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.start_from_head(); + as.reset(); + line_start = TRUE; + do { + g = page_contents->glyphs.get_data(); + handle_state_assertion(g); + nf = calc_nf(g, nf); + + if (line_start) { + if (line_start && nf && is_courier_until_eol()) { + remove_tabs(); + g = page_contents->glyphs.get_data(); + } + } + + // line_start = g->is_br() || g->is_nf() || g->is_fi() + // || (nf && g->is_eol()); + line_start = g->is_br() || (nf && g->is_eol()); + page_contents->glyphs.move_right(); + } while (! page_contents->glyphs.is_equal_to_head()); + } +} + +void html_printer::insert_tab0_foreach_tab (void) +{ + text_glob *start_of_line = 0; + text_glob *g = 0; + int seen_tab = FALSE; + int seen_col = FALSE; + int nf = FALSE; + + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.start_from_head(); + as.reset(); + start_of_line = page_contents->glyphs.get_data(); + do { + g = page_contents->glyphs.get_data(); + handle_state_assertion(g); + nf = calc_nf(g, nf); + + if (g->is_tab()) + seen_tab = TRUE; + + if (g->is_col()) + seen_col = TRUE; + + if (g->is_br() || (nf && g->is_eol())) { + do { + page_contents->glyphs.move_right(); + g = page_contents->glyphs.get_data(); + handle_state_assertion(g); + nf = calc_nf(g, nf); + if (page_contents->glyphs.is_equal_to_head()) { + if (seen_tab && !seen_col) + insert_tab_0(start_of_line); + return; + } + } while (g->is_br() || (nf && g->is_eol()) || g->is_ta()); + // printf("\nstart_of_line is: %s\n", g->text_string); + if (seen_tab && !seen_col) { + insert_tab_0(start_of_line); + page_contents->glyphs.move_to(g); + } + + seen_tab = FALSE; + seen_col = FALSE; + start_of_line = g; + } + page_contents->glyphs.move_right(); + } while (! page_contents->glyphs.is_equal_to_head()); + if (seen_tab && !seen_col) + insert_tab_0(start_of_line); + + } +} + +/* + * update_min_max - updates the extent of a column, given the left and + * right extents of a glyph, g. + */ + +void html_printer::update_min_max (colType type_of_col, + int *minimum, int *maximum, + text_glob *g) +{ + switch (type_of_col) { + + case tab_tag: + break; + case tab0_tag: + *minimum = g->minh; + break; + case col_tag: + *minimum = g->minh; + *maximum = g->maxh; + break; + default: + break; + } +} + +/* + * add_table_end - moves left one glyph, adds a table end tag and adds + * a debugging string. + */ + +void html_printer::add_table_end (const char * +#if defined(DEBUG_TABLES) + debug_string +#endif +) +{ + page_contents->glyphs.move_left(); + insert_tab_te(); +#if defined(DEBUG_TABLES) + page_contents->insert_tag(string(debug_string)); +#endif +} + +/* + * lookahead_for_tables - checks for .col tags and inserts table + * start/end tags + */ + +void html_printer::lookahead_for_tables (void) +{ + text_glob *g; + text_glob *start_of_line = 0; + text_glob *start_of_table = 0; + text_glob *last = 0; + colType type_of_col = none; + int found_col = FALSE; + int ncol = 0; + int colmin = 0; // pacify compiler + int colmax = 0; // pacify compiler + html_table *tbl = new html_table(&html, -1); + const char *tab_defs = 0; + char align = 'L'; + int nf = FALSE; + int old_pageoffset = pageoffset; + + remove_courier_tabs(); + page_contents->dump_page(); + insert_tab0_foreach_tab(); + page_contents->dump_page(); + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.start_from_head(); + as.reset(); + g = page_contents->glyphs.get_data(); + if (g->is_br()) { + g = page_contents->glyphs.move_right_get_data(); + handle_state_assertion(g); + if (page_contents->glyphs.is_equal_to_head()) { + if (tbl != 0) { + delete tbl; + tbl = 0; + } + return; + } + + start_of_line = g; + ncol = 0; + if (found_col) + last = g; + found_col = FALSE; + } + + do { +#if defined(DEBUG_TABLES) + fprintf(stderr, " [") ; + fprintf(stderr, g->text_string) ; + fprintf(stderr, "] ") ; + fflush(stderr); + if (strcmp(g->text_string, "XXXXXXX") == 0) + stop(); +#endif + + nf = calc_nf(g, nf); + calc_po_in(g, nf); + if (g->is_col()) { + if (type_of_col == tab_tag && start_of_table != 0) { + page_contents->glyphs.move_left(); + insert_tab_te(); + start_of_table->remember_table(tbl); + tbl = new html_table(&html, -1); + page_contents->insert_tag(string("*** TAB -> COL ***")); + if (tab_defs != 0) + tbl->tab_stops->init(tab_defs); + start_of_table = 0; + last = 0; + } + type_of_col = col_tag; + found_col = TRUE; + ncol = g->get_arg(); + align = 'L'; + colmin = 0; + colmax = 0; + } else if (g->is_tab()) { + type_of_col = tab_tag; + colmin = g->get_tab_args(&align); + align = 'L'; // for now as 'C' and 'R' are broken + ncol = tbl->find_tab_column(colmin); + colmin += pageoffset + get_troff_indent(); + colmax = tbl->get_tab_pos(ncol+1); + if (colmax > 0) + colmax += pageoffset + get_troff_indent(); + } else if (g->is_tab0()) { + if (type_of_col == col_tag && start_of_table != 0) { + page_contents->glyphs.move_left(); + insert_tab_te(); + start_of_table->remember_table(tbl); + tbl = new html_table(&html, -1); + page_contents->insert_tag(string("*** COL -> TAB ***")); + start_of_table = 0; + last = 0; + } + if (tab_defs != 0) + tbl->tab_stops->init(tab_defs); + type_of_col = tab0_tag; + ncol = 1; + colmin = 0; + colmax = tbl->get_tab_pos(2) + pageoffset + get_troff_indent(); + } else if (! g->is_a_tag()) + update_min_max(type_of_col, &colmin, &colmax, g); + if ((g->is_col() || g->is_tab() || g->is_tab0()) + && (start_of_line != 0) + && (0 /* nullptr */ == start_of_table)) { + start_of_table = insert_tab_ts(start_of_line); + start_of_line = 0; + } else if (g->is_ce() && (start_of_table != 0)) { + add_table_end("*** CE ***"); + start_of_table->remember_table(tbl); + tbl = new html_table(&html, -1); + start_of_table = 0; + last = 0; + } else if (g->is_ta()) { + tab_defs = g->text_string; + if (type_of_col == col_tag) + tbl->tab_stops->check_init(tab_defs); + if (!tbl->tab_stops->compatible(tab_defs)) { + if (start_of_table != 0) { + add_table_end("*** TABS ***"); + start_of_table->remember_table(tbl); + tbl = new html_table(&html, -1); + start_of_table = 0; + type_of_col = none; + last = 0; + } + tbl->tab_stops->init(tab_defs); + } + } + if (((! g->is_a_tag()) || g->is_tab()) && (start_of_table != 0)) { + // we are in a table and have a glyph + if ((ncol == 0) + || (! tbl->add_column(ncol, colmin, colmax, align))) { + if (ncol == 0) + add_table_end("*** NCOL == 0 ***"); + else + add_table_end("*** CROSSED COLS ***"); + + start_of_table->remember_table(tbl); + tbl = new html_table(&html, -1); + start_of_table = 0; + type_of_col = none; + last = 0; + } + } + /* + * move onto next glob, check whether we are starting a new line + */ + g = page_contents->glyphs.move_right_get_data(); + handle_state_assertion(g); + if (0 /* nullptr */ == g) { + if (found_col) { + page_contents->glyphs.start_from_head(); + as.reset(); + last = g; + found_col = FALSE; + } + } else if (g->is_br() || (nf && g->is_eol())) { + do { + g = page_contents->glyphs.move_right_get_data(); + handle_state_assertion(g); + nf = calc_nf(g, nf); + } while ((g != 0) && (g->is_br() || (nf && g->is_eol()))); + start_of_line = g; + ncol = 0; + if (found_col) + last = g; + found_col = FALSE; + } + } while ((g != 0) && (! page_contents->glyphs.is_equal_to_head())); + +#if defined(DEBUG_TABLES) + fprintf(stderr, "finished scanning for tables\n"); +#endif + + page_contents->glyphs.start_from_head(); + if (start_of_table != 0) { + if (last != 0) + while (last != page_contents->glyphs.get_data()) + page_contents->glyphs.move_left(); + + insert_tab_te(); + start_of_table->remember_table(tbl); + tbl = 0; + page_contents->insert_tag(string("*** LAST ***")); + } + } + if (tbl != 0) { + delete tbl; + tbl = 0; + } + + // and reset the registers + pageoffset = old_pageoffset; + troff_indent = 0; + temp_indent = 0; + end_tempindent = 0; +} + +void html_printer::flush_page (void) +{ + suppress_sub_sup = TRUE; + flush_sbuf(); + page_contents->dump_page(); + lookahead_for_tables(); + page_contents->dump_page(); + flush_globs(); + current_paragraph->done_para(); + current_paragraph->flush_text(); + // move onto a new page + delete page_contents; +#if defined(DEBUG_TABLES) + fprintf(stderr, "\n\n*** flushed page ***\n\n"); + html.simple_comment("new page called"); +#endif + page_contents = new page; +} + +/* + * determine_space - works out whether we need to write a space. + * If last glyph is adjoining, then emit no space. + */ + +void html_printer::determine_space (text_glob *g) +{ + if (current_paragraph->is_in_pre()) { + /* + * .nf has been specified + */ + while (output_hpos < g->minh) { + output_hpos += space_width; + current_paragraph->emit_space(); + } + } else { + if ((output_vpos != g->minv) || (output_hpos < g->minh)) { + current_paragraph->emit_space(); + } + } +} + +/* + * is_line_start - returns TRUE if we are at the start of a line. + */ + +int html_printer::is_line_start (int nf) +{ + int line_start = FALSE; + int result = TRUE; + text_glob *orig = page_contents->glyphs.get_data(); + text_glob *g; + + if (! page_contents->glyphs.is_equal_to_head()) { + do { + page_contents->glyphs.move_left(); + g = page_contents->glyphs.get_data(); + result = g->is_a_tag(); + if (g->is_fi()) + nf = FALSE; + else if (g->is_nf()) + nf = TRUE; + line_start = g->is_col() || g->is_br() || (nf && g->is_eol()); + } while ((!line_start) && (result)); + /* + * now restore our previous position. + */ + while (page_contents->glyphs.get_data() != orig) + page_contents->glyphs.move_right(); + } + return result; +} + +/* + * is_font_courier - returns TRUE if the font, f, is courier. + */ + +int html_printer::is_font_courier (font *f) +{ + if (f != 0) { + const char *fontname = f->get_name(); + + return( (fontname != 0) && (fontname[0] == 'C') ); + } + return FALSE; +} + +/* + * end_font - shuts down the font corresponding to fontname. + */ + +void html_printer::end_font (const char *fontname) +{ + if (strcmp(fontname, "B") == 0) { + current_paragraph->done_bold(); + } else if (strcmp(fontname, "I") == 0) { + current_paragraph->done_italic(); + } else if (strcmp(fontname, "BI") == 0) { + current_paragraph->done_bold(); + current_paragraph->done_italic(); + } else if (strcmp(fontname, "CR") == 0) { + current_paragraph->done_tt(); + } else if (strcmp(fontname, "CI") == 0) { + current_paragraph->done_italic(); + current_paragraph->done_tt(); + } else if (strcmp(fontname, "CB") == 0) { + current_paragraph->done_bold(); + current_paragraph->done_tt(); + } else if (strcmp(fontname, "CBI") == 0) { + current_paragraph->done_bold(); + current_paragraph->done_italic(); + current_paragraph->done_tt(); + } +} + +/* + * start_font - starts the font corresponding to name. + */ + +void html_printer::start_font (const char *fontname) +{ + if (strcmp(fontname, "R") == 0) { + current_paragraph->done_bold(); + current_paragraph->done_italic(); + current_paragraph->done_tt(); + } else if (strcmp(fontname, "B") == 0) { + current_paragraph->do_bold(); + } else if (strcmp(fontname, "I") == 0) { + current_paragraph->do_italic(); + } else if (strcmp(fontname, "BI") == 0) { + current_paragraph->do_bold(); + current_paragraph->do_italic(); + } else if (strcmp(fontname, "CR") == 0) { + if ((! fill_on) && (is_courier_until_eol()) && + is_line_start(! fill_on)) { + current_paragraph->do_pre(); + } + current_paragraph->do_tt(); + } else if (strcmp(fontname, "CI") == 0) { + if ((! fill_on) && (is_courier_until_eol()) && + is_line_start(! fill_on)) { + current_paragraph->do_pre(); + } + current_paragraph->do_tt(); + current_paragraph->do_italic(); + } else if (strcmp(fontname, "CB") == 0) { + if ((! fill_on) && (is_courier_until_eol()) && + is_line_start(! fill_on)) { + current_paragraph->do_pre(); + } + current_paragraph->do_tt(); + current_paragraph->do_bold(); + } else if (strcmp(fontname, "CBI") == 0) { + if ((! fill_on) && (is_courier_until_eol()) && + is_line_start(! fill_on)) { + current_paragraph->do_pre(); + } + current_paragraph->do_tt(); + current_paragraph->do_italic(); + current_paragraph->do_bold(); + } +} + +/* + * start_size - from is old font size, to is the new font size. + * The HTML elements <big> and <small> respectively + * increase and decrease the font size by 20%. We try and + * map these onto glyph sizes. + */ + +void html_printer::start_size (int from, int to) +{ + if (from < to) { + while (from < to) { + current_paragraph->do_big(); + from += SIZE_INCREMENT; + } + } else if (from > to) { + while (from > to) { + current_paragraph->do_small(); + from -= SIZE_INCREMENT; + } + } +} + +/* + * do_font - checks to see whether we need to alter the html font. + */ + +void html_printer::do_font (text_glob *g) +{ + /* + * check if the output_style.point_size has not been set yet + * this allow users to place .ps at the top of their troff files + * and grohtml can then treat the .ps value as the base font size (3) + */ + if (output_style.point_size == -1) { + output_style.point_size = pointsize; + } + + if (g->text_style.f != output_style.f) { + if (output_style.f != 0) { + end_font(output_style.f->get_name()); + } + output_style.f = g->text_style.f; + if (output_style.f != 0) { + start_font(output_style.f->get_name()); + } + } + if (output_style.point_size != g->text_style.point_size) { + do_sup_or_sub(g); + if ((output_style.point_size > 0) && + (g->text_style.point_size > 0)) { + start_size(output_style.point_size, g->text_style.point_size); + } + if (g->text_style.point_size > 0) { + output_style.point_size = g->text_style.point_size; + } + } + if (output_style.col != g->text_style.col) { + current_paragraph->done_color(); + output_style.col = g->text_style.col; + current_paragraph->do_color(&output_style.col); + } +} + +/* + * start_subscript - returns TRUE if, g, looks like a subscript start. + */ + +int html_printer::start_subscript (text_glob *g) +{ + int r = font::res; + int height = output_style.point_size*r/72; + + return ((output_style.point_size != 0) && + (output_vpos < g->minv) && + (output_vpos-height > g->maxv) && + (output_style.point_size > g->text_style.point_size)); +} + +/* + * start_superscript - returns TRUE if, g, looks like a superscript + * start. + */ + +int html_printer::start_superscript (text_glob *g) +{ + int r = font::res; + int height = output_style.point_size*r/72; + + return ((output_style.point_size != 0) && + (output_vpos > g->minv) && + (output_vpos-height < g->maxv) && + (output_style.point_size > g->text_style.point_size)); +} + +/* + * end_subscript - returns TRUE if, g, looks like the end of a + * subscript. + */ + +int html_printer::end_subscript (text_glob *g) +{ + int r = font::res; + int height = output_style.point_size*r/72; + + return ((output_style.point_size != 0) && + (g->minv < output_vpos) && + (output_vpos-height > g->maxv) && + (output_style.point_size < g->text_style.point_size)); +} + +/* + * end_superscript - returns TRUE if, g, looks like the end of a + * superscript. + */ + +int html_printer::end_superscript (text_glob *g) +{ + int r = font::res; + int height = output_style.point_size*r/72; + + return ((output_style.point_size != 0) && + (g->minv > output_vpos) && + (output_vpos-height < g->maxv) && + (output_style.point_size < g->text_style.point_size)); +} + +/* + * do_sup_or_sub - checks to see whether the next glyph is a + * subscript/superscript start/end and it calls the + * services of html-text to issue the appropriate tags. + */ + +void html_printer::do_sup_or_sub (text_glob *g) +{ + if (! suppress_sub_sup) { + if (start_subscript(g)) { + current_paragraph->do_sub(); + } else if (start_superscript(g)) { + current_paragraph->do_sup(); + } else if (end_subscript(g)) { + current_paragraph->done_sub(); + } else if (end_superscript(g)) { + current_paragraph->done_sup(); + } + } +} + +/* + * do_end_para - writes out the html text after shutting down the + * current paragraph. + */ + +void html_printer::do_end_para (text_glob *g) +{ + do_font(g); + current_paragraph->done_para(); + current_paragraph->remove_para_space(); + html.put_string(g->text_string+9); + output_vpos = g->minv; + output_hpos = g->maxh; + output_vpos_max = g->maxv; + suppress_sub_sup = FALSE; +} + +/* + * emit_html - write out the html text + */ + +void html_printer::emit_html (text_glob *g) +{ + do_font(g); + determine_space(g); + current_paragraph->do_emittext(g->text_string, g->text_length); + output_vpos = g->minv; + output_hpos = g->maxh; + output_vpos_max = g->maxv; + suppress_sub_sup = FALSE; +} + +/* + * flush_sbuf - flushes the current sbuf into the list of glyphs. + */ + +void html_printer::flush_sbuf() +{ + if (sbuf.length() > 0) { + int r=font::res; // resolution of the device + set_style(sbuf_style); + + if (overstrike_detected && (! is_bold(sbuf_style.f))) { + font *bold_font = make_bold(sbuf_style.f); + if (bold_font != 0) + sbuf_style.f = bold_font; + } + + page_contents->add(&sbuf_style, sbuf, line_number, + (sbuf_vpos - (sbuf_style.point_size * r / 72)), + sbuf_start_hpos, sbuf_vpos, sbuf_end_hpos); + output_hpos = sbuf_end_hpos; + output_vpos = sbuf_vpos; + last_sbuf_length = 0; + sbuf_prev_hpos = sbuf_end_hpos; + overstrike_detected = FALSE; + sbuf.clear(); + } +} + +void html_printer::set_line_thickness(const environment *env) +{ + line_thickness = env->size; +} + +void html_printer::draw(int code, int *p, int np, + const environment *env) +{ + switch (code) { + + case 'l': +# if 0 + if (np == 2) { + page_contents->add_line(&sbuf_style, + line_number, + env->hpos, env->vpos, + (env->hpos + p[0]), (env->vpos + p[1]), + line_thickness); + } else { + error("2 arguments required for line"); + } +# endif + break; + 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; + } + line_thickness = p[0]; + } + break; + } + + case 'P': + break; + case 'p': + break; + case 'E': + break; + case 'e': + break; + case 'C': + break; + case 'c': + break; + case 'a': + break; + case '~': + break; + case 'f': + break; + case 'F': + // fill with color env->fill + if (background != 0) + delete background; + background = new color; + *background = *env->fill; + break; + + default: + error("unrecognised drawing command '%1'", char(code)); + break; + } +} + +html_printer::html_printer() +: html(0, MAX_LINE_LENGTH), + no_of_printed_pages(0), + last_sbuf_length(0), + overstrike_detected(FALSE), + output_hpos(-1), + output_vpos(-1), + output_vpos_max(-1), + line_thickness(-1), + inside_font_style(0), + page_number(0), + header_indent(-1), + suppress_sub_sup(TRUE), + cutoff_heading(100), + indent(0), + table(0), + end_center(0), + end_tempindent(0), + next_tag(INLINE), + fill_on(TRUE), + max_linelength(-1), + linelength(0), + pageoffset(0), + troff_indent(0), + device_indent(0), + temp_indent(0), + pointsize(base_point_size), + line_number(0), + background(default_background), + seen_indent(FALSE), + next_indent(0), + seen_pageoffset(FALSE), + next_pageoffset(0), + seen_linelength(FALSE), + next_linelength(0), + seen_center(FALSE), + next_center(0), + seen_space(0), + seen_break(0), + current_column(0), + row_space(FALSE) +{ + file_list.add_new_file(xtmpfile()); + html.set_file(file_list.get_file()); + if (font::hor != 24) + fatal("horizontal motion quantum must be 24"); + if (font::vert != 40) + fatal("vertical motion quantum must be 40"); +#if 0 + // should be sorted html.. + if (font::res % (font::sizescale*72) != 0) + fatal("res must be a multiple of 72*sizescale"); +#endif + int r = font::res; + int point = 0; + while (r % 10 == 0) { + r /= 10; + point++; + } + res = r; + html.set_fixed_point(point); + space_glyph = name_to_glyph("space"); + space_width = font::hor; + paper_length = font::paperlength; + linelength = font::res*13/2; + if (paper_length == 0) + paper_length = 11*font::res; + + page_contents = new page(); +} + +/* + * add_to_sbuf - adds character code or name to the sbuf. + */ + +void html_printer::add_to_sbuf (glyph *g, const string &s) +{ + if (0 /* nullptr */ == sbuf_style.f) + return; + + const char *html_glyph = 0; + unsigned int code = sbuf_style.f->get_code(g); + + if (s.empty()) { + if (sbuf_style.f->contains(g)) + html_glyph = get_html_entity(sbuf_style.f->get_code(g)); + else + html_glyph = 0; + + if ((0 /* nullptr */ == html_glyph) && (code >= UNICODE_DESC_START)) + html_glyph = to_unicode(code); + } else + html_glyph = get_html_translation(sbuf_style.f, s); + + last_sbuf_length = sbuf.length(); + if (0 /* nullptr */ == html_glyph) + sbuf += ((char)code); + else + sbuf += html_glyph; +} + +int html_printer::sbuf_continuation (glyph *g, const char *name, + const environment *env, int w) +{ + /* + * lets see whether the glyph is closer to the end of sbuf + */ + if ((sbuf_end_hpos == env->hpos) + || ((sbuf_prev_hpos < sbuf_end_hpos) + && (env->hpos < sbuf_end_hpos) + && ((sbuf_end_hpos-env->hpos < env->hpos-sbuf_prev_hpos)))) { + add_to_sbuf(g, name); + sbuf_prev_hpos = sbuf_end_hpos; + sbuf_end_hpos += w + sbuf_kern; + return TRUE; + } else { + if ((env->hpos >= sbuf_end_hpos) + && ((sbuf_kern == 0) + || (sbuf_end_hpos - sbuf_kern != env->hpos))) { + /* + * lets see whether a space is needed or not + */ + + if (env->hpos-sbuf_end_hpos < space_width) { + add_to_sbuf(g, name); + sbuf_prev_hpos = sbuf_end_hpos; + sbuf_end_hpos = env->hpos + w; + return TRUE; + } + } + } + return FALSE; +} + +/* + * get_html_translation - given the position of the character and its + * name return the device encoding for such + * character. + */ + +const char *get_html_translation (font *f, const string &name) +{ + if ((0 /* nullptr */ == f) || name.empty()) + return 0; + else { + glyph *g = name_to_glyph((char *)(name + '\0').contents()); + if (f->contains(g)) + return get_html_entity(f->get_code(g)); + else + return 0; + } +} + +/* + * get_html_entity - given a Unicode character's code point, return an + * HTML entity that represents the character, if the + * character cannot represent itself in all contexts. + * the return value, if not a null pointer, is + * allocated in a static buffer and is only valid + * until the next call of this function. + */ +static const char *get_html_entity (unsigned int code) +{ + if (code < UNICODE_DESC_START) { + switch (code) { + case 0x0022: return """; + case 0x0026: return "&"; + case 0x003C: return "<"; + case 0x003E: return ">"; + default: return 0; + } + } else { + switch (code) { + case 0x00A0: return " "; + case 0x00A1: return "¡"; + case 0x00A2: return "¢"; + case 0x00A3: return "£"; + case 0x00A4: return "¤"; + case 0x00A5: return "¥"; + case 0x00A6: return "¦"; + case 0x00A7: return "§"; + case 0x00A8: return "¨"; + case 0x00A9: return "©"; + case 0x00AA: return "ª"; + case 0x00AB: return "«"; + case 0x00AC: return "¬"; + case 0x00AE: return "®"; + case 0x00AF: return "¯"; + case 0x00B0: return "°"; + case 0x00B1: return "±"; + case 0x00B2: return "²"; + case 0x00B3: return "³"; + case 0x00B4: return "´"; + case 0x00B5: return "µ"; + case 0x00B6: return "¶"; + case 0x00B7: return "·"; + case 0x00B8: return "¸"; + case 0x00B9: return "¹"; + case 0x00BA: return "º"; + case 0x00BB: return "»"; + case 0x00BC: return "¼"; + case 0x00BD: return "½"; + case 0x00BE: return "¾"; + case 0x00BF: return "¿"; + case 0x00C0: return "À"; + case 0x00C1: return "Á"; + case 0x00C2: return "Â"; + case 0x00C3: return "Ã"; + case 0x00C4: return "Ä"; + case 0x00C5: return "Å"; + case 0x00C6: return "Æ"; + case 0x00C7: return "Ç"; + case 0x00C8: return "È"; + case 0x00C9: return "É"; + case 0x00CA: return "Ê"; + case 0x00CB: return "Ë"; + case 0x00CC: return "Ì"; + case 0x00CD: return "Í"; + case 0x00CE: return "Î"; + case 0x00CF: return "Ï"; + case 0x00D0: return "Ð"; + case 0x00D1: return "Ñ"; + case 0x00D2: return "Ò"; + case 0x00D3: return "Ó"; + case 0x00D4: return "Ô"; + case 0x00D5: return "Õ"; + case 0x00D6: return "Ö"; + case 0x00D7: return "×"; + case 0x00D8: return "Ø"; + case 0x00D9: return "Ù"; + case 0x00DA: return "Ú"; + case 0x00DB: return "Û"; + case 0x00DC: return "Ü"; + case 0x00DD: return "Ý"; + case 0x00DE: return "Þ"; + case 0x00DF: return "ß"; + case 0x00E0: return "à"; + case 0x00E1: return "á"; + case 0x00E2: return "â"; + case 0x00E3: return "ã"; + case 0x00E4: return "ä"; + case 0x00E5: return "å"; + case 0x00E6: return "æ"; + case 0x00E7: return "ç"; + case 0x00E8: return "è"; + case 0x00E9: return "é"; + case 0x00EA: return "ê"; + case 0x00EB: return "ë"; + case 0x00EC: return "ì"; + case 0x00ED: return "í"; + case 0x00EE: return "î"; + case 0x00EF: return "ï"; + case 0x00F0: return "ð"; + case 0x00F1: return "ñ"; + case 0x00F2: return "ò"; + case 0x00F3: return "ó"; + case 0x00F4: return "ô"; + case 0x00F5: return "õ"; + case 0x00F6: return "ö"; + case 0x00F7: return "÷"; + case 0x00F8: return "ø"; + case 0x00F9: return "ù"; + case 0x00FA: return "ú"; + case 0x00FB: return "û"; + case 0x00FC: return "ü"; + case 0x00FD: return "ý"; + case 0x00FE: return "þ"; + case 0x00FF: return "ÿ"; + case 0x0152: return "Œ"; + case 0x0153: return "œ"; + case 0x0160: return "Š"; + case 0x0161: return "š"; + case 0x0178: return "Ÿ"; + case 0x0192: return "ƒ"; + case 0x0391: return "Α"; + case 0x0392: return "Β"; + case 0x0393: return "Γ"; + case 0x0394: return "Δ"; + case 0x0395: return "Ε"; + case 0x0396: return "Ζ"; + case 0x0397: return "Η"; + case 0x0398: return "Θ"; + case 0x0399: return "Ι"; + case 0x039A: return "Κ"; + case 0x039B: return "Λ"; + case 0x039C: return "Μ"; + case 0x039D: return "Ν"; + case 0x039E: return "Ξ"; + case 0x039F: return "Ο"; + case 0x03A0: return "Π"; + case 0x03A1: return "Ρ"; + case 0x03A3: return "Σ"; + case 0x03A4: return "Τ"; + case 0x03A5: return "Υ"; + case 0x03A6: return "Φ"; + case 0x03A7: return "Χ"; + case 0x03A8: return "Ψ"; + case 0x03A9: return "Ω"; + case 0x03B1: return "α"; + case 0x03B2: return "β"; + case 0x03B3: return "γ"; + case 0x03B4: return "δ"; + case 0x03B5: return "ε"; + case 0x03B6: return "ζ"; + case 0x03B7: return "η"; + case 0x03B8: return "θ"; + case 0x03B9: return "ι"; + case 0x03BA: return "κ"; + case 0x03BB: return "λ"; + case 0x03BC: return "μ"; + case 0x03BD: return "ν"; + case 0x03BE: return "ξ"; + case 0x03BF: return "ο"; + case 0x03C0: return "π"; + case 0x03C1: return "ρ"; + case 0x03C2: return "ς"; + case 0x03C3: return "σ"; + case 0x03C4: return "τ"; + case 0x03C5: return "υ"; + case 0x03C6: return "φ"; + case 0x03C7: return "χ"; + case 0x03C8: return "ψ"; + case 0x03C9: return "ω"; + case 0x03D1: return "ϑ"; + case 0x03D6: return "ϖ"; + case 0x2013: return "–"; + case 0x2014: return "—"; + case 0x2018: return "‘"; + case 0x2019: return "’"; + case 0x201A: return "‚"; + case 0x201C: return "“"; + case 0x201D: return "”"; + case 0x201E: return "„"; + case 0x2020: return "†"; + case 0x2021: return "‡"; + case 0x2022: return "•"; + case 0x2030: return "‰"; + case 0x2032: return "′"; + case 0x2033: return "″"; + case 0x2039: return "‹"; + case 0x203A: return "›"; + case 0x203E: return "‾"; + case 0x2044: return "⁄"; + case 0x20AC: return "€"; + case 0x2111: return "ℑ"; + case 0x2118: return "℘"; + case 0x211C: return "ℜ"; + case 0x2122: return "™"; + case 0x2135: return "ℵ"; + case 0x2190: return "←"; + case 0x2191: return "↑"; + case 0x2192: return "→"; + case 0x2193: return "↓"; + case 0x2194: return "↔"; + case 0x21D0: return "⇐"; + case 0x21D1: return "⇑"; + case 0x21D2: return "⇒"; + case 0x21D3: return "⇓"; + case 0x21D4: return "⇔"; + case 0x2200: return "∀"; + case 0x2202: return "∂"; + case 0x2203: return "∃"; + case 0x2205: return "∅"; + case 0x2207: return "∇"; + case 0x2208: return "∈"; + case 0x2209: return "∉"; + case 0x220B: return "∋"; + case 0x220F: return "∏"; + case 0x2211: return "∑"; + case 0x2212: return "−"; + case 0x2217: return "∗"; + case 0x221A: return "√"; + case 0x221D: return "∝"; + case 0x221E: return "∞"; + case 0x2220: return "∠"; + case 0x2227: return "∧"; + case 0x2228: return "∨"; + case 0x2229: return "∩"; + case 0x222A: return "∪"; + case 0x222B: return "∫"; + case 0x2234: return "∴"; + case 0x223C: return "∼"; + case 0x2245: return "≅"; + case 0x2248: return "≈"; + case 0x2260: return "≠"; + case 0x2261: return "≡"; + case 0x2264: return "≤"; + case 0x2265: return "≥"; + case 0x2282: return "⊂"; + case 0x2283: return "⊃"; + case 0x2284: return "⊄"; + case 0x2286: return "⊆"; + case 0x2287: return "⊇"; + case 0x2295: return "⊕"; + case 0x2297: return "⊗"; + case 0x22A5: return "⊥"; + case 0x22C5: return "⋅"; + case 0x2308: return "⌈"; + case 0x2309: return "⌉"; + case 0x230A: return "⌊"; + case 0x230B: return "⌋"; + case 0x2329: return "⟨"; + case 0x232A: return "⟩"; + case 0x25CA: return "◊"; + case 0x2660: return "♠"; + case 0x2663: return "♣"; + case 0x2665: return "♥"; + case 0x2666: return "♦"; + case 0x27E8: return "⟨"; + case 0x27E9: return "⟩"; + default: return to_unicode(code); + } + } +} + +/* + * overstrike - returns TRUE if the glyph (i, name) is going to + * overstrike a previous glyph in sbuf. If TRUE the font + * is changed to bold and the previous sbuf is flushed. + */ + +int html_printer::overstrike(glyph *g, const char *name, + const environment *env, int w) +{ + if ((env->hpos < sbuf_end_hpos) + || ((sbuf_kern != 0) && (sbuf_end_hpos - sbuf_kern < env->hpos))) + { + /* + * at this point we have detected an overlap + */ + if (overstrike_detected) { + /* already detected, remove previous glyph and use this glyph */ + sbuf.set_length(last_sbuf_length); + add_to_sbuf(g, name); + sbuf_end_hpos = env->hpos + w; + return TRUE; + } else { + /* first time we have detected an overstrike in the sbuf */ + sbuf.set_length(last_sbuf_length); /* remove previous glyph */ + if (! is_bold(sbuf_style.f)) + flush_sbuf(); + overstrike_detected = TRUE; + add_to_sbuf(g, name); + sbuf_end_hpos = env->hpos + w; + return TRUE; + } + } + return FALSE; +} + +/* + * set_char - adds a character into the sbuf if it is a continuation + * with the previous word otherwise flush the current sbuf + * and add character anew. + */ + +void html_printer::set_char(glyph *g, font *f, const environment *env, + int w, const char *name) +{ + style sty(f, env->size, env->height, env->slant, env->fontno, + *env->col); + if (sty.slant != 0) { + if (sty.slant > 80 || sty.slant < -80) { + error("slant of %1 degrees out of range", sty.slant); + sty.slant = 0; + } + } + if (((!sbuf.empty()) + && (sty == sbuf_style) + && (sbuf_vpos == env->vpos)) + && (sbuf_continuation(g, name, env, w) + || overstrike(g, name, env, w))) + return; + + flush_sbuf(); + if (0 /* nullptr */ == sbuf_style.f) + sbuf_style = sty; + add_to_sbuf(g, name); + sbuf_end_hpos = env->hpos + w; + sbuf_start_hpos = env->hpos; + sbuf_prev_hpos = env->hpos; + sbuf_vpos = env->vpos; + sbuf_style = sty; + sbuf_kern = 0; +} + +/* + * set_numbered_char - handle numbered characters. Negative values are + * interpreted as unbreakable spaces; the value + * (taken positive) gives the width. + */ + +void html_printer::set_numbered_char(int num, const environment *env, + int *widthp) +{ + int nbsp_width = 0; + if (num < 0) { + nbsp_width = -num; + num = 160; // + } + glyph *g = number_to_glyph(num); + int fn = env->fontno; + if (fn < 0 || fn >= nfonts) { + error("invalid font position '%1'", fn); + return; + } + font *f = font_table[fn]; + if (f == 0) { + error("no font mounted at position %1", fn); + return; + } + if (!f->contains(g)) { + error("font '%1' does not contain numbered character %2", + f->get_name(), + num); + return; + } + int w; + if (nbsp_width) + w = nbsp_width; + else + w = f->get_width(g, env->size); + w = round_width(w); + if (widthp) + *widthp = w; + set_char(g, f, env, w, 0); +} + +glyph *html_printer::set_char_and_width(const char *nm, + const environment *env, + int *widthp, font **f) +{ + glyph *g = name_to_glyph(nm); + int fn = env->fontno; + if (fn < 0 || fn >= nfonts) { + error("invalid font position '%1'", fn); + return UNDEFINED_GLYPH; + } + *f = font_table[fn]; + if (*f == 0) { + error("no font mounted at position %1", fn); + return UNDEFINED_GLYPH; + } + if (!(*f)->contains(g)) { + if (nm[0] != '\0' && nm[1] == '\0') + error("font '%1' does not contain ordinary character '%2'", + (*f)->get_name(), nm[0]); + else + error("font '%1' does not contain special character '%2'", + (*f)->get_name(), nm); + return UNDEFINED_GLYPH; + } + int w = (*f)->get_width(g, env->size); + w = round_width(w); + if (widthp) + *widthp = w; + return g; +} + +/* + * write_title - writes the title to this document + */ + +void html_printer::write_title (int in_head) +{ + if (title.has_been_found) { + if (in_head) { + html.put_string("<title>"); + html.put_string(title.text); + html.put_string("</title>").nl().nl(); + } else { + title.has_been_written = TRUE; + if (title.with_h1) { + if (dialect == xhtml) + html.put_string("<h1>"); + else + html.put_string("<h1 align=\"center\">"); + html.put_string(title.text); + html.put_string("</h1>").nl().nl(); + } + } + } else if (in_head) { + // place empty title tags to help conform to 'tidy' + html.put_string("<title></title>").nl(); + } +} + +/* + * write_rule - emits HTML rule element if the auto_rule is TRUE. + */ + +static void write_rule (void) +{ + if (auto_rule) { + if (dialect == xhtml) + fputs("<hr/>\n", stdout); + else + fputs("<hr>\n", stdout); + } +} + +void html_printer::begin_page(int n) +{ + page_number = n; +#if defined(DEBUGGING) + html.begin_comment("Page: ") + .put_string(i_to_a(page_number)).end_comment();; +#endif + no_of_printed_pages++; + + output_style.f = 0; + output_style.point_size= -1; + output_space_code = 32; + output_draw_point_size = -1; + output_line_thickness = -1; + output_hpos = -1; + output_vpos = -1; + output_vpos_max = -1; + current_paragraph = new html_text(&html, dialect); + do_indent(get_troff_indent(), pageoffset, linelength); + current_paragraph->do_para("", FALSE); +} + +void html_printer::end_page(int) +{ + flush_sbuf(); + flush_page(); +} + +font *html_printer::make_font(const char *nm) +{ + return html_font::load_html_font(nm); +} + +void html_printer::do_body (void) +{ + if (0 /* nullptr */ == background) + fputs("<body>\n\n", stdout); + else { + char buf[(INT_HEXDIGITS * 3) + 1]; + unsigned int r, g, b; + + background->get_rgb(&r, &g, &b); + // we have to scale 0..0xFFFF to 0..0xFF + sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101); + + fputs("<body bgcolor=\"#", stdout); + fputs(buf, stdout); + fputs("\">\n\n", stdout); + } +} + +/* + * emit_link - generates: <a href="to">name</a> + */ + +void html_printer::emit_link (const string &to, const char *name) +{ + fputs("<a href=\"", stdout); + fputs(to.contents(), stdout); + fputs("\">", stdout); + fputs(name, stdout); + fputs("</a>", stdout); +} + +/* + * write_navigation - writes out the links which navigate between + * file fragments. + */ + +void html_printer::write_navigation (const string &top, + const string &prev, + const string &next, + const string ¤t) +{ + int need_bar = FALSE; + + if (multiple_files) { + current_paragraph->done_para(); + write_rule(); + if (groff_sig) + fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n" + "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n" + "<colgroup><col class=\"left\"></col>" + "<col class=\"right\"></col></colgroup>\n" + "<tr><td class=\"left\">", stdout); + handle_valid_flag(FALSE); + fputs("[ ", stdout); + if ((strcmp(prev.contents(), "") != 0) + && prev != top + && prev != current) { + emit_link(prev, "prev"); + need_bar = TRUE; + } + if ((strcmp(next.contents(), "") != 0) + && next != top + && next != current) { + if (need_bar) + fputs(" | ", stdout); + emit_link(next, "next"); + need_bar = TRUE; + } + if (top != "<standard input>" + && (strcmp(top.contents(), "") != 0) + && top != current) { + if (need_bar) + fputs(" | ", stdout); + emit_link(top, "top"); + } + fputs(" ]\n", stdout); + if (groff_sig) { + fputs("</td><td class=\"right\"><i><small>" + "This document was produced using " + "<a href=\"http://www.gnu.org/software/groff/\">" + "groff-", stdout); + fputs(Version_string, stdout); + fputs("</a>.</small></i></td></tr></table>\n", stdout); + } + write_rule(); + } +} + +/* + * do_file_components - scan the file list copying each temporary file + * in turn. This has twofold use: firstly to emit + * section heading links, between file fragments + * if required and secondly to generate jobname + * file fragments if required. + */ + +void html_printer::do_file_components (void) +{ + int fragment_no = 1; + string top; + string prev; + string next; + string current; + + file_list.start_of_list(); + top = string(job_name); + if (dialect == xhtml) + top += string(".xhtml"); + else + top += string(".html"); + top += '\0'; + next = file_list.next_file_name(); + next += '\0'; + current = next; + while (file_list.get_file() != 0) { + if (fseek(file_list.get_file(), 0L, 0) < 0) + fatal("fseek on temporary file failed"); + html.copy_file(file_list.get_file()); + fclose(file_list.get_file()); + file_list.move_next(); + if (file_list.is_new_output_file()) { +#ifdef LONG_FOR_TIME_T + long t; +#else + time_t t; +#endif + + if (fragment_no > 1) + write_navigation(top, prev, next, current); + prev = current; + current = next; + next = file_list.next_file_name(); + next += '\0'; + string split_file = file_list.file_name(); + split_file += '\0'; + fflush(stdout); + if (!freopen(split_file.contents(), "w", stdout)) { + fatal("unable to reopen standard output stream: %1", + strerror(errno)); + } + fragment_no++; + if (dialect == xhtml) + writeHeadMetaStyle(); + + if (do_write_creator_comment) { + html.begin_comment("Creator : ") + .put_string("groff ") + .put_string("version ") + .put_string(Version_string) + .end_comment(); + } + + if (do_write_date_comment) { + t = current_time(); + html.begin_comment("CreationDate: ") + .put_string(ctime(&t), strlen(ctime(&t))-1) + .end_comment(); + } + + if (dialect == html4) + writeHeadMetaStyle(); + + html.put_string("<title>"); + html.put_string(split_file.contents()); + html.put_string("</title>").nl().nl(); + + fputs(head_info.contents(), stdout); + fputs("</head>\n", stdout); + write_navigation(top, prev, next, current); + } + if (file_list.are_links_required()) + header.write_headings(stdout, TRUE); + } + if (fragment_no > 1) + write_navigation(top, prev, next, current); + else { + assert(current_paragraph != 0); + current_paragraph->done_para(); + write_rule(); + if (valid_flag) { + if (groff_sig) + fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n" + "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n" + "<colgroup><col class=\"left\"></col>" + "<col class=\"right\"></col></colgroup>\n" + "<tr><td class=\"left\">", stdout); + handle_valid_flag(TRUE); + if (groff_sig) { + fputs("</td><td class=\"right\"><i><small>" + "This document was produced using " + "<a href=\"http://www.gnu.org/software/groff/\">" + "groff-", stdout); + fputs(Version_string, stdout); + fputs("</a>.</small></i></td></tr></table>\n", stdout); + } + write_rule(); + } + } +} + +/* + * writeHeadMetaStyle - emits the <head> <meta> and <style> tags and + * related information. + */ + +void html_printer::writeHeadMetaStyle (void) +{ + if (dialect == html4) { + fputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional" + "//EN\"\n", stdout); + fputs("\"http://www.w3.org/TR/html4/loose.dtd\">\n", stdout); + fputs("<html>\n", stdout); + fputs("<head>\n", stdout); + fputs("<meta name=\"generator\" " + "content=\"groff -Thtml, see www.gnu.org\">\n", stdout); + fputs("<meta http-equiv=\"Content-Type\" " + "content=\"text/html; charset=US-ASCII\">\n", stdout); + fputs("<meta name=\"Content-Style\" content=\"text/css\">\n", + stdout); + fputs("<style type=\"text/css\">\n", stdout); + } + else { + fputs("<?xml version=\"1.0\" encoding=\"us-ascii\"?>\n", stdout); + fputs("<!DOCTYPE html PUBLIC \"-//W3C//" + "DTD XHTML 1.1 plus MathML 2.0//EN\"\n", stdout); + fputs(" \"http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd\"\n", + stdout); + fputs(" [<!ENTITY mathml \"http://www.w3.org/1998/Math/" + "MathML\">]>\n", stdout); + + fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\" " + "xml:lang=\"en\">\n", stdout); + fputs("<head>\n", stdout); + fputs("<meta name=\"generator\" " + "content=\"groff -Txhtml, see www.gnu.org\"/>\n", stdout); + fputs("<meta http-equiv=\"Content-Type\" " + "content=\"text/html; charset=US-ASCII\"/>\n", stdout); + fputs("<meta name=\"Content-Style\" content=\"text/css\"/>\n", + stdout); + fputs("<style type=\"text/css\">\n", stdout); + fputs(" .center { text-align: center }\n", stdout); + fputs(" .right { text-align: right }\n", stdout); + } + fputs(" p { margin-top: 0; margin-bottom: 0; " + "vertical-align: top }\n", stdout); + fputs(" pre { margin-top: 0; margin-bottom: 0; " + "vertical-align: top }\n", stdout); + fputs(" table { margin-top: 0; margin-bottom: 0; " + "vertical-align: top }\n", stdout); + fputs(" h1 { text-align: center }\n", stdout); + fputs("</style>\n", stdout); +} + +html_printer::~html_printer() +{ +#ifdef LONG_FOR_TIME_T + long t; +#else + time_t t; +#endif + + if (current_paragraph) + current_paragraph->flush_text(); + html.end_line(); + html.set_file(stdout); + + if (dialect == xhtml) + writeHeadMetaStyle(); + + if (do_write_creator_comment) { + html.begin_comment("Creator : ") + .put_string("groff ") + .put_string("version ") + .put_string(Version_string) + .end_comment(); + } + + if (do_write_date_comment) { + t = current_time(); + html.begin_comment("CreationDate: ") + .put_string(ctime(&t), strlen(ctime(&t))-1) + .end_comment(); + } + + if (dialect == html4) + writeHeadMetaStyle(); + + write_title(TRUE); + head_info += '\0'; + fputs(head_info.contents(), stdout); + fputs("</head>\n", stdout); + do_body(); + + write_title(FALSE); + header.write_headings(stdout, FALSE); + write_rule(); +#if defined(DEBUGGING) + html.begin_comment("Total number of pages: ") + .put_string(i_to_a(no_of_printed_pages)).end_comment(); +#endif + html.end_line(); + html.end_line(); + + if (multiple_files) { + fputs("</body>\n", stdout); + fputs("</html>\n", stdout); + do_file_components(); + } else { + do_file_components(); + fputs("</body>\n", stdout); + fputs("</html>\n", stdout); + } +} + +/* + * get_str - returns a duplicate of string, s. The duplicate + * string is terminated at the next ',' or ']'. + */ + +static char *get_str (const char *s, char **n) +{ + int i = 0; + char *v; + + while ((s[i] != (char)0) && (s[i] != ',') && (s[i] != ']')) + i++; + if (i>0) { + v = new char[i+1]; + memcpy(v, s, i+1); + v[i] = (char)0; + if (s[i] == ',') + (*n) = (char *)&s[i+1]; + else + (*n) = (char *)&s[i]; + return v; + } + if (s[i] == ',') + (*n) = (char *)&s[1]; + else + (*n) = (char *)s; + return 0; +} + +/* + * make_val - creates a string from if s is a null pointer. + */ + +char *make_val (char *s, int v, char *id, char *f, char *l) +{ + if (0 /* nullptr */ == s) { + char buf[30]; + + sprintf(buf, "%d", v); + return strsave(buf); + } + else { + /* + * check that value, s, is the same as, v. + */ + char *t = s; + + while (*t == '=') + t++; + if (atoi(t) != v) { + if (0 /* nullptr */ == f) + f = (char *)"stdin"; + if (0 /* nullptr */ == l) + l = (char *)"<none>"; + fprintf(stderr, "%s:%s: grohtml assertion failed at id%s; " + "expected %d, got %s\n", f, l, id, v, s); + } + return s; + } +} + +/* + * handle_assertion - handles the assertions created via .www:ASSERT + * in www.tmac. See www.tmac for examples. This + * method should be called as we are parsing the + * ditroff input. It checks the x, y position + * assertions. It does _not_ check the troff state + * assertions as these are unknown at this point. + */ + +void html_printer::handle_assertion (int minv, int minh, + int maxv, int maxh, const char *s) +{ + char *n; + char *cmd = get_str(s, &n); + char *id = get_str(n, &n); + char *val = get_str(n, &n); + char *file= get_str(n, &n); + char *line= get_str(n, &n); + + if (strcmp(cmd, "assertion:[x") == 0) + as.addx(cmd, id, make_val(val, minh, id, file, line), file, line); + else if (strcmp(cmd, "assertion:[y") == 0) + as.addy(cmd, id, make_val(val, minv, id, file, line), file, line); + else + if (strncmp(cmd, "assertion:[", strlen("assertion:[")) == 0) + page_contents->add_tag(&sbuf_style, string(s), + line_number, minv, minh, maxv, maxh); +} + +/* + * build_state_assertion - builds the troff state assertions. + */ + +void html_printer::handle_state_assertion (text_glob *g) +{ + if (g != 0 && g->is_a_tag() + && (strncmp(g->text_string, "assertion:[", 11) == 0)) { + char *n = (char *)&g->text_string[11]; + char *cmd = get_str(n, &n); + char *val = get_str(n, &n); + (void)get_str(n, &n); // unused + char *file= get_str(n, &n); + char *line= get_str(n, &n); + + as.build(cmd, val, file, line); + } +} + +/* + * special - handle all x X requests from troff. For post-html they + * allow users to pass raw HTML commands, turn auto linked + * headings off/on, and so forth. + */ + +void html_printer::special(char *s, const environment *env, char type) +{ + if (type != 'p') + return; + if (s != 0) { + flush_sbuf(); + if (env->fontno >= 0) { + style sty(get_font_from_index(env->fontno), env->size, + env->height, env->slant, env->fontno, *env->col); + sbuf_style = sty; + } + + if (strncmp(s, "html:", 5) == 0) { + int r=font::res; /* resolution of the device */ + font *f=sbuf_style.f; + + if (0 /* nullptr */ == f) + f = font::load_font("TR"); + + /* + * pass rest of string through to html output during flush + */ + page_contents->add_and_encode(&sbuf_style, string(&s[5]), + line_number, + env->vpos-env->size*r/72, env->hpos, + env->vpos , env->hpos, + FALSE); + + /* + * assume that the html command has no width, if it does then + * hopefully troff will have fudged this in a macro by requesting + * that the formatting move right by the appropriate amount. + */ + } else if ((strncmp(s, "html</p>:", 9) == 0) || + (strncmp(s, "html<?p>:", 9) == 0) || + (strncmp(s, "math<?p>:", 9) == 0)) { + int r=font::res; /* resolution of the device */ + font *f=sbuf_style.f; + string t; + + if (0 /* nullptr */ == f) + f = font::load_font("TR"); + + if (strncmp(s, "math<?p>:", 9) == 0) { + if (strncmp((char *)&s[9], "<math>", 6) == 0) { + s[9] = '\0'; + t = s; + t += "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">"; + t += (char *)&s[15]; + t += '\0'; + s = (char *)&t[0]; + } + } + + /* + * need to pass all of string through to html output during flush + */ + page_contents->add_and_encode(&sbuf_style, string(s), + line_number, + env->vpos-env->size*r/72, env->hpos, + env->vpos , env->hpos, + TRUE); + + /* + * assume that the html command has no width, if it does then + * hopefully troff will have fudged this in a macro by + * requesting that the formatting move right by the appropriate + * amount. + */ + + } else if (strncmp(s, "index:", 6) == 0) { + cutoff_heading = atoi(&s[6]); + } else if (strncmp(s, "assertion:[", 11) == 0) { + int r=font::res; /* resolution of the device */ + + handle_assertion(env->vpos-env->size*r/72, env->hpos, + env->vpos, env->hpos, s); + } + } +} + +/* + * devtag - handles device troff tags sent from the 'troff'. + * These include the troff state machine tags: + * .br, .sp, .in, .tl, .ll etc + * + * (see man 5 grohtml_tags). + */ + +void html_printer::devtag (char *s, const environment *env, char type) +{ + if (type != 'p') + return; + + if (s != 0) { + flush_sbuf(); + if (env->fontno >= 0) { + style sty(get_font_from_index(env->fontno), env->size, + env->height, env->slant, env->fontno, *env->col); + sbuf_style = sty; + } + + if (strncmp(s, "devtag:", strlen("devtag:")) == 0) { + int r=font::res; /* resolution of the device */ + + page_contents->add_tag(&sbuf_style, string(s), + line_number, + env->vpos-env->size*r/72, env->hpos, + env->vpos , env->hpos); + } + } +} + + +/* + * taken from number.cpp in src/roff/troff, [hunits::hunits(units x)] + */ + +int html_printer::round_width(int x) +{ + int r = font::hor; + int n; + + // don't depend on rounding direction for division of negative ints + if (r == 1) + n = x; + else + n = (x < 0 + ? -((-x + r/2 - 1)/r) + : (x + r/2 - 1)/r); + return n * r; +} + +/* + * handle_valid_flag - emits a valid XHTML 1.1 or HTML 4.01 button, + * provided -V was supplied on the command line. + */ + +void html_printer::handle_valid_flag (int needs_para) +{ + if (valid_flag) { + if (needs_para) + fputs("<p>", stdout); + if (dialect == xhtml) + fputs("<a href=\"http://validator.w3.org/check?uri=referer\">" + "<img src=\"http://www.w3.org/Icons/valid-xhtml11-blue\" " + "alt=\"Valid XHTML 1.1 Transitional\" " + "height=\"31\" width=\"88\" /></a>\n", stdout); + else + fputs("<a href=\"http://validator.w3.org/check?uri=referer\">" + "<img src=\"http://www.w3.org/Icons/valid-html401-blue\" " + "alt=\"Valid HTML 4.01 Transitional\" " + "height=\"31\" width=\"88\"></a>\n", stdout); + if (needs_para) + fputs("</p>", stdout); + } +} + +int main(int argc, char **argv) +{ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + int c; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((c = getopt_long(argc, argv, + "a:bCdD:eF:g:Ghi:I:j:lno:prs:S:vVx:y", long_options, NULL)) + != EOF) + switch(c) { + case 'a': + /* text antialiasing bits - handled by pre-html */ + break; + case 'b': + // set background color to white + default_background = new color; + default_background->set_gray(color::MAX_COLOR_VAL); + break; + case 'C': + // Don't write CreationDate HTML comments. + do_write_date_comment = FALSE; + break; + case 'd': + /* handled by pre-html */ + break; + case 'D': + /* handled by pre-html */ + break; + case 'e': + /* handled by pre-html */ + break; + case 'F': + font::command_line_font_dir(optarg); + break; + case 'g': + /* graphic antialiasing bits - handled by pre-html */ + break; + case 'G': + // Don't write Creator HTML comments. + do_write_creator_comment = FALSE; + break; + case 'h': + /* do not use the Hn headings of html, but manufacture our own */ + manufacture_headings = TRUE; + break; + case 'i': + /* handled by pre-html */ + break; + case 'I': + /* handled by pre-html */ + break; + case 'j': + multiple_files = TRUE; + job_name = optarg; + break; + case 'l': + auto_links = FALSE; + break; + case 'n': + simple_anchors = TRUE; + break; + case 'o': + /* handled by pre-html */ + break; + case 'p': + /* handled by pre-html */ + break; + case 'r': + auto_rule = FALSE; + break; + case 's': + base_point_size = atoi(optarg); + break; + case 'S': + split_level = atoi(optarg) + 1; + break; + case 'v': + printf("GNU post-grohtml (groff) version %s\n", Version_string); + exit(0); + break; + case 'V': + valid_flag = TRUE; + break; + case 'x': + if (strcmp(optarg, "x") == 0) { + dialect = xhtml; + simple_anchors = TRUE; + } else if (strcmp(optarg, "4") == 0) + dialect = html4; + else + warning("unsupported HTML dialect: '%1'", optarg); + break; + case 'y': + groff_sig = TRUE; + break; + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0 == "unhandled getopt_long return value"); + } + if (optind >= argc) { + do_file("-"); + } else { + for (int i = optind; i < argc; i++) + do_file(argv[i]); + } + return 0; +} + +static void usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-bCGhlnrVy] [-F font-directory] [-j output-stem]" +" [-s base-type-size] [-S heading-level] [-x html-dialect] [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s --help\n", + program_name, program_name, program_name); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/devices/grolbp/charset.h b/src/devices/grolbp/charset.h new file mode 100644 index 0000000..c4e43a0 --- /dev/null +++ b/src/devices/grolbp/charset.h @@ -0,0 +1,89 @@ +// -*- C++ -*- +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + Written by Francisco Andrés Verdú <pandres@dragonet.es> 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 <http://www.gnu.org/licenses/>. */ + +// Definition of the WP54 character set + +unsigned char symset[] = { +0x57,0x50,0x35,0x34,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61, +0x72,0x64,0x65,0x2d,0x42,0x6f,0x6f,0x6b,0x00,0x41,0x76, +0x61,0x6e,0x74,0x47,0x61,0x72,0x64,0x65,0x2d,0x44,0x65, +0x6d,0x69,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61,0x72, +0x64,0x65,0x2d,0x42,0x6f,0x6f,0x6b,0x4f,0x62,0x6c,0x69, +0x71,0x75,0x65,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61, +0x72,0x64,0x65,0x2d,0x44,0x65,0x6d,0x69,0x4f,0x62,0x6c, +0x69,0x71,0x75,0x65,0x00,0x42,0x6f,0x6f,0x6b,0x6d,0x61, +0x6e,0x2d,0x4c,0x69,0x67,0x68,0x74,0x00,0x42,0x6f,0x6f, +0x6b,0x6d,0x61,0x6e,0x2d,0x44,0x65,0x6d,0x69,0x00,0x42, +0x6f,0x6f,0x6b,0x6d,0x61,0x6e,0x2d,0x4c,0x69,0x67,0x68, +0x74,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x42,0x6f,0x6f, +0x6b,0x6d,0x61,0x6e,0x2d,0x44,0x65,0x6d,0x69,0x49,0x74, +0x61,0x6c,0x69,0x63,0x00,0x43,0x65,0x6e,0x74,0x75,0x72, +0x79,0x53,0x63,0x68,0x6c,0x62,0x6b,0x2d,0x52,0x6f,0x6d, +0x61,0x6e,0x00,0x43,0x65,0x6e,0x74,0x75,0x72,0x79,0x53, +0x63,0x68,0x6c,0x62,0x6b,0x2d,0x42,0x6f,0x6c,0x64,0x00, +0x43,0x65,0x6e,0x74,0x75,0x72,0x79,0x53,0x63,0x68,0x6c, +0x62,0x6b,0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x43, +0x65,0x6e,0x74,0x75,0x72,0x79,0x53,0x63,0x68,0x6c,0x62, +0x6b,0x2d,0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,0x69, +0x63,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x52,0x6f,0x6d, +0x61,0x6e,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x42,0x6f, +0x6c,0x64,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x49,0x74, +0x61,0x6c,0x69,0x63,0x00,0x44,0x75,0x74,0x63,0x68,0x2d, +0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,0x69,0x63,0x00, +0x53,0x77,0x69,0x73,0x73,0x00,0x53,0x77,0x69,0x73,0x73, +0x2d,0x42,0x6f,0x6c,0x64,0x00,0x53,0x77,0x69,0x73,0x73, +0x2d,0x4f,0x62,0x6c,0x69,0x71,0x75,0x65,0x00,0x53,0x77, +0x69,0x73,0x73,0x2d,0x42,0x6f,0x6c,0x64,0x4f,0x62,0x6c, +0x69,0x71,0x75,0x65,0x00,0x53,0x77,0x69,0x73,0x73,0x2d, +0x4e,0x61,0x72,0x72,0x6f,0x77,0x00,0x53,0x77,0x69,0x73, +0x73,0x2d,0x4e,0x61,0x72,0x72,0x6f,0x77,0x2d,0x42,0x6f, +0x6c,0x64,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,0x4e,0x61, +0x72,0x72,0x6f,0x77,0x2d,0x4f,0x62,0x6c,0x69,0x71,0x75, +0x65,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,0x4e,0x61,0x72, +0x72,0x6f,0x77,0x2d,0x42,0x6f,0x6c,0x64,0x4f,0x62,0x6c, +0x69,0x71,0x75,0x65,0x00,0x5a,0x61,0x70,0x66,0x43,0x61, +0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x2d, +0x52,0x6f,0x6d,0x61,0x6e,0x00,0x5a,0x61,0x70,0x66,0x43, +0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63, +0x2d,0x42,0x6f,0x6c,0x64,0x00,0x5a,0x61,0x70,0x66,0x43, +0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63, +0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x5a,0x61,0x70, +0x66,0x43,0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68, +0x69,0x63,0x2d,0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c, +0x69,0x63,0x00,0x5a,0x61,0x70,0x66,0x43,0x68,0x61,0x6e, +0x63,0x65,0x72,0x79,0x2d,0x4d,0x65,0x64,0x69,0x75,0x6d, +0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x00,0x09,0x00,0x0A, +0x00,0x0B,0x00,0x0E,0x00,0x14,0x00,0x17,0x00,0x18,0x00, +0x1F,0x00,0x20,0x00,0x36,0x00,0x37,0x00,0x38,0x00,0x45,0x00, +0x47,0x00,0x48,0x00,0x80,0x00,0x82,0x00,0x83,0x00,0x84, +0x00,0x85,0x00,0x87,0x00,0x8B,0x00,0x8C,0x00,0x8D,0x00,0x8E, +0x00,0x8F,0x00,0x90,0x00,0x91,0x00,0x92,0x00,0x95,0x00,0x96, +0x00,0x97,0x00,0x98,0x00,0x99,0x00,0x9C,0x00,0x9E,0x00, +0x9F,0x00,0xA0,0x00,0xA1,0x00,0xA2,0x00,0xA3,0x00,0xCB,0x00, +0xCC,0x00,0xCD,0x00,0xCE,0x00,0xD1,0x00,0xD3,0x00,0xD4, +0x00,0xD5,0x00,0xD6,0x00,0xFA,0x00,0xFB,0x00,0xFC,0x00,0xFD, +0x00,0xCF,0x00,0x26,0x00,0x7E,0x03,0x05,0x00,0xA5,0x00, +0xA6,0x00,0xA8,0x00,0xAA,0x00,0xAD,0x00,0xAE,0x00,0xAF,0x00, +0xB0,0x00,0xB1,0x00,0xB2,0x00,0xB3,0x00,0xB5,0x00,0xB6,0x00, +0xB8,0x00,0xB9,0x00,0xBA,0x00,0xBB,0x00,0xBC,0x00,0xBE, +0x00,0xBF,0x00,0xC0,0x00,0xC1,0x00,0xC6,0x00,0xDC,0x00,0xEB, +0x00,0xEC,0x00,0xF2,0x00,0xF3,0x00,0x15,0x00,0x16,0x00, +0x86 +}; diff --git a/src/devices/grolbp/grolbp.1.man b/src/devices/grolbp/grolbp.1.man new file mode 100644 index 0000000..c8cda76 --- /dev/null +++ b/src/devices/grolbp/grolbp.1.man @@ -0,0 +1,504 @@ +'\" t +.TH grolbp @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +grolbp \- +.I groff +output driver for Canon CaPSL printers +. +. +.\" Modified from grolj4 man page by Francisco Andrés Verdú +.\" <pandres@dragonet.es> for the grolbp program. +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1994-2020 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_grolbp_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY grolbp +.RB [ \-l ] +.RB [ \-c\~\c +.IR num-copies ] +.RB [ \-F\~\c +.IR font-directory ] +.RB [ \-o\~\c +.IR orientation ] +.RB [ \-p\~\c +.IR paper-format ] +.RB [ \-w\~\c +.IR width ] +.RI [ file\~ .\|.\|.] +. +.SY grolbp +[\c +.BI \-\-copies= num-copies\c +] [\c +.BI \-\-fontdir= font-directory\c +] [\c +.B \-\-landscape\c +] [\c +.BI \-\-linewidth= width\c +] [\c +.BI \-\-orientation= orientation\c +] [\c +.BI \-\-papersize= paper-format\c +] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY grolbp +.B \-h +. +.SY grolbp +.B \-\-help +.YS +. +. +.SY grolbp +.B \-v +. +.SY grolbp +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +This GNU +.I roff +output driver translates the output of +.MR @g@troff @MAN1EXT@ +into a CaPSL and VDM format suitable for Canon LBP-4 and LBP-8 printers. +. +Normally, +.I grolbp +is invoked by +.MR groff @MAN1EXT@ +when the latter is given the +.RB \[lq] \-T\~lbp \[rq] +option. +. +(In this installation, +.B @DEVICE@ +is the default output device.) +. +Use +.IR groff 's +.B \-P +option to pass any options shown above to +.IR grolbp . +. +If no +.I file +arguments are given, +or if +.I file +is \[lq]\-\[rq], +.I grolbp +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.\" ==================================================================== +.SS Typefaces +.\" ==================================================================== +. +The driver supports the Dutch, +Swiss, +and Swiss-Narrow scalable typefaces, +each in the regular, +bold, +italic, +and bold-italic styles. +. +Additionally, +the bitmapped, +monospaced Courier and Elite typefaces are available in regular, +bold, +and +italic styles; +Courier at 8 and 12 points, +Elite at 8 and 10 points. +. +The following chart summarizes the +.I groff +font names used to access them. +. +. +.P +.TS +tab(|) allbox center; +Cb Cb Cb Cb Cb +L L L L L +. +Typeface | Roman | Bold | Italic | Bold-Italic +Dutch | TR | TB | TI | TBI +Swiss | HR | HB | HI | HBI +Swiss Narrow | HNR | HNB | HNI | HNBI +Courier | CR | CB | CI | +Elite | ER | EB | EI | +.TE +. +. +.\" ==================================================================== +.SS "Paper format, orientation, and device description file" +.\" ==================================================================== +. +.I grolbp +supports paper formats +.RB \[lq] A4 \[rq], +.RB \[lq] letter \[rq], +.RB \[lq] legal \[rq], +and +.RB \[lq] executive \[rq]. +. +These are matched case-insensitively. +. +The +.BR \-p , +.B \-\-papersize +option overrides any setting in the device description file +.IR DESC . +. +If neither specifies a paper format, +A4 is assumed. +. +. +.P +In its +.I DESC +file, +.I grolbp +(case-insensitively) recognizes an +.B orientation +directive accepting one mandatory argument, +.B portrait +or +.BR landscape . +. +The first valid orientation directive encountered controls. +.\" XXX: This is inconsistent with other description file processing. +. +The +.BR \-l , +.BR \-o , +and +.B \-\-orientation +command-line options +override any setting in +.IR DESC . +. +If none of the foregoing specify the orientation, +portrait is assumed. +. +. +.\" ==================================================================== +.SS "Font description files" +.\" ==================================================================== +. +In addition to the font description file directives documented in +.MR groff_font @MAN5EXT@ , +.I grolbp +recognizes +.BR lbpname , +which maps the +.I groff +font name to the font name used internally by the printer. +. +Its syntax is as follows. +.RS +.EX +.RI lbpname\~ printer-font-name +.EE +.RE +. +. +.BR lbpname 's +argument is case-sensitive. +. +The printer's font names are encoded as follows. +. +. +.P +For bitmapped fonts, +.I printer-font_name +has the form +.RS +.EX +.RI N\[la] base-font-name \[ra]\[la] font-style \[ra] +.EE +.RE +.I base-font-name +is the font name as it appears in the printer's font listings without +the first letter, +up to +(but not including) +the font size. +. +.I font-style +can be one of the letters +.BR R , +.BR I , +or +.BR B , +.\" The bold-italic style apparently was not supported for bitmap fonts. +indicating the roman, +italic, +and bold styles, +respectively. +. +For instance, +if the printer's \[lq]font listing A\[rq] +shows \[lq]Nelite12I.ISO_USA\[rq], +the corresponding entry in the +.I groff +font description file is +.RS +.EX +lbpname NeliteI +.EE +.RE +. +You may need to modify +.I grolbp +to add support for new bitmapped fonts, +since the available font names and font sizes of bitmapped fonts +(as documented above) +are hard-coded into the program. +. +. +.P +For scalable fonts, +.I printer-font-name +is identical to the font name as it appears in the printer's \[lq]font +listing A\[rq]. +. +For instance, +to select the \[lq]Swiss\[rq] font in bold-italic style, +which appears in the font listing +as \%\[lq]Swiss\-BoldOblique\[rq], +.RS +.EX +lbpname Swiss\-BoldOblique +.EE +.RE +is the required directive, +and this is what we find in the +.I groff +font description file +.I HBI +for the +.B lbp +device. +. +. +.\" ==================================================================== +.SS "Drawing commands" +.\" ==================================================================== +. +For compatibility with +.MR grolj4 @MAN1EXT@ , +an additional drawing command is available. +. +. +.TP +.BI \[rs]D\[aq]R\~ "dh dv" \[aq] +Draw a rule +(solid black rectangle) +with one corner at the drawing position, +and the diagonally opposite corner at the drawing position +.RI +( dh , dv ). +.\" XXX , at which the drawing position will be afterward. ? +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-h +and +.B \-\-help +display a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.BI \-c " num-copies" +.TQ +.BI \-\-copies= num-copies +Produce +.I num-copies +copies of each page. +. +. +.TP +.BI \-F " font-directory" +.TQ +.BI \-\-fontdir= font-directory +Prepend directory +.RI font-directory /dev name +to the search path for font and device description files; +.I name +is the name of the device, +usually +.BR lbp . +. +. +.TP +.B \-l +.TQ +.B \-\-landscape +Format the document in landscape orientation. +. +. +.TP +.BI \-o " orientation" +.TQ +.BI \-\-orientation= orientation +Format the document in the given +.IR orientation , +which must be +.RB \%\[lq] portrait \[rq] +or +.RB \%\[lq] landscape \[rq]. +. +. +.TP +.BI \-p " paper-format" +.TQ +.BI \-\-papersize= paper-format +Set the paper format to +.IR paper-format , +which must be a valid paper format as described above. +. +. +.TP +.BI \-w " width" +.TQ +.BI \-\-linewidth= width +Set the default line thickness to +.I width +thousandths of an em; +the default is +.B 40 +(0.04\~em). +. +. +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +lists directories in which to seek the selected output device's +directory of device and font description files. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @FONTDIR@/\:\%devlbp/\:DESC +describes the +.B lbp +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devlbp/ F +describes the font known +.RI as\~ F +on device +.BR lbp . +. +. +.TP +.I @MACRODIR@/\:lbp\:.tmac +defines macros for use with the +.B lbp +output device. +. +It is automatically loaded by +.I troffrc +when the +.B lbp +output device is selected. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ , +.MR groff_out @MAN5EXT@ , +.MR groff_font @MAN5EXT@ , +.MR groff_char @MAN7EXT@ +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_grolbp_1_man_C] +.do rr *groff_grolbp_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/grolbp/grolbp.am b/src/devices/grolbp/grolbp.am new file mode 100644 index 0000000..3ee3a0a --- /dev/null +++ b/src/devices/grolbp/grolbp.am @@ -0,0 +1,36 @@ +# Copyright (C) 2014-2020 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +bin_PROGRAMS += grolbp +grolbp_SOURCES = \ + src/devices/grolbp/lbp.cpp \ + src/devices/grolbp/lbp.h \ + src/devices/grolbp/charset.h + +grolbp_LDADD = $(LIBM) \ + libdriver.a \ + libgroff.a \ + lib/libgnu.a +man1_MANS += src/devices/grolbp/grolbp.1 +EXTRA_DIST += src/devices/grolbp/grolbp.1.man + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/grolbp/lbp.cpp b/src/devices/grolbp/lbp.cpp new file mode 100644 index 0000000..35cd54e --- /dev/null +++ b/src/devices/grolbp/lbp.cpp @@ -0,0 +1,738 @@ +/* Copyright (C) 1994-2020 Free Software Foundation, Inc. + Written by Francisco Andrés Verdú <pandres@dragonet.es> 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 <http://www.gnu.org/licenses/>. */ + +/* +TODO + + - Add X command to include bitmaps +*/ + +#include <assert.h> + +#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: diff --git a/src/devices/grolbp/lbp.h b/src/devices/grolbp/lbp.h new file mode 100644 index 0000000..ee1c7b9 --- /dev/null +++ b/src/devices/grolbp/lbp.h @@ -0,0 +1,544 @@ +// -*- C -*- +/* Copyright (C) 1994-2020 Free Software Foundation, Inc. + Written by Francisco Andrés Verdú <pandres@dragonet.es> + +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 <http://www.gnu.org/licenses/>. */ + +/* This file contains a set of utility functions to use canon CaPSL printers + * (lbp-4 and lbp-8 series printers) */ + +#ifndef LBP_H +#define LBP_H + +#include <stdio.h> +#include <stdarg.h> + +static FILE *lbpoutput = NULL; +static FILE *vdmoutput = NULL; + + +static inline void +lbpinit(FILE *outfile) +{ + lbpoutput = outfile; +} + + +static void +lbpprintf(const char *format, ... ) +{ /* Taken from cjet */ + va_list stuff; + + va_start(stuff, format); + vfprintf(lbpoutput, format, stuff); + va_end(stuff); +} + + +static inline void +lbpputs(const char *data) +{ + fputs(data,lbpoutput); +} + + +static inline void +lbpputc(unsigned char c) +{ + fputc(c,lbpoutput); +} + + +static inline void +lbpsavestatus(int idx ) +{ + fprintf(lbpoutput,"\033[%d%%y",idx); +} + + +static inline void +lbprestorestatus(int idx ) +{ + fprintf(lbpoutput,"\033[%d%cz",idx ,'%'); +} + + +static inline void +lbpsavepos(int idx) +{ + fprintf(lbpoutput,"\033[1;%d;0x",idx); +} + + +static inline void +lbprestorepos(int idx) +{ + fprintf(lbpoutput,"\033[0;%d;0x",idx); +} + + +static inline void +lbprestoreposx(int idx) +{ + fprintf(lbpoutput,"\033[0;%d;1x",idx); +} + + +static inline void +lbpmoverel(int despl, char direction) +{ + fprintf(lbpoutput,"\033[%d%c",despl,direction); +} + + +static inline void +lbplinerel(int width,int despl,char direction ) +{ + fprintf(lbpoutput,"\033[%d;0;9{\033[%d%c\033[9}",width,despl,direction); +} + + +static inline void +lbpmoveabs(int x, int y) +{ + fprintf(lbpoutput,"\033[%d;%df",y,x); +} + + +static inline void +lbplineto(int x,int y, int width ) +{ + fprintf(lbpoutput,"\033[%d;0;9{",width); + lbpmoveabs(x,y); + fprintf(lbpoutput,"\033[9}\n"); +} + + +static inline void +lbpruleabs(int x, int y, int hsize, int vsize) +{ + lbpmoveabs(x,y); + fprintf(lbpoutput,"\033[0;9;000s"); + lbpmoveabs(x+hsize,y+vsize); + fprintf(lbpoutput,"\033[9r"); +} + + +static void vdmprintf(const char *format, ... ); + + +static inline char * +vdmnum(int num,char *result) +{ + char b1,b2,b3; + char *p = result; + int nm; + + nm = abs(num); + /* First byte 1024 - 32768 */ + b1 = ((nm >> 10) & 0x3F); + if (b1) *p++ = b1 | 0x40; + + /* Second Byte 16 - 1024 */ + b2 = ((nm >> 4) & 0x3F); + if ( b1 || b2) *p++= b2 | 0x40; + + /* Third byte 0 - 15 */ + b3 = ((nm & 0x0F) | 32); + if (num >= 0) b3 |= 16; + *p++ = b3; + *p = 0x00; /* End of the resulting string */ + return result; +} + + +static inline void +vdmorigin(int newx, int newy) +{ + char nx[4],ny[4]; + + vdmprintf("}\"%s%s\x1e",vdmnum(newx,nx),vdmnum(newy,ny)); +} + + +static inline FILE * +vdminit(FILE *vdmfile) +{ + char scale[4],size[4],lineend[4]; + +/* vdmoutput = tmpfile();*/ + vdmoutput = vdmfile; + /* Initialize the VDM mode */ + vdmprintf("\033[0&}#GROLBP\x1e!0%s%s\x1e$\x1e}F%s\x1e",\ + vdmnum(-3,scale),vdmnum(1,size),vdmnum(1,lineend)); + return vdmoutput; + +} + + +static inline void +vdmend() +{ + vdmprintf("}p\x1e"); +} + + +static void +vdmprintf(const char *format, ... ) +{ /* Taken from cjet */ + va_list stuff; + + if (vdmoutput == NULL) vdminit(tmpfile()); + va_start(stuff, format); + vfprintf(vdmoutput, format, stuff); + va_end(stuff); +} + + +static inline void +vdmsetfillmode(int pattern,int perimeter, int inverted) +{ + char patt[4],perim[4], + rot[4], /* rotation */ + espejo[4], /* espejo */ + inv[4]; /* Inverted */ + + vdmprintf("I%s%s%s%s%s\x1e",vdmnum(pattern,patt),\ + vdmnum(perimeter,perim),vdmnum(0,rot), + vdmnum(0,espejo),vdmnum(inverted,inv)); +} + + +static inline void +vdmcircle(int centerx, int centery, int radius) +{ + char x[4],y[4],rad[4]; + + vdmprintf("5%s%s%s\x1e",vdmnum(centerx,x),vdmnum(centery,y),\ + vdmnum(radius,rad)); +} + + +static inline void +vdmaarc(int centerx, int centery, int radius,int startangle,int angle,int style,int arcopen) +{ + char x[4],y[4],rad[4],stx[4],sty[4],styl[4],op[4]; + + vdmprintf("}6%s%s%s%s%s%s%s\x1e",vdmnum(arcopen,op),\ + vdmnum(centerx,x),vdmnum(centery,y),\ + vdmnum(radius,rad),vdmnum(startangle,stx),vdmnum(angle,sty),\ + vdmnum(style,styl)); +} + + +static inline void +vdmvarc(int centerx, int centery,int radius, int startx, int starty, int endx, int endy,\ + int style,int arcopen) +{ + char x[4],y[4],rad[4],stx[4],sty[4],enx[4],eny[4],styl[4],op[4]; + + vdmprintf("}6%s%s%s%s%s%s%s%s%s\x1e",vdmnum(arcopen,op),\ + vdmnum(centerx,x),vdmnum(centery,y),\ + vdmnum(radius,rad),vdmnum(startx,stx),vdmnum(starty,sty),\ + vdmnum(endx,enx),vdmnum(endy,eny),vdmnum(style,styl)); +} + + +static inline void +vdmellipse(int centerx, int centery, int radiusx, int radiusy,int rotation) +{ + char x[4],y[4],radx[4],rady[4],rotat[4]; + + vdmprintf("}7%s%s%s%s%s\x1e\n",vdmnum(centerx,x),vdmnum(centery,y),\ + vdmnum(radiusx,radx),vdmnum(radiusy,rady),\ + vdmnum(rotation,rotat)); +} + + +static inline void +vdmsetlinetype(int lintype) +{ + char ltyp[4], expfact[4]; + + vdmprintf("E1%s%s\x1e",vdmnum(lintype,ltyp),vdmnum(1,expfact)); + +} + + +static inline void +vdmsetlinestyle(int lintype, int pattern,int unionstyle) +{ + char patt[4],ltip[4], + rot[4], /* rotation */ + espejo[4], /* espejo */ + in[4]; /* Inverted */ + + vdmprintf("}G%s%s%s%s%s\x1e",vdmnum(lintype,ltip),\ + vdmnum(pattern,patt),vdmnum(0,rot), + vdmnum(0,espejo),vdmnum(0,in)); + vdmprintf("}F%s",vdmnum(unionstyle,rot)); +} + + +static inline void +vdmlinewidth(int width) +{ + char wh[4]; + + vdmprintf("F1%s\x1e",vdmnum(width,wh)); +} + + +static inline void +vdmrectangle(int origx, int origy,int dstx, int dsty) +{ + char xcoord[4],ycoord[4],sdstx[4],sdsty[4]; + + vdmprintf("}:%s%s%s%s\x1e\n",vdmnum(origx,xcoord),vdmnum(dstx,sdstx),\ + vdmnum(origy,ycoord),vdmnum(dsty,sdsty)); +} + + +static inline void +vdmpolyline(int numpoints, int *points) +{ + int i,*p = points; + char xcoord[4],ycoord[4]; + + if (numpoints < 2) return; + vdmprintf("1%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord)); + p += 2; + for (i = 1; i < numpoints ; i++) { + vdmprintf("%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord)); + p += 2; + } /* for */ + vdmprintf("\x1e\n"); +} + + +static inline void +vdmpolygon(int numpoints, int *points) +{ + int i,*p = points; + char xcoord[4],ycoord[4]; + + if (numpoints < 2) return; + vdmprintf("2%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord)); + p += 2; + for (i = 1; i < numpoints ; i++) { + vdmprintf("%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord)); + p += 2; + } /* for */ + vdmprintf("\x1e\n"); + +} + + +/************************************************************************ + * Higher level auxiliary functions * + ************************************************************************/ +static inline int +vdminited() +{ + return (vdmoutput != NULL); +} + + +static inline void +vdmline(int startx, int starty, int sizex, int sizey) +{ + int points[4]; + + points[0] = startx; + points[1] = starty; + points[2] = sizex; + points[3] = sizey; + + vdmpolyline(2,points); + +} + + +/*#define THRESHOLD .05 */ /* inch */ +#define THRESHOLD 1 /* points (1/300 inch) */ +static inline void +splinerel(double px,double py,int flush) +{ + static int lx = 0 ,ly = 0; + static double pend = 0.0; + static int dy = 0, despx = 0, despy = 0, sigpend = 0; + int dxnew = 0, dynew = 0, sg; + char xcoord[4],ycoord[4]; + double npend ; + + if (flush == -1) {lx = (int)px; ly = (int)py; return;} + + if (flush == 0) { + dxnew = (int)px -lx; + dynew = (int)py -ly; + if ((dxnew == 0) && (dynew == 0)) return; + sg = (dxnew < 0)? -1 : 0; +/* fprintf(stderr,"s (%d,%d) (%d,%d)\n",dxnew,dynew,despx,despy);*/ + if (dynew == 0) { + despx = dxnew; + if ((sg == sigpend) && (dy == 0)){ + return; + } + dy = 0; + } + else { + dy = 1; + npend = (1.0*dxnew)/dynew; + if (( npend == pend) && (sigpend == sg)) + { despy = dynew; despx = dxnew; return; } + else + { sigpend = sg; + pend = npend; + } /* else (( npend == pend) && ... */ + } /* else (if (dynew == 0)) */ + } /* if (!flush ) */ + + /* if we've changed direction we must draw the line */ +/* fprintf(stderr," (%d) %.2f,%.2f\n",flush,(float)px,(float)py);*/ + if ((despx != 0) || (despy != 0)) vdmprintf("%s%s",vdmnum(despx,xcoord),\ + vdmnum(despy,ycoord)); + /*if ((despx != 0) || (despy != 0)) fprintf(stderr,"2 + *%d,%d\n",despx,despy);*/ + if (flush) { + dxnew = dy = despx = despy = 0; + return; + } /* if (flush) */ + dxnew -= despx; + dynew -= despy; + if ((dxnew != 0) || (dynew != 0)) vdmprintf("%s%s",vdmnum(dxnew,xcoord),\ + vdmnum(dynew,ycoord)); + +/* if ((dxnew != 0) || (dynew != 0)) fprintf(stderr,"3 + * %d,%d\n",dxnew,dynew);*/ + lx = (int)px; ly = (int)py; + dxnew = dy = despx = despy = 0; + +} + + +/********************************************************************** + * The following code to draw splines is adapted from the transfig package + */ +static void +quadratic_spline(double a_1, double b_1, double a_2, double b_2, \ + double a_3, double b_3, double a_4, double b_4) +{ + double x_1, y_1, x_4, y_4; + double x_mid, y_mid; + + x_1 = a_1; y_1 = b_1; + x_4 = a_4; y_4 = b_4; + x_mid = (a_2 + a_3)/2.0; + y_mid = (b_2 + b_3)/2.0; + if ((fabs(x_1 - x_mid) < THRESHOLD) + && (fabs(y_1 - y_mid) < THRESHOLD)) { + splinerel(x_mid, y_mid, 0); +/* fprintf(tfp, "PA%.4f,%.4f;\n", x_mid, y_mid);*/ + } + else { + quadratic_spline(x_1, y_1, ((x_1+a_2)/2.0), ((y_1+b_2)/2.0), + ((3.0*a_2+a_3)/4.0), ((3.0*b_2+b_3)/4.0), x_mid, y_mid); + } + + if ((fabs(x_mid - x_4) < THRESHOLD) + && (fabs(y_mid - y_4) < THRESHOLD)) { + splinerel(x_4, y_4, 0); +/* fprintf(tfp, "PA%.4f,%.4f;\n", x_4, y_4);*/ + } + else { + quadratic_spline(x_mid, y_mid, + ((a_2+3.0*a_3)/4.0), ((b_2+3.0*b_3)/4.0), + ((a_3+x_4)/2.0), ((b_3+y_4)/2.0), x_4, y_4); + } +} + + +#define XCOORD(i) numbers[(2*i)] +#define YCOORD(i) numbers[(2*i)+1] +static void +vdmspline(int numpoints, int o_x, int o_y, int *numbers) +{ + double cx_1, cy_1, cx_2, cy_2, cx_3, cy_3, cx_4, cy_4; + double x_1, y_1, x_2, y_2; + char xcoord[4],ycoord[4]; + int i; + + /*p = s->points; + x_1 = p->x/ppi;*/ + x_1 = o_x; + y_1 = o_y; +/* p = p->next; + x_2 = p->x/ppi; + y_2 = p->y/ppi;*/ + x_2 = o_x + XCOORD(0); + y_2 = o_y + YCOORD(0); + cx_1 = (x_1 + x_2)/2.0; + cy_1 = (y_1 + y_2)/2.0; + cx_2 = (x_1 + 3.0*x_2)/4.0; + cy_2 = (y_1 + 3.0*y_2)/4.0; + +/* fprintf(stderr,"Spline %d (%d,%d)\n",numpoints,(int)x_1,(int)y_1);*/ + vdmprintf("1%s%s",vdmnum((int)x_1,xcoord),vdmnum((int)y_1,ycoord)); + splinerel(x_1,y_1,-1); + splinerel(cx_1,cy_1,0); +/* fprintf(tfp, "PA%.4f,%.4f;PD%.4f,%.4f;\n", + x_1, y_1, cx_1, cy_1);*/ + + /*for (p = p->next; p != NULL; p = p->next) {*/ + for (i = 1; i < (numpoints); i++) { + x_1 = x_2; + y_1 = y_2; +/* x_2 = p->x/ppi; + y_2 = p->y/ppi;*/ + x_2 = x_1 + XCOORD(i); + y_2 = y_1 + YCOORD(i); + cx_3 = (3.0*x_1 + x_2)/4.0; + cy_3 = (3.0*y_1 + y_2)/4.0; + cx_4 = (x_1 + x_2)/2.0; + cy_4 = (y_1 + y_2)/2.0; + /* fprintf(stderr,"Point (%d,%d) - (%d,%d)\n",(int)x_1,(int)(y_1),(int)x_2,(int)y_2);*/ + quadratic_spline(cx_1, cy_1, cx_2, cy_2, cx_3, cy_3, cx_4, cy_4); + cx_1 = cx_4; + cy_1 = cy_4; + cx_2 = (x_1 + 3.0*x_2)/4.0; + cy_2 = (y_1 + 3.0*y_2)/4.0; + } + x_1 = x_2; + y_1 = y_2; +/* p = s->points->next; + x_2 = p->x/ppi; + y_2 = p->y/ppi;*/ + x_2 = o_x + XCOORD(0); + y_2 = o_y + YCOORD(0); + cx_3 = (3.0*x_1 + x_2)/4.0; + cy_3 = (3.0*y_1 + y_2)/4.0; + cx_4 = (x_1 + x_2)/2.0; + cy_4 = (y_1 + y_2)/2.0; + splinerel(x_1, y_1, 0); + splinerel(x_1, y_1, 1); + /*vdmprintf("%s%s",vdmnum((int)(x_1-lx),xcoord),\ + vdmnum((int)(y_1-ly),ycoord));*/ + vdmprintf("\x1e\n"); +/* fprintf(tfp, "PA%.4f,%.4f;PU;\n", x_1, y_1);*/ + + +} + + +#endif diff --git a/src/devices/grolj4/grolj4.1.man b/src/devices/grolj4/grolj4.1.man new file mode 100644 index 0000000..6227779 --- /dev/null +++ b/src/devices/grolj4/grolj4.1.man @@ -0,0 +1,896 @@ +.TH grolj4 @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +grolj4 \- +.I groff +output driver for HP LaserJet 4 and compatible printers +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1994-2020, 2022 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_grolj4_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +.\" This macro definition is poor style from a portability standpoint, +.\" but it's a good test and demonstration of the standard font +.\" repertoire for the devices where it has any effect at all, and so +.\" should be retained. +.de FT +. if '\\*(.T'lj4' .ft \\$1 +.. +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY grolj4 +.RB [ \-l ] +.RB [ \-c\~\c +.IR num-copies ] +.RB [ \-d +.RI [ n ]] +.RB [ \-F\~\c +.IR font-directory ] +.RB [ \-p\~\c +.IR paper-format ] +.RB [ \-w\~\c +.IR line-width ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY grolj4 +.B \-\-help +.YS +. +. +.SY grolj4 +.B \-v +. +.SY grolj4 +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +This GNU +.I roff +output driver translates the output of +.MR @g@troff @MAN1EXT@ +into a PCL5 format suitable for an HP LaserJet 4 printer. +. +Normally, +.I grolj4 +is invoked by +.MR groff @MAN1EXT@ +when the latter is given the +.RB \[lq] \-T\~lj4 \[rq] +option. +. +(In this installation, +.B @DEVICE@ +is the default output device.) +. +Use +.IR groff 's +.B \-P +option to pass any options shown above to +.IR grolj4 . +. +If no +.I file +arguments are given, +or if +.I file +is \[lq]\-\[rq], +.I grolj4 +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.\" ==================================================================== +.SS Typefaces +.\" ==================================================================== +. +.I grolj4 +supports the standard four styles: +.B R +(roman), +.B I +.RI ( italic ), +.B B +.RB ( bold ), +and +.B BI +(\f[BI]bold-italic\f[]). +. +Fonts are grouped into families +.BR A , +.BR C , +.BR G , +.BR O , +.BR T , +.BR TN , +.BR U , +and +.B UC +having members in each style. +. +. +.RS +.TP 14n +.B AB +.FT AB +Arial Bold +.FT +. +.TQ +.B ABI +.FT ABI +Arial Bold Italic +.FT +. +.TQ +.B AI +.FT AI +Arial Italic +.FT +. +.TQ +.B AR +.FT AR +Arial Roman +.FT +. +.TQ +.B CB +.FT CB +Courier Bold +.FT +. +.TQ +.B CBI +.FT CBI +Courier Bold Italic +.FT +. +.TQ +.B CI +.FT CI +Courier Italic +.FT +. +.TQ +.B CR +.FT CR +Courier Roman +.FT +. +.TQ +.B GB +.FT GB +Garamond Halbfett +.FT +. +.TQ +.B GBI +.FT GBI +Garamond Kursiv Halbfett +.FT +. +.TQ +.B GI +.FT GI +Garamond Kursiv +.FT +. +.TQ +.B GR +.FT GR +Garamond Antiqua +.FT +. +.TQ +.B OB +.FT OB +CG Omega Bold +.FT +. +.TQ +.B OBI +.FT OBI +CG Omega Bold Italic +.FT +. +.TQ +.B OI +.FT OI +CG Omega Italic +.FT +. +.TQ +.B OR +.FT OR +CG Omega Roman +. +.TQ +.B OB +.FT OB +CG Omega Bold +.FT +. +.TQ +.B OBI +.FT OBI +CG Omega Bold Italic +.FT +. +.TQ +.B OI +.FT OI +CG Omega Italic +.FT +. +.TQ +.B OR +.FT OR +CG Omega Roman +.FT +. +.TQ +.B TB +.FT TB +CG Times Bold +.FT +. +.TQ +.B TBI +.FT TBI +CG Times Bold Italic +.FT +. +.TQ +.B TI +.FT TI +CG Times Italic +.FT +. +.TQ +.B TR +.FT TR +CG Times Roman +.FT +. +.TQ +.B TNRB +.FT TNRB +M Times Bold +.FT +. +.TQ +.B TNRBI +.FT TNRBI +M Times Bold Italic +.FT +. +.TQ +.B TNRI +.FT TNRI +M Times Italic +.FT +. +.TQ +.B TNRR +.FT TNRR +M Times Roman +.FT +. +.TQ +.B UB +.FT UB +Univers Bold +.FT +. +.TQ +.B UBI +.FT UBI +Univers Bold Italic +.FT +. +.TQ +.B UI +.FT UI +Univers Medium Italic +.FT +. +.TQ +.B UR +.FT UR +Univers Medium +.FT +. +.TQ +.B UCB +.FT UCB +Univers Condensed Bold +.FT +. +.TQ +.B UCBI +.FT UCBI +Univers Condensed Bold Italic +.FT +. +.TQ +.B UCI +.FT UCI +Univers Condensed Medium Italic +.FT +. +.TQ +.B UCR +.FT UCR +Univers Condensed Medium +.FT +.RE +. +. +.P +The following fonts are not members of a family. +. +. +.RS +.TP 14n +.B ALBB +.FT ALBB +Albertus Extra Bold +.FT +. +.TQ +.B ALBR +.FT ALBR +Albertus Medium +.FT +. +.TQ +.B AOB +.FT AOB +Antique Olive Bold +. +.TQ +.B AOI +.FT AOI +Antique Olive Italic +. +.TQ +.B AOR +.FT AOR +Antique Olive Roman +. +.TQ +.B CLARENDON +.FT CLARENDON +Clarendon +. +.TQ +.B CORONET +.FT CORONET +Coronet +. +.TQ +.B LGB +.FT LGB +Letter Gothic Bold +. +.TQ +.B LGI +.FT LGI +Letter Gothic Italic +. +.TQ +.B LGR +.FT LGR +Letter Gothic Roman +. +.TQ +.B MARIGOLD +.FT MARIGOLD +Marigold +.RE +. +. +.P +The special font is +.B S +(PostScript Symbol); +.B SYMBOL +(M Symbol), +and +.B WINGDINGS +(Wingdings) +are also available but not mounted by default. +. +. +.\" ==================================================================== +.SS "Paper format and device description file" +.\" ==================================================================== +. +.I grolj4 +supports paper formats +.RB \[lq] A4 \[rq], +.RB \[lq] B5 \[rq], +.RB \[lq] C5 \[rq], +.RB \[lq] com10 \[rq], +.RB \[lq] DL \[rq], +.RB \%\[lq] executive \[rq], +.RB \%\[lq] legal \[rq], +.RB \%\[lq] letter \[rq], +and +.RB \[lq] monarch \[rq]. +. +These are matched case-insensitively. +. +The +.B \-p +option overrides any setting in the device description file +.IR DESC . +. +If neither specifies a paper format, +\[lq]letter\[rq] is assumed. +. +. +.\" ==================================================================== +.SS "Font description files" +.\" ==================================================================== +. +.I grolj4 +recognizes four font description file directives in addition to those +documented in +.MR groff_font @MAN5EXT@ . +. +. +.TP +.BI pclweight\~ n +Set the stroke weight to +.IR n , +an integer in the range \-7 to +7; +the default is\~0. +. +. +.TP +.BI pclstyle\~ n +Set the style to +.IR n , +an integer in the range 0 to 32767; +the default is\~0. +. +. +.TP +.BI pclproportional\~ n +Set the proportional spacing Boolean flag to +.IR n , +which can be either 0 or\~1; +the default is\~0. +. +. +.TP +.BI pcltypeface\~ n +Set the typeface family to +.IR n , +an integer in the range 0 to 65535; +the default is\~0. +. +. +.\" ==================================================================== +.SS "Drawing commands" +.\" ==================================================================== +. +An additional drawing command is recognized as an extension to those +documented in +.MR groff @MAN7EXT@ . +. +. +.TP +.BI \[rs]D\[aq]R\~ "dh dv" \[aq] +Draw a rule +(solid black rectangle) +with one corner at the drawing position, +and the diagonally opposite corner at the drawing position +.RI +( dh , dv ), +at which the drawing position will be afterward. +. +This generates a PCL fill rectangle command, +and so will work on printers that do not support HP-GL/2, +unlike the other +.B \[rs]D +commands. +. +. +.\" ==================================================================== +.SS Fonts +.\" ==================================================================== +. +Nominally, +all Hewlett-Packard LaserJet\~\%4-series and newer printers have the +same internal fonts: +45 scalable fonts and one bitmapped Lineprinter font. +. +The scalable fonts are available in sizes between 0.25 points and 999.75 +points, +in 0.25-point increments; +the Lineprinter font is available only in 8.5-point size. +. +. +.P +The LaserJet font files included with +.I groff +assume that all printers since the LaserJet\~4 are identical. +. +There are some differences between fonts in the earlier and more recent +printers, +however. +. +The LaserJet\~4 printer used Agfa Intellifont technology for 35 of the +internal scalable fonts; +the remaining 10 scalable fonts were TrueType. +. +Beginning with the LaserJet\~\%4000-series printers introduced in 1997, +all scalable internal fonts have been TrueType. +. +The number of printable glyphs differs slightly between Intellifont and +TrueType fonts +(generally, +the TrueType fonts include more glyphs), +and +there are some minor differences in glyph metrics. +. +Differences among printer models are described in the +.I "PCL\~5 Comparison Guide" +and the +.I "PCL\~5 Comparison Guide Addendum" +(for printers introduced since approximately 2001). +. +. +.P +LaserJet printers reference a glyph by a combination of a 256-glyph +symbol set and an index within that symbol set. +. +Many glyphs appear in more than one symbol set; +all combinations of symbol set and index that reference the same glyph +are equivalent. +. +For each glyph, +.MR hpftodit @MAN1EXT@ +searches a list of symbol sets, +and selects the first set that contains the glyph. +. +The printing code generated by +.I hpftodit +is an integer that encodes a numerical value for the symbol set in the +high byte(s), +and the index in the low byte. +. +See +.MR groff_font @MAN5EXT@ +for a complete description of the font file format; +symbol sets are described in greater detail in the +.IR "PCL\~5 Printer Language Technical Reference Manual" . +. +. +.P +Two of the scalable fonts, +Symbol and Wingdings, +are bound to 256-glyph symbol sets; +the remaining scalable fonts, +as well as the Lineprinter font, +support numerous symbol sets, +sufficient to enable printing of more than 600 glyphs. +. +. +.P +The metrics generated by +.I hpftodit +assume that the +.I DESC +file contains values of 1200 for +.I res +and 6350 for +.IR unitwidth , +or any combination +(e.g., +2400 and 3175) +for which +.IR res \~\[tmu]\~ unitwidth \~=\~7\|620\|000. +. +Although HP PCL\~5 LaserJet printers support an internal resolution of +7200 units per inch, +they use a 16-bit signed integer for positioning; +if +.B devlj4 +is to support U.S.\& ledger paper (11\~in\~\[mu]\~17\~in; +in = inch), +the maximum usable resolution is 32\|767\~\[di]\~17, +or 1927 units per inch, +which rounds down to 1200 units per inch. +. +If the largest required paper dimension is less +(e.g., +8.5\~in\~\[mu]\~11\~in, +or A5), +a greater +.I res +(and lesser +.IR unitwidth ) +can be specified. +. +. +.P +Font metrics for Intellifont fonts were provided by Tagged Font Metric +(TFM) files originally developed by Agfa/Compugraphic. +. +The TFM files provided for these fonts supported 600+ glyphs and +contained extensive lists of kerning pairs. +. +. +.P +To accommodate developers who had become accustomed to TFM files, +HP also provided TFM files for the 10 TrueType fonts included in the +LaserJet\~4. +. +The TFM files for TrueType fonts generally included less information +than the Intellifont TFMs, +supporting fewer glyphs, +and in most cases, +providing no kerning information. +. +By the time the LaserJet\~4000 printer was introduced, +most developers had migrated to other means of obtaining font metrics, +and support for new TFM files was very limited. +. +The TFM files provided for the TrueType fonts in the LaserJet\~4000 +support only the Latin 2 (ISO 8859-2) symbol set, +and include no kerning information; +consequently, +they are of little value for any but the most rudimentary documents. +. +. +.P +Because the Intellifont TFM files contain considerably more information, +they generally are preferable to the TrueType TFM files even for use +with the TrueType fonts in the newer printers. +. +The metrics for the TrueType fonts are very close, +though not identical, +to those for the earlier Intellifont fonts of the same names. +. +Although most output using the Intellifont metrics with the newer +printers is quite acceptable, +a few glyphs may fail to print as expected. +. +The differences in glyph metrics may be particularly noticeable with +composite parentheses, +brackets, +and braces used by +.MR eqn @MAN1EXT@ . +. +A script, +located in +.IR @FONTDIR@/\:\%devlj4/\:generate , +can be used to adjust the metrics for these glyphs in the special font +\[lq]S\[rq] for use with printers that have all TrueType fonts. +. +. +.P +At the time HP last supported TFM files, +only version 1.0 of the Unicode standard was available. +. +Consequently, +many glyphs lacking assigned code points were assigned by HP to the +Private Use Area (PUA). +. +Later versions of the Unicode standard included code points outside the +PUA for many of these glyphs. +. +The HP-supplied TrueType TFM files use the PUA assignments; +TFM files generated from more recent TrueType font files require the +later Unicode values to access the same glyphs. +. +Consequently, +two different mapping files may be required: +one for the HP-supplied TFM files, +and one for more recent TFM files. +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.BI \-c\~ num-copies +Format +.I num-copies +copies of each page. +. +. +.TP +.BR \-d \~[\c +.IR n ] +Use duplex mode +.IR n : +1\~is long-side binding (default), +and 2\~is short-side binding. +. +. +.TP +.BI \-F " font-directory" +Prepend directory +.IR font-directory /dev name +to the search path for font and device description files; +.I name +is the name of the device, +usually +.BR lj4 . +. +. +.TP +.B \-l +Format the document in landscape orientation. +. +. +.TP +.BI \-p " paper-format" +Set the paper format to +.IR paper-format , +which must be a valid paper format as described above. +. +. +.TP +.BI \-w " line-width" +Set the default line thickness to +.I line-width +thousandths of an em; +the default is +.B 40 +(0.04\~em). +. +. +.br +.ne 4v \" Keep section heading and paragraph together. +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +lists directories in which to seek the selected output device's +directory of device and font description files. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @FONTDIR@/\:\%devlj4/\:DESC +describes the +.B lj4 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devlj4/ F +describes the font known +.RI as\~ F +on device +.BR lj4 . +. +. +.TP +.I @MACRODIR@/\:lj4\:.tmac +defines macros for use with the +.B lj4 +output device. +. +It is automatically loaded by +.I troffrc +when the +.B lj4 +output device is selected. +. +. +.\" ==================================================================== +.SH Bugs +.\" ==================================================================== +. +.\" XXX: What does this mean? The period/full stop glyph? Flyspecks? +Small dots. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.UR http://\:www\:.hp\:.com/\:ctg/\:Manual/\:bpl13210\:.pdf +.I HP PCL/PJL Reference: +.I PCL\~5 Printer Language Technical Reference Manual, +.I Part I +.UE +. +. +.P +.MR hpftodit @MAN1EXT@ , +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ , +.MR groff_out @MAN5EXT@ , +.MR groff_font @MAN5EXT@ , +.MR groff_char @MAN7EXT@ +. +. +.\" Clean up. +.rm FT +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_grolj4_1_man_C] +.do rr *groff_grolj4_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/grolj4/grolj4.am b/src/devices/grolj4/grolj4.am new file mode 100644 index 0000000..37138b2 --- /dev/null +++ b/src/devices/grolj4/grolj4.am @@ -0,0 +1,32 @@ +# Copyright (C) 2014-2020 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +bin_PROGRAMS += grolj4 +grolj4_SOURCES = src/devices/grolj4/lj4.cpp +grolj4_LDADD = $(LIBM) \ + libdriver.a \ + libgroff.a \ + lib/libgnu.a +man1_MANS += src/devices/grolj4/grolj4.1 +EXTRA_DIST += \ + src/devices/grolj4/grolj4.1.man + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/grolj4/lj4.cpp b/src/devices/grolj4/lj4.cpp new file mode 100644 index 0000000..a429a7d --- /dev/null +++ b/src/devices/grolj4/lj4.cpp @@ -0,0 +1,715 @@ +/* Copyright (C) 1994-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +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 <http://www.gnu.org/licenses/>. */ + +/* +TODO + +option to use beziers for circle/ellipse/arc +option to use lines for spline (for LJ3) +left/top offset registration +output bin selection option +paper source option +output non-integer parameters using fixed point numbers +X command to insert contents of file +X command to specify inline escape sequence (how to specify unprintable chars?) +X command to include bitmap graphics +*/ + +#include <assert.h> + +#include "driver.h" +#include "nonposix.h" + +extern "C" const char *Version_string; + +static struct { + const char *name; + int code; + // at 300dpi + int x_offset_portrait; + int x_offset_landscape; +} paper_table[] = { + { "letter", 2, 75, 60 }, + { "legal", 3, 75, 60 }, + { "executive", 1, 75, 60 }, + { "a4", 26, 71, 59 }, + { "com10", 81, 75, 60 }, + { "monarch", 80, 75, 60 }, + { "c5", 91, 71, 59 }, + { "b5", 100, 71, 59 }, + { "dl", 90, 71, 59 }, +}; + +static int user_paper_size = -1; +static int landscape_flag = 0; +static int duplex_flag = 0; + +// An upper limit on the paper dimensions in centipoints, +// used for setting HPGL picture frame. +#define MAX_PAPER_WIDTH (12*720) +#define MAX_PAPER_HEIGHT (17*720) + +// Dotted lines that are thinner than this don't work right. +#define MIN_DOT_PEN_WIDTH .351 + +#ifndef DEFAULT_LINE_WIDTH_FACTOR +// in ems/1000 +#define DEFAULT_LINE_WIDTH_FACTOR 40 +#endif + +const int DEFAULT_HPGL_UNITS = 1016; +int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR; +unsigned ncopies = 0; // 0 means don't send ncopies command + +static int lookup_paper_size(const char *); + +class lj4_font : public font { +public: + ~lj4_font(); + void handle_unknown_font_command(const char *command, const char *arg, + const char *filename, int lineno); + static lj4_font *load_lj4_font(const char *); + int weight; + int style; + int proportional; + int typeface; +private: + lj4_font(const char *); +}; + +lj4_font::lj4_font(const char *nm) +: font(nm), weight(0), style(0), proportional(0), typeface(0) +{ +} + +lj4_font::~lj4_font() +{ +} + +lj4_font *lj4_font::load_lj4_font(const char *s) +{ + lj4_font *f = new lj4_font(s); + if (!f->load()) { + delete f; + return 0; + } + return f; +} + +static struct { + const char *s; + int lj4_font::*ptr; + int min; + int max; +} command_table[] = { + { "pclweight", &lj4_font::weight, -7, 7 }, + { "pclstyle", &lj4_font::style, 0, 32767 }, + { "pclproportional", &lj4_font::proportional, 0, 1 }, + { "pcltypeface", &lj4_font::typeface, 0, 65535 }, +}; + +void lj4_font::handle_unknown_font_command(const char *command, + const char *arg, + const char *filename, int lineno) +{ + for (unsigned int i = 0; + i < sizeof(command_table)/sizeof(command_table[0]); i++) { + if (strcmp(command, command_table[i].s) == 0) { + if (arg == 0) + fatal_with_file_and_line(filename, lineno, + "'%1' command requires an argument", + command); + char *ptr; + long n = strtol(arg, &ptr, 10); + if (n == 0 && ptr == arg) + fatal_with_file_and_line(filename, lineno, + "'%1' command requires numeric argument", + command); + if (n < command_table[i].min) { + error_with_file_and_line(filename, lineno, + "argument for '%1' command must not be less than %2", + command, command_table[i].min); + n = command_table[i].min; + } + else if (n > command_table[i].max) { + error_with_file_and_line(filename, lineno, + "argument for '%1' command must not be greater than %2", + command, command_table[i].max); + n = command_table[i].max; + } + this->*command_table[i].ptr = int(n); + break; + } + } +} + +class lj4_printer : public printer { +public: + lj4_printer(int); + ~lj4_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, int dot = 0); + void hpgl_init(); + void hpgl_start(); + void hpgl_end(); + int moveto(int hpos, int vpos); + int moveto1(int hpos, int vpos); + + int cur_hpos; + int cur_vpos; + lj4_font *cur_font; + int cur_size; + unsigned short cur_symbol_set; + int x_offset; + int line_thickness; + double pen_width; + double hpgl_scale; + int hpgl_inited; + int paper_size; +}; + +inline +int lj4_printer::moveto(int hpos, int vpos) +{ + if (cur_hpos != hpos || cur_vpos != vpos || cur_hpos < 0) + return moveto1(hpos, vpos); + else + return 1; +} + +inline +void lj4_printer::hpgl_start() +{ + fputs("\033%1B", stdout); +} + +inline +void lj4_printer::hpgl_end() +{ + fputs(";\033%0A", stdout); +} + +lj4_printer::lj4_printer(int ps) +: cur_hpos(-1), + cur_font(0), + cur_size(0), + cur_symbol_set(0), + line_thickness(-1), + pen_width(-1.0), + hpgl_inited(0) +{ + if (7200 % font::res != 0) + fatal("invalid resolution %1: resolution must be a factor of 7200", + font::res); + fputs("\033E", stdout); // reset + if (font::res != 300) + printf("\033&u%dD", font::res); // unit of measure + if (ncopies > 0) + printf("\033&l%uX", ncopies); + paper_size = 0; // default to letter + if (font::papersize) { + int n = lookup_paper_size(font::papersize); + if (n < 0) + error("ignoring invalid paper format '%1'", font::papersize); + else + paper_size = n; + } + if (ps >= 0) + paper_size = ps; + printf("\033&l%dA" // paper format + "\033&l%dO" // orientation + "\033&l0E", // no top margin + paper_table[paper_size].code, + landscape_flag != 0); + if (landscape_flag) + x_offset = paper_table[paper_size].x_offset_landscape; + else + x_offset = paper_table[paper_size].x_offset_portrait; + x_offset = (x_offset * font::res) / 300; + if (duplex_flag) + printf("\033&l%dS", duplex_flag); +} + +lj4_printer::~lj4_printer() +{ + fputs("\033E", stdout); +} + +void lj4_printer::begin_page(int) +{ +} + +void lj4_printer::end_page(int) +{ + putchar('\f'); + cur_hpos = -1; +} + +void lj4_printer::end_of_line() +{ + cur_hpos = -1; // force absolute motion +} + +inline +int is_unprintable(unsigned char c) +{ + return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27); +} + +void lj4_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 (symbol_set != cur_symbol_set) { + printf("\033(%d%c", symbol_set/32, (symbol_set & 31) + 64); + cur_symbol_set = symbol_set; + } + if (f != cur_font) { + lj4_font *psf = (lj4_font *)f; + // FIXME only output those that are needed + printf("\033(s%dp%ds%db%dT", + psf->proportional, + psf->style, + psf->weight, + psf->typeface); + if (!psf->proportional || !cur_font || !cur_font->proportional) + cur_size = 0; + cur_font = psf; + } + if (env->size != cur_size) { + if (cur_font->proportional) { + static const char *quarters[] = { "", ".25", ".5", ".75" }; + printf("\033(s%d%sV", env->size/4, quarters[env->size & 3]); + } + else { + double pitch = double(font::res)/w; + // PCL uses the next largest pitch, so round it down. + pitch = floor(pitch*100.0)/100.0; + printf("\033(s%.2fH", pitch); + } + cur_size = env->size; + } + if (!moveto(env->hpos, env->vpos)) + return; + if (is_unprintable(ch)) + fputs("\033&p1X", stdout); + putchar(ch); + cur_hpos += w; +} + +int lj4_printer::moveto1(int hpos, int vpos) +{ + if (hpos < x_offset || vpos < 0) + return 0; + fputs("\033*p", stdout); + if (cur_hpos < 0) + printf("%dx%dY", hpos - x_offset, vpos); + else { + if (cur_hpos != hpos) + printf("%s%d%c", hpos > cur_hpos ? "+" : "", + hpos - cur_hpos, vpos == cur_vpos ? 'X' : 'x'); + if (cur_vpos != vpos) + printf("%s%dY", vpos > cur_vpos ? "+" : "", vpos - cur_vpos); + } + cur_hpos = hpos; + cur_vpos = vpos; + return 1; +} + +void lj4_printer::draw(int code, int *p, int np, const environment *env) +{ + switch (code) { + case 'R': + { + if (np != 2) { + error("2 arguments required for rule"); + break; + } + int hpos = env->hpos; + int vpos = env->vpos; + int hsize = p[0]; + int vsize = p[1]; + if (hsize < 0) { + hpos += hsize; + hsize = -hsize; + } + if (vsize < 0) { + vpos += vsize; + vsize = -vsize; + } + if (!moveto(hpos, vpos)) + return; + printf("\033*c%da%db0P", hsize, vsize); + break; + } + case 'l': + if (np != 2) { + error("2 arguments required for line"); + break; + } + hpgl_init(); + if (!moveto(env->hpos, env->vpos)) + return; + hpgl_start(); + set_line_thickness(env->size, p[0] == 0 && p[1] == 0); + printf("PD%d,%d", p[0], p[1]); + hpgl_end(); + break; + case 'p': + case 'P': + { + if (np & 1) { + error("even number of arguments required for polygon"); + break; + } + if (np == 0) { + error("no arguments for polygon"); + break; + } + hpgl_init(); + if (!moveto(env->hpos, env->vpos)) + return; + hpgl_start(); + if (code == 'p') + set_line_thickness(env->size); + printf("PMPD%d", p[0]); + for (int i = 1; i < np; i++) + printf(",%d", p[i]); + printf("PM2%cP", code == 'p' ? 'E' : 'F'); + hpgl_end(); + break; + } + case '~': + { + if (np & 1) { + error("even number of arguments required for spline"); + break; + } + if (np == 0) { + error("no arguments for spline"); + break; + } + hpgl_init(); + if (!moveto(env->hpos, env->vpos)) + return; + hpgl_start(); + set_line_thickness(env->size); + printf("PD%d,%d", p[0]/2, p[1]/2); + const int tnum = 2; + const int tden = 3; + if (np > 2) { + fputs("BR", stdout); + for (int i = 0; i < np - 2; i += 2) { + if (i != 0) + putchar(','); + printf("%d,%d,%d,%d,%d,%d", + (p[i]*tnum)/(2*tden), + (p[i + 1]*tnum)/(2*tden), + p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden), + p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden), + (p[i] - p[i]/2) + p[i + 2]/2, + (p[i + 1] - p[i + 1]/2) + p[i + 3]/2); + } + } + printf("PR%d,%d", p[np - 2] - p[np - 2]/2, p[np - 1] - p[np - 1]/2); + hpgl_end(); + break; + } + case 'c': + case 'C': + // troff adds an extra argument to C + if (np != 1 && !(code == 'C' && np == 2)) { + error("1 argument required for circle"); + break; + } + hpgl_init(); + if (!moveto(env->hpos + p[0]/2, env->vpos)) + return; + hpgl_start(); + if (code == 'c') { + set_line_thickness(env->size); + printf("CI%d", p[0]/2); + } + else + printf("WG%d,0,360", p[0]/2); + hpgl_end(); + break; + case 'e': + case 'E': + if (np != 2) { + error("2 arguments required for ellipse"); + break; + } + hpgl_init(); + if (!moveto(env->hpos + p[0]/2, env->vpos)) + return; + hpgl_start(); + printf("SC0,%.4f,0,-%.4f,2", hpgl_scale * double(p[0])/p[1], hpgl_scale); + if (code == 'e') { + set_line_thickness(env->size); + printf("CI%d", p[1]/2); + } + else + printf("WG%d,0,360", p[1]/2); + printf("SC0,%.4f,0,-%.4f,2", hpgl_scale, hpgl_scale); + hpgl_end(); + break; + case 'a': + { + if (np != 4) { + error("4 arguments required for arc"); + break; + } + hpgl_init(); + if (!moveto(env->hpos, env->vpos)) + return; + hpgl_start(); + set_line_thickness(env->size); + double c[2]; + if (adjust_arc_center(p, c)) { + double sweep = ((atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0]) + - atan2(-c[1], -c[0])) + * 180.0/PI); + if (sweep > 0.0) + sweep -= 360.0; + printf("PDAR%d,%d,%f", int(c[0]), int(c[1]), sweep); + } + else + printf("PD%d,%d", p[0] + p[2], p[1] + p[3]); + hpgl_end(); + } + break; + case 'f': + if (np != 1 && np != 2) { + error("1 argument required for fill"); + break; + } + hpgl_init(); + hpgl_start(); + if (p[0] >= 0 && p[0] <= 1000) + printf("FT10,%d", p[0]/10); + hpgl_end(); + break; + case 'F': + // not implemented yet + break; + 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; + } + line_thickness = p[0]; + } + break; + } + default: + error("unrecognised drawing command '%1'", char(code)); + break; + } +} + +void lj4_printer::hpgl_init() +{ + if (hpgl_inited) + return; + hpgl_inited = 1; + hpgl_scale = double(DEFAULT_HPGL_UNITS)/font::res; + printf("\033&f0S" // push position + "\033*p0x0Y" // move to 0,0 + "\033*c%dx%dy0T" // establish picture frame + "\033%%1B" // switch to HPGL + "SP1SC0,%.4f,0,-%.4f,2IR0,100,0,100" // set up scaling + "LA1,4,2,4" // round line ends and joins + "PR" // relative plotting + "TR0" // opaque + ";\033%%1A" // back to PCL + "\033&f1S", // pop position + MAX_PAPER_WIDTH, MAX_PAPER_HEIGHT, + hpgl_scale, hpgl_scale); +} + +void lj4_printer::set_line_thickness(int size, int dot) +{ + double pw; + if (line_thickness < 0) + pw = (size * (line_width_factor * 25.4))/(font::sizescale * 72000.0); + else + pw = line_thickness*25.4/font::res; + if (dot && pw < MIN_DOT_PEN_WIDTH) + pw = MIN_DOT_PEN_WIDTH; + if (pw != pen_width) { + printf("PW%f", pw); + pen_width = pw; + } +} + +font *lj4_printer::make_font(const char *nm) +{ + return lj4_font::load_lj4_font(nm); +} + +printer *make_printer() +{ + return new lj4_printer(user_paper_size); +} + +static +int lookup_paper_size(const char *s) +{ + for (unsigned int i = 0; + i < sizeof(paper_table)/sizeof(paper_table[0]); i++) { + // FIXME Perhaps allow unique prefix. + if (strcasecmp(s, paper_table[i].name) == 0) + return i; + } + return -1; +} + +static void usage(FILE *stream); + +extern "C" int optopt, optind; + +int main(int argc, char **argv) +{ + setlocale(LC_NUMERIC, "C"); + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + int c; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((c = getopt_long(argc, argv, "c:d:F:I:lp:vw:", long_options, NULL)) + != EOF) + switch(c) { + case 'l': + landscape_flag = 1; + break; + case 'I': + // ignore include search path + break; + case ':': + if (optopt == 'd') { + fprintf(stderr, "duplex assumed to be long-side\n"); + duplex_flag = 1; + } else + fprintf(stderr, "option -%c requires an argument\n", optopt); + fflush(stderr); + break; + case 'd': + if (!isdigit(*optarg)) // this ugly hack prevents -d without + optind--; // args from messing up the arg list + duplex_flag = atoi(optarg); + if (duplex_flag != 1 && duplex_flag != 2) { + fprintf(stderr, "odd value for duplex; assumed to be long-side\n"); + duplex_flag = 1; + } + break; + case 'p': + { + int n = lookup_paper_size(optarg); + if (n < 0) + error("ignoring invalid paper format '%1'", font::papersize); + else + user_paper_size = n; + break; + } + case 'v': + printf("GNU grolj4 (groff) version %s\n", Version_string); + exit(0); + break; + case 'F': + font::command_line_font_dir(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 + line_width_factor = int(n); + break; + } + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + SET_BINARY(fileno(stdout)); + if (optind >= argc) + do_file("-"); + else { + for (int i = optind; i < argc; i++) + do_file(argv[i]); + } + return 0; +} + +static void usage(FILE *stream) +{ + fprintf(stream, + "usage: %s [-l] [-c n] [-d [n]] [-F dir] [-p paper-format]" + " [-w n] [file ...]\n" + "usage: %s {-v | --version}\n" + "usage: %s --help\n", + program_name, program_name, program_name); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/devices/gropdf/TODO b/src/devices/gropdf/TODO new file mode 100644 index 0000000..12042c2 --- /dev/null +++ b/src/devices/gropdf/TODO @@ -0,0 +1,31 @@ +pspic.tmac +---------- + +Equiv for gropdf is pdfpic (which is dependent on a program pdfbb (to +extract MediaBox (etc.) from the pdf) which is not written yet! Meanwhile +you could use \X'pdf: pdfpic filename -L|R|C wid (hgt) (linelen)' (-R and -C +require a linelen) Wid or hgt may be zero (in which case the same scaling as +the other axis is used). The disadvantage of this call (over pdfpic macro) +is that it is transparent to groff, after placing the image the current X/Y +position remains what it was, so you need to do your own 'motion control' +(like a .sp) to "step over" the image you just placed. + +psfig.tmac +---------- + +No equiv for gropdf. + +psatk.tmac +---------- + +No equiv for gropdf. + +-I : search -I directory for included files + +-w : set line width + +Another \X : \X'ps: exec 0 setlinejoin'\X'ps: exec 0 setlinecap' for mom + +Cater for fonts with >255 glyphs (currently accessing a glyph above 255 +(i.e. \N[260]) causes a fail). This will be fixed when font subsetting is +implemented. diff --git a/src/devices/gropdf/gropdf.1.man b/src/devices/gropdf/gropdf.1.man new file mode 100644 index 0000000..d1d39bb --- /dev/null +++ b/src/devices/gropdf/gropdf.1.man @@ -0,0 +1,1845 @@ +.TH gropdf @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +gropdf \- +.I groff +output driver for Portable Document Format +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 2011-2022 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_gropdf_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.\" This macro definition is poor style from a portability standpoint, +.\" but it's a good test and demonstration of the standard font +.\" repertoire for the devices where it has any effect at all, and so +.\" should be retained. +.de FT +. if '\\*(.T'ps' .ft \\$1 +. if '\\*(.T'pdf' .ft \\$1 +.. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY gropdf +.RB [ \-dels ] +.RB [ \-F\~\c +.IR font-directory ] +.RB [ \-I\~\c +.IR inclusion-directory ] +.RB [ \-p\~\c +.IR paper-format ] +\#.RB [ \-w\~\c +\#.IR n ] +.RB [ \-u +.RI [ cmap-file ]] +.RB [ \-y\~\c +.IR foundry ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY gropdf +.B \-\-help +.YS +. +. +.SY gropdf +.B \-v +. +.SY gropdf +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU +.I roff +PDF output driver translates the output of +.MR @g@troff @MAN1EXT@ +into Portable Document Format. +. +Normally, +.I gropdf +is invoked by +.MR groff @MAN1EXT@ +when the latter is given the +.RB \[lq] \-T\~pdf \[rq] +option. +. +(In this installation, +.B @DEVICE@ +is the default output device.) +. +Use +.IR groff 's +.B \-P +option to pass any options shown above to +.IR gropdf . +. +If no +.I file +arguments are given, +or if +.I file +is \[lq]\-\[rq], +.I gropdf +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.P +See section \[lq]Font installation\[rq] below for a guide to installing +fonts for +.IR gropdf . +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-d +Include debug information as comments within the PDF. +. +Also produces an uncompressed PDF. +. +. +.TP +.B \-e +Forces +.I gropdf +to embed +.I all +fonts (even the 14 base PDF fonts). +. +. +.TP +.BI \-F " dir" +Prepend directory +.IR dir /dev name +to the search path for font, and device description files; +.I name +is the name of the device, usually +.BR pdf . +. +.TP +.BI \-I\~ dir +Search the directory +.I dir +for files named in +.B \[rs]X\[aq]pdf: pdfpic\[aq] +device control commands. +. +.B \-I +may be specified more than once; +each +.I dir +is searched in the given order. +. +To search the current working directory before others, +add +.RB \[lq] "\-I .\&" \[rq] +at the desired place; +it is otherwise searched last. +. +. +.TP +.B \-l +Orient the document in landscape format. +. +.TP +.BI \-p " paper-format" +Set the physical dimensions of the output medium. +. +This overrides the +.BR papersize , +.BR paperlength , +and +.B paperwidth +directives in the +.I DESC +file; +it accepts the same arguments as the +.B papersize +directive. +. +See +.MR groff_font @MAN5EXT@ +for details. +. +. +.TP +.B \-s +Append a comment line to end of PDF showing statistics, +i.e.\& number of pages in document. +. +Ghostscript's +.B ps2pdf +complains about this line if it is included, but works anyway. +. +. +.TP +.BR \-u \~[\c +.IR cmap-file ] +.I gropdf +normally includes a ToUnicode CMap with any font created using +.I text.enc +as the encoding file, +this makes it easier to search for words which contain ligatures. +. +You can include your own CMap by specifying a +.I cmap-file +or have no CMap at all by omitting the argument. +. +. +.\" .TP +.\" .BI \-w n +.\" Lines should be drawn using a thickness of +.\" .IR n \~\c +.\" thousandths of an em. +.\" . +.\" If this option is not given, the line thickness defaults to +.\" 0.04\~em. +.\" . +.\" . +.TP +.BI \-y " foundry" +Set the foundry to use for selecting fonts of the same name. +. +. +.\" ==================================================================== +.SH Usage +.\" ==================================================================== +. +The input to +.I gropdf +must be in the format output by +.MR @g@troff @MAN1EXT@ . +. +This is described in +.MR groff_out @MAN5EXT@ . +. +In addition, the device and font description files for the device used +must meet certain requirements: +. +The resolution must be an integer multiple of\~72 times the +.BR sizescale . +. +The +.B pdf +device uses a resolution of 72000 and a sizescale of 1000. +. +. +.LP +The device description file must contain a valid paper format; +see +.MR groff_font @MAN5EXT@ . +. +.I gropdf +uses the same Type\~1 Adobe PostScript fonts as the +.B grops +device driver. +. +Although the PDF Standard allows the use of other font types (like +TrueType) this implementation only accepts the Type\~1 PostScript +font. +. +Fewer Type\~1 fonts are supported natively in PDF documents than the +standard 35 fonts supported by +.B grops +and all PostScript printers, but all the fonts are available since any +which aren't supported natively are automatically embedded in the +PDF. +. +. +.LP +.I gropdf +supports the concept of foundries, +that is different versions of basically the same font. +. +During install a +.I Foundry +file controls where fonts are found and builds +.I groff +fonts from the files it discovers on your system. +. +. +.LP +Each font description file must contain a command +. +.IP +.BI internalname\ psname +. +.LP +which says that the PostScript name of the font is +.IR psname . +. +Lines starting with +.B # +and blank lines are ignored. +. +The code for each character given in the font file must correspond +to the code in the default encoding for the font. +. +This code can be used with the +.B \[rs]N +escape sequence in +.B troff +to select the character, +even if the character does not have a +.I groff +name. +. +Every character in the font file must exist in the PostScript font, and +the widths given in the font file must match the widths used +in the PostScript font. +. +. +.LP +Note that +.I gropdf +is currently only able to display the first 256 glyphs in any font. +This restriction will be lifted in a later version. +. +. +.\" .LP +.\" Note that +.\" .B grops +.\" is able to display all glyphs in a PostScript font, not only 256. +.\" .I enc_file +.\" (or the default encoding if no encoding file specified) just defines +.\" the order of glyphs for the first 256 characters; +.\" all other glyphs are accessed with additional encoding vectors which +.\" .B grops +.\" produces on the fly. +. +. +.LP +.I gropdf +can automatically include the downloadable fonts necessary +to print the document. +. +Fonts may be in PFA or PFB format. +.LP +. +Any downloadable fonts which should, when required, be included by +.I gropdf +must be listed in the file +.IR @FONTDIR@/\:\%devpdf/\:\%download ; +this should consist of lines of the form +. +.IP +.I +foundry font filename +. +.LP +where +.I foundry +is the foundry name or blank for the default foundry. +. +.I font +is the PostScript name of the font, +and +.I filename +is the name of the file containing the font; +lines beginning with +.B # +and blank lines are ignored; +fields must be separated by tabs +(spaces are +.B not +allowed); +.I filename +is searched for using the same mechanism that is used +for +.I groff +font metric files. +. +The +.I download +file itself is also sought using this mechanism. +. +Foundry names are usually a single character +(such as \[oq]U\[cq] for the URW foundry) +or empty for the default foundry. +. +This default uses the same fonts as +.I ghostscript +uses when it embeds fonts in a PDF file. +. +. +.LP +In the default setup there are styles called +.BR R , +.BR I , +.BR B , +and +.B BI +mounted at font positions 1 to\~4. +. +The fonts are grouped into families +.BR A , +.BR BM , +.BR C , +.BR H , +.BR HN , +.BR N , +.BR P , +and\~\c +.B T +having members in each of these styles: +. +.RS +.TP +.B AR +.FT AR +AvantGarde-Book +.FT +. +.TQ +.B AI +.FT AI +AvantGarde-BookOblique +.FT +. +.TQ +.B AB +.FT AB +AvantGarde-Demi +.FT +. +.TQ +.B ABI +.FT ABI +AvantGarde-DemiOblique +.FT +. +.TQ +.B BMR +.FT BMR +Bookman-Light +.FT +. +.TQ +.B BMI +.FT BMI +Bookman-LightItalic +.FT +. +.TQ +.B BMB +.FT BMB +Bookman-Demi +.FT +. +.TQ +.B BMBI +.FT BMBI +Bookman-DemiItalic +.FT +. +.TQ +.B CR +.FT CR +Courier +.FT +. +.TQ +.B CI +.FT CI +Courier-Oblique +.FT +. +.TQ +.B CB +.FT CB +Courier-Bold +.FT +. +.TQ +.B CBI +.FT CBI +Courier-BoldOblique +.FT +. +.TQ +.B HR +.FT HR +Helvetica +.FT +. +.TQ +.B HI +.FT HI +Helvetica-Oblique +.FT +. +.TQ +.B HB +.FT HB +Helvetica-Bold +.FT +. +.TQ +.B HBI +.FT HBI +Helvetica-BoldOblique +.FT +. +.TQ +.B HNR +.FT HNR +Helvetica-Narrow +.FT +. +.TQ +.B HNI +.FT HNI +Helvetica-Narrow-Oblique +.FT +. +.TQ +.B HNB +.FT HNB +Helvetica-Narrow-Bold +.FT +. +.TQ +.B HNBI +.FT HNBI +Helvetica-Narrow-BoldOblique +.FT +. +.TQ +.B NR +.FT NR +NewCenturySchlbk-Roman +.FT +. +.TQ +.B NI +.FT NI +NewCenturySchlbk-Italic +.FT +. +.TQ +.B NB +.FT NB +NewCenturySchlbk-Bold +.FT +. +.TQ +.B NBI +.FT NBI +NewCenturySchlbk-BoldItalic +.FT +. +.TQ +.B PR +.FT PR +Palatino-Roman +.FT +. +.TQ +.B PI +.FT PI +Palatino-Italic +.FT +. +.TQ +.B PB +.FT PB +Palatino-Bold +.FT +. +.TQ +.B PBI +.FT PBI +Palatino-BoldItalic +.FT +. +.TQ +.B TR +.FT TR +Times-Roman +.FT +. +.TQ +.B TI +.FT TI +Times-Italic +.FT +. +.TQ +.B TB +.FT TB +Times-Bold +.FT +. +.TQ +.B TBI +.FT TBI +Times-BoldItalic +.FT +.RE +. +. +.LP +There is also the following font which is not a member of a family: +. +.RS +.TP +.B ZCMI +.FT ZCMI +ZapfChancery-MediumItalic +.FT +.RE +. +. +.LP +There are also some special fonts called +.B S +for the PS Symbol font. +. +The lower case greek characters are automatically slanted (to match +the SymbolSlanted font (SS) available to PostScript). +. +Zapf Dingbats is available as +.BR ZD ; +the \[lq]hand pointing left\[rq] glyph +.RB ( \[rs][lh] ) +is available since it has been defined using the +.B \[rs]X\[aq]pdf: xrev\[aq] +device control command, +which reverses the direction of letters within words. +. +. +.LP +The default color for +.B \[rs]m +and +.B \[rs]M +is black. +. +. +.LP +.I gropdf +understands some of the device control commands supported by +.MR grops 1 . +. +. +.TP +.B \[rs]X\[aq]ps: invis\[aq] +Suppress output. +. +. +.TP +.B \[rs]X\[aq]ps: endinvis\[aq] +Stop suppressing output. +. +. +.TP +.BI "\[rs]X\[aq]ps: exec gsave currentpoint 2 copy translate\~" n\~\c +.B rotate neg exch neg exch translate\[aq] +where +.I n +is the angle of rotation. +. +This is to support the +.B align +command in +.MR @g@pic 1 . +. +. +.TP +.B \[rs]X\[aq]ps: exec grestore\[aq] +Used by +.MR @g@pic 1 +to restore state after rotation. +. +. +.TP +.BI "\[rs]X\[aq]ps: exec " "n\~" "setlinejoin\[aq]" +where +.I n +can be one of the following values. +. +. +.IP +0 = Miter join +.br +1 = Round join +.br +2 = Bevel join +. +. +.TP +.BI "\[rs]X\[aq]ps: exec " "n " "setlinecap\[aq]" +where +.I n +can be one of the following values. +. +. +.IP +0 = Butt cap +.br +1 = Round cap, and +.br +2 = Projecting square cap +. +. +.LP +.TP +.BR "\[rs]X\[aq]ps:\~" .\|.\|.\& "\~pdfmark\[aq]" +All the +.I pdfmark +macros installed by using +.I \-m pdfmark +or +.I \-m mspdf +(see documentation in +.IR pdfmark.pdf ). +. +A subset of these macros are installed automatically when you use +.B \-Tpdf +so you should not need to use +.RB \[lq] "\-m pdfmark" \[rq] +to access most PDF functionality. +. +. +.LP +.I gropdf +also supports a subset of the commands introduced in +.IR present.tmac . +. +Specifically it supports:- +. +. +.IP +PAUSE +.br +BLOCKS +.br +BLOCKE +. +. +.LP +Which allows you to create presentation type PDFs. +. +Many of the other +commands are already available in other macro packages. +. +. +.LP +These commands are implemented with +.I groff +X commands:- +. +. +.LP +.TP +.B \[rs]X\[aq]ps: exec %%%%PAUSE\[aq] +The section before this is treated as a block and is introduced using +the current +.B BLOCK +transition setting +(see +.RB \[lq] "\[rs]X\[aq]pdf: transition\[aq]" \[rq] +below). +. +Equivalently, +.B \%.pdfpause +is available as a macro. +.TP +.B \[rs]X\[aq]ps: exec %%%%BEGINONCE\[aq] +Any text following this command (up to %%%%ENDONCE) is shown only once, +the next %%%%PAUSE will remove it. +If producing a non-presentation PDF, i.e.\& +ignoring the pauses, see +.I \%GROPDF_NOSLIDE +below, this text is ignored. +.LP +.TP +.B \[rs]X\[aq]ps: exec %%%%ENDONCE\[aq] +This terminates the block defined by %%%%BEGINONCE. +This pair of commands +is what implements the \&.BLOCKS Once/.BLOCKE commands in +.IR present.tmac . +. +. +.LP +The +.I mom +macro package already integrates these extensions, +so you can build slides with +.IR mom . +. +. +.LP +If you use +.I present.tmac +with +.I gropdf +there is no need to run the program +.MR presentps @MAN1EXT@ +since the output will already be a presentation PDF. +. +. +.LP +All other +.B ps: +tags are silently ignored. +. +. +.LP +One +.B \[rs]X +device control command used by the DVI driver is also recognised. +. +. +.TP +.BI \[rs]X\[aq]papersize= paper-format \[aq] +where the +.I paper-format +parameter is the same as that to the +.B papersize +directive. +. +See +.MR groff_font @MAN5EXT@ . +. +This means that you can alter the page size at will within the PDF file +being created by +.IR gropdf . +. +If you do want to change the paper format, +it must be done before you start creating the page. +. +. +.LP +.I gropdf +supports several more device control features using the +.B pdf: +tag. +. +Some have counterpart +.I convenience macros +that take the same arguments and behave equivalently. +. +. +.TP +.BI "\[rs]X\[aq]pdf: pdfpic\~" file\~\c +.IR "alignment width height line-length" \[aq] +Place an image of the specified +.I width +containing the PDF drawing from file +.I file +of desired +.I width +and +.I height +(if +.I height +is missing or zero then it is scaled proportionally). +. +If +.I alignment +is +.B \-L +the drawing is left-aligned. +. +If it is +.B \-C +or +.B \-R +a +.I line-length +greater than the width of the drawing is required as well. +. +If +.I width +is specified as zero then the width is scaled in proportion to the +height. +. +.\" .IP +.\" See +.\" .BR groff_tmac (@MAN7EXT@) +.\" for a description of the +.\" .B PSPIC +.\" macro which provides a convenient high-level interface for inclusion +.\" of PostScript graphics. +. +.TP +.B \[rs]X\[aq]pdf: xrev\[aq] +Toggle the reversal of glyph direction. +. +This feature works \[lq]letter by letter\[rq], +that is, +each letter in a word is reversed left-to-right, +not the entire word. +. +One application is the reversal of glyphs in the Zapf Dingbats font. +. +To restore the normal glyph orientation, +repeat the command. +. +. +.TP +.BI "\[rs]X\[aq]pdf: markstart " "/ANN-definition" \[aq] +.TQ +.B \[rs]X\[aq]pdf: markend\[aq] +Macros that support PDF bookmarks use these calls internally to +start and stop (respectively) the placement of the bookmark's +.I hot spot; +the user will have called +.RB \[lq] .pdfhref\~L \[rq] +with the text of the hot spot. +. +Normally, +these are never used except from within the +.I pdfmark +macros. +. +. +.TP +.B \[rs]X\[aq]pdf: marksuspend\[aq] +.TQ +.B \[rs]X\[aq]pdf: markrestart\[aq] +If you use a page location trap to produce a header or footer, +or otherwise interrupt a document's text, +you need to use these commands if a PDF +.I hot spot +crosses a trap boundary; +otherwise any text output by the trap will be marked as part of the hot +spot. +. +To prevent this error, +place these device control commands or their corresponding +convenience macros +.B \%.pdfmarksuspend +and +.B \%.pdfmarkrestart +at the start and end of the trap macro, +respectively. +. +. +.TP +.BI "\[rs]X\[aq]pdf: pagename\~" name \[aq] +Assign the current page a +.IR name . +. +All documents bear two default names, +.RB \[oq] top "\[cq] and \[oq]" bottom \[cq]. +. +The convenience macro for this command is +.BR \%.pdfpagename . +. +. +.TP +.BI "\[rs]X'pdf: switchtopage\~" "when name" \[aq] +Normally each new page is appended to the end of the document, +this command allows following pages to be inserted at a +.I \[oq]named\[cq] +position within the document (see pagename command above). +.I \[oq]when\[cq] +can be either +.RI \[oq] after "\[cq] or \[oq]" before \[cq]. +If it is omitted it defaults to +.RI \[oq] before \[cq]. +. +It should be used at the end of the page before you want the switch to +happen. +. +This allows pages such as a TOC to be moved to elsewhere in the +document, +but more esoteric uses are possible. +. +The convenience macro for this command is +.BR \%.pdfswitchtopage . +. +. +.TP +.BI \[rs]X\[aq]pdf:\~transition\~ feature\~\c +.IB "mode duration dimension motion direction scale bool" \[aq] +where +.I feature +can be either SLIDE or BLOCK. +When it is SLIDE the transition is used +when a new slide is introduced to the screen, +if BLOCK then this transition is used for the individual blocks which +make up the slide. +. +. +.IP +.I mode +is the transition type between slides:- +.RS +.IP +.B Split +- Two lines sweep across the screen, revealing the new page. +The lines +may be either horizontal or vertical and may move inward from the +edges of the page or outward from the center, as specified by the +.I dimension +and +.I motion +entries, respectively. +.br +.B Blinds +- Multiple lines, evenly spaced across the screen, synchronously +sweep in the same direction to reveal the new page. +The lines may be +either horizontal or vertical, as specified by the +.I dimension +entry. +Horizontal +lines move downward; vertical lines move to the right. +.br +.B Box +- A rectangular box sweeps inward from the edges of the page or +outward from the center, as specified by the +.I motion +entry, revealing the new page. +.br +.B Wipe +- A single line sweeps across the screen from one edge to the other in +the direction specified by the +.I direction +entry, revealing the new page. +.br +.B Dissolve +- The old page dissolves gradually to reveal the new one. +.br +.B Glitter +- Similar to Dissolve, +except that the effect sweeps across the page in a wide band moving from +one side of the screen to the other in the direction specified by the +.I direction +entry. +.br +.B R +- The new page simply replaces the old one with no special transition +effect; the +.I direction +entry shall be ignored. +.br +.B Fly +- (PDF 1.5) Changes are flown out or in (as specified by +.IR motion ), +in the +direction specified by +.IR direction , +to or from a location that is offscreen except +when +.I direction +is +.BR None . +.br +.B Push +- (PDF 1.5) The old page slides off the screen while the new page +slides in, pushing the old page out in the direction specified by +.IR direction . +.br +.B Cover +- (PDF 1.5) The new page slides on to the screen in the direction +specified by +.IR direction , +covering the old page. +.br +.B Uncover +- (PDF 1.5) The old page slides off the screen in the direction +specified by +.IR direction , +uncovering the new page in the direction +specified by +.IR direction . +.br +.B Fade +- (PDF 1.5) The new page gradually becomes visible through the +old one. +.LP +.RE +.IP +.I duration +is the length of the transition in seconds (default 1). +.LP +.IP +.I dimension +(Optional; +.BR Split " and " Blinds +transition styles only) The dimension in which the +specified transition effect shall occur: +.B H +Horizontal, or +.B V +Vertical. +.LP +.IP +.I motion +(Optional; +.BR Split , +.BR Box " and " Fly +transition styles only) The direction of motion for +the specified transition effect: +.B I +Inward from the edges of the page, or +.B O +Outward from the center of the page. +.LP +.IP +.I direction +(Optional; +.BR Wipe , +.BR Glitter , +.BR Fly , +.BR Cover , +.BR Uncover " and " Push +transition styles only) +The direction in which the specified transition effect shall moves, +expressed in degrees counterclockwise starting from a left-to-right +direction. +If the value is a number, it shall be one of: +.B 0 += Left to right, +.B 90 += Bottom to top (Wipe only), +.B 180 += Right to left (Wipe only), +.B 270 += Top to bottom, +.B 315 += Top-left to bottom-right (Glitter only) +The value can be +.BR None , +which is relevant only for the +.B Fly +transition when the value of +.I scale +is not 1.0. +.LP +.IP +.I scale +(Optional; PDF 1.5; +.B Fly +transition style only) The starting or ending scale at +which the changes shall be drawn. +If +.I motion +specifies an inward transition, the scale +of the changes drawn shall progress from +.I scale +to 1.0 over the course of the +transition. +If +.I motion +specifies an outward transition, the scale of the changes drawn +shall progress from 1.0 to +.I scale +over the course of the transition +.LP +.IP +.I bool +(Optional; PDF 1.5; +.B Fly +transition style only) If +.BR true , +the area that shall be flown +in is rectangular and opaque. +.LP +.IP +This command can be used by calling the macro +.B .pdftransition +using the parameters described above. +Any of the parameters may be +replaced with a "." which signifies the parameter retains its +previous value, also any trailing missing parameters are ignored. +.LP +.IP +.B Note: +not all PDF Readers support any or all these transitions. +.LP +. +. +.TP +.BI "\eX\[aq]pdf: background\~" "cmd left top right bottom weight" \[aq] +.TQ +.B "\eX\[aq]pdf: background off\[aq]" +.TQ +.BI "\eX\[aq]pdf: background footnote\~" bottom \[aq] +produces a background rectangle on the page, +where +.RS +.TP +.I cmd +is the command, +which can be any of +.RB \[lq] page | fill | box \[rq] +in combination. +. +Thus, +.RB \[lq] pagefill \[rq] +would draw a rectangle which covers the whole current page size +(in which case the rest of the parameters can be omitted because the box +dimensions are taken from the current media size). +. +.RB \[lq] boxfill \[rq], +on the other hand, +requires the given dimensions to place the box. +. +Including +.RB \[lq] fill \[rq] +in the command will paint the rectangle with the current fill colour +(as with +.BR \[rs]M[] ) +and including +.RB \[lq] box \[rq] +will give the rectangle a border in the current stroke colour +(as with +.BR \[rs]m[] ). +. +. +.IP +.I cmd +may also be +.RB \[lq] off \[rq] +on its own, +which will terminate drawing the current box. +. +If you have specified a page colour with +.RB \[lq] pagefill \[rq], +it is always the first box in the stack, +and if you specify it again, +it will replace the first entry. +. +Be aware that the +.RB \[lq] pagefill \[rq] +box renders the page opaque, +so tools that \[lq]watermark\[rq] PDF pages are unlikely to be +successful. +. +To return the background to transparent, +issue an +.RB \[lq] off \[rq] +command with no other boxes open. +. +. +.IP +Finally, +.I cmd +may be +.RB \[lq] footnote \[rq] +followed by a new value for +.IR bottom , +which will be used for all open boxes on the current page. +This is to allow room for footnote areas that grow while a page is +processed +(to accommodate multiple footnotes, +for instance). +. +(If the value is negative, +it is used as an offset from the bottom of the page.) +. +. +.TP +.I left +.TQ +.I top +.TQ +.I right +.TQ +.I bottom +are the coordinates of the box. +. +The +.I top +and +.I bottom +coordinates are the minimum and maximum for the box, +since the actual start of the box is +.IR groff 's +drawing position when you issue the command, +and the bottom of the box is the point where you turn the box +.RB \[lq] off \[rq]. +. +The top and bottom coordinates are used only if the box drawing extends +onto the next page; +ordinarily, +they would be set to the header and footer margins. +. +. +.TP +.I weight +provides the line width for the border if +.RB \[lq] box \[rq] +is included in the command. +. +. +.P +The convenience macro for this escape sequence is +.BR .pdfbackground . +. +An +.I sboxes +macro file is also available; +see +.MR groff_tmac @MAN5EXT@ . +.RE +. +. +.\" ==================================================================== +.SS Macros +.\" ==================================================================== +. +.IR gropdf 's +support macros in +.I pdf\.tmac +define the convenience macros described above. +. +Some features have no direct device control command counterpart. +. +. +.\" pdfhref +. +. +.TP +.BI ".pdfinfo /" "field content"\~\c +\&.\|.\|. +Define PDF metadata. +. +.I field +may be be one of +.BR Title , +.BR Author , +.BR Subject , +.BR Keywords , +or another datum supported by the PDF standard or your reader. +. +.I field +must be prefixed with a slash. +. +. +.\" ==================================================================== +.SS "Importing graphics" +.\" ==================================================================== +. +.I gropdf +supports only the inclusion of other PDF files for inline images. +. +Such a PDF file may, +however, +contain any of the graphic formats supported by +the PDF standard, +such as JPEG/JFIF, +PNG, +and GIF. +. +Any application that outputs PDF can thus be used to prepare files for +embedding in documents processed by +.I groff +and +.IR gropdf . +. +. +.P +The PDF file you wish to insert must be a single page and the drawing +must just fit inside the media size of the PDF file. +. +In +.MR inkscape 1 +or +.MR gimp 1 , +for example, +make sure the canvas size just fits the image. +. +. +.P +The PDF parser +.I gropdf +implements has not been rigorously tested with all applications that +produce PDF. +. +If you find a single-page PDF which fails to import properly, +try processing it with the +.MR pdftk 1 +program. +. +. +.RS +.EX +pdftk\~\c +.I existing-file\~\c +output\~\c +.I new-file +.EE +.RE +. +You may find that +.I new-file +imports successfully. +. +. +.\" ==================================================================== +.SS "TrueType and other font formats" +.\" ==================================================================== +. +.I gropdf +does not yet support any font formats besides Adobe Type 1 +(PFA or PFB). +. +. +.\" ==================================================================== +.SH "Font installation" +.\" ==================================================================== +. +The following is a step-by-step font installation guide for +.I gropdf. +. +. +.IP \[bu] 2n +Convert your font to something +.I groff +understands. +. +This is a PostScript Type\~1 font in PFA or PFB format, +together with an AFM file. +. +A PFA file begins as follows. +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +%!PS\-AdobeFont\-1.0: +.EE +.RE \" but only one to get back to it +. +A PFB file contains this string as well, +preceded by some non-printing bytes. +. +In the following steps, +we will consider the use of CTAN's +.UR https://\:ctan.org/\:tex\-archive/\:fonts/\:brushscr +BrushScriptX-Italic +.UE +font in PFA format. +.RE \" now restore left margin +. +. +.IP \[bu] +Convert the AFM file to a +.I groff +font description file with the +.MR afmtodit @MAN1EXT@ +program. +. +For instance, +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +$ \c +.B afmtodit BrushScriptX\-Italic.afm text.map BSI +.EE +.RE \" but only one to get back to it +. +converts the Adobe Font Metric file +.I BrushScriptX\-Italic.afm +to the +.I groff +font description file +.IR BSI . +.RE \" now restore left margin +. +. +.IP +If you have a font family which provides regular upright (roman), +bold, +italic, +and +bold-italic styles, +(where \[lq]italic\[rq] may be \[lq]oblique\[rq] or \[lq]slanted\[rq]), +we recommend using +.BR R , +.BR B , +.BR I , +and +.BR BI , +respectively, +as suffixes to the +.I groff +font family name to enable +.IR groff 's +font family and style selection features. +. +An example is +.IR groff 's +built-in support for Times: +the font family +name is abbreviated as +.BR T , +and the +.I groff +font names are therefore +.BR TR , +.BR TB , +.BR TI , +and +.BR TBI . +. +In our example, +however, +the BrushScriptX font is available in a single style only, +italic. +. +. +.IP \[bu] +Install the +.I groff +font description file(s) in a +.I devpdf +subdirectory in the search path that +.I groff +uses for device and font file descriptions. +. +See the +.I GROFF_FONT_PATH +entry in section \[lq]Environment\[rq] of +.MR @g@troff @MAN1EXT@ +for the current value of the font search path. +. +While +.I groff +doesn't directly use AFM files, +it is a good idea to store them alongside its font description files. +. +. +.IP \[bu] +Register fonts in the +.I devpdf/download +file so they can be located for embedding in PDF files +.I gropdf +generates. +. +Only the first +.I download +file encountered in the font search path is read. +. +If in doubt, +copy the default +.I download +file +(see section \[lq]Files\[rq] below) +to the first directory in the font search path and add your fonts there. +. +The PostScript font name used by +.I gropdf +is stored in the +.B internalname +field in the +.I groff +font description file. +. +(This name does not necessarily resemble the font's file name.) +. +If the font in our example had originated from a foundry named +.BR Z , +we would add the following line to +.IR download . +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +Z\[->]BrushScriptX\-Italic\[->]BrushScriptX\-Italic.pfa +.EE +.RE \" but only one to get back to it +. +A tab character, +depicted as \[->], +separates the fields. +. +The default foundry has no name: +its field is empty and +entries corresponding to it start with a tab character, +as will the one in our example. +.RE \" now restore left margin +. +. +.IP \[bu] +Test the selection and embedding of the new font. +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +printf "\[rs]\[rs]f[BSI]Hello, world!\[rs]n" \ +| groff \-T pdf \-P \-e >hello.pdf +see hello.pdf +.EE +.RE +.RE +. +. +.br +.ne 5v +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +A list of directories in which to seek the selected output device's +directory of device and font description files. +. +If, +in the +.I download +file, +the font file has been specified with a full path, +no directories are searched. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.TP +.I GROPDF_NOSLIDE +If set and evaluates to a true value +(to Perl), +.\" XXX: The above is inconsistent with the way grotty(1) handles +.\" "GROFF_NO_SGR". +.I gropdf +ignores commands specific to presentation PDFs, +producing a normal PDF instead. +. +. +.TP +.I SOURCE_DATE_EPOCH +A timestamp +(expressed as seconds since the Unix epoch) +to use as the output creation timestamp in place of the current time. +. +The time is converted to human-readable form using Perl's +.I \%localtime() +function and recorded in a PDF comment. +. +. +.TP +.I TZ +The time zone to use when converting the current time +(or value of +.IR SOURCE_DATE_EPOCH ) +to human-readable form; +see +.MR tzset 3 . +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @FONTDIR@/\:\%devpdf/\:DESC +describes the +.B pdf +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devpdf/ F +describes the font known +.RI as\~ F +on device +.BR pdf . +. +. +.TP +.IR @FONTDIR@/\:\%devpdf/\:U\- F +describes the font +from the URW foundry +(versus the Adobe default) +known +.RI as\~ F +on device +.BR pdf . +. +. +.TP +.I @FONTDIR@/\:\%devpdf/\%download +lists fonts available for embedding within the PDF document +(by analogy to the +.B ps +device's downloadable font support). +. +. +.\" XXX: Why are we shipping this but not BuildFoundries.pl? +.TP +.I @FONTDIR@/\:\%devpdf/\%Foundry +is a data file used by the +.I groff +build system to locate PostScript Type\~1 fonts. +. +. +.TP +.I @FONTDIR@/\:\%devpdf/\:enc/\:\%text\:.enc +describes the encoding scheme used by most PostScript Type\~1 fonts; +the +.B \%encoding +directive of +font description files for the +.B pdf +device refers to it. +. +. +.TP +.I @MACRODIR@/\:pdf\:.tmac +defines macros for use with the +.B pdf +output device. +. +It is automatically loaded by +.I troffrc +when the +.B pdf +output device is selected. +. +. +.TP +.I @MACRODIR@/\:\%pdfpic\:.tmac +defines the +.B PDFPIC +macro for embedding images in a document; +see +.MR groff_tmac @MAN5EXT@ . +. +It is automatically loaded by +.I troffrc. +.\" +.\" +.\" .TP +.\" .B @MACRODIR@/pspic.tmac +.\" Definition of +.\" .B PSPIC +.\" macro, +.\" automatically loaded by +.\" .BR ps.tmac . +.\" . +. +. +.\" ==================================================================== +.SH Authors +.\" ==================================================================== +. +.I gropdf +was written and is maintained by +.MT deri@\:chuzzlewit\:.myzen\:.co\:.uk +Deri James +.ME . +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.TP +.I @DOCDIR@/\:\%sboxes/\:\%msboxes\:.ms +.TQ +.I @DOCDIR@/\:\%sboxes/\:\%msboxes\:.pdf +\[lq]Using PDF boxes with +.I groff +and the +.I ms +macros\[rq], +by Deri James. +. +. +.TP +.I present.tmac +is part of +.UR https://\:bob\:.diertens\:.org/\:corner/\:useful/\:gpresent/ +.I gpresent +.UE , +a software package by Bob Diertens that works with +.I groff +to produce presentations +(\[lq]foils\[rq], +or \[lq]slide decks\[rq]). +. +. +.P +.MR afmtodit @MAN1EXT@ , +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ , +.MR groff_font @MAN5EXT@ , +.MR groff_out @MAN5EXT@ +.\" Not actually referenced in above discussion. +.\" .BR \%pfbtops (@MAN1EXT@), +.\" .BR \%groff_tmac (@MAN5EXT@), +. +. +.\" Clean up. +.rm FT +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_gropdf_1_man_C] +.do rr *groff_gropdf_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/gropdf/gropdf.am b/src/devices/gropdf/gropdf.am new file mode 100644 index 0000000..3dbd865 --- /dev/null +++ b/src/devices/gropdf/gropdf.am @@ -0,0 +1,58 @@ +# Copyright (C) 2011-2020 Free Software Foundation, Inc. +# Written by Deri James <deri@chuzzlewit.myzen.co.uk> +# Automake migration by Bertrand Garrigues +# +# 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 <http://www.gnu.org/licenses/>. + +gropdf_dir = $(top_srcdir)/src/devices/gropdf + +bin_SCRIPTS += gropdf pdfmom +EXTRA_DIST += \ + src/devices/gropdf/TODO \ + src/devices/gropdf/gropdf.pl \ + src/devices/gropdf/pdfmom.pl \ + src/devices/gropdf/gropdf.1.man \ + src/devices/gropdf/pdfmom.1.man + +man1_MANS += \ + src/devices/gropdf/gropdf.1 \ + src/devices/gropdf/pdfmom.1 + +gropdf: $(gropdf_dir)/gropdf.pl $(SH_DEPS_SED_SCRIPT) + $(AM_V_GEN)$(RM) $@ \ + && sed -f $(SH_DEPS_SED_SCRIPT) \ + -e "s|[@]VERSION[@]|$(VERSION)|" \ + -e "s|[@]PERL[@]|$(PERL)|" \ + -e "s|[@]GROFF_FONT_DIR[@]|$(fontpath)|" \ + -e "s|[@]RT_SEP[@]|$(RT_SEP)|" $(gropdf_dir)/gropdf.pl \ + >$@ \ + && chmod +x $@ + +pdfmom: $(gropdf_dir)/pdfmom.pl $(SH_DEPS_SED_SCRIPT) + $(AM_V_GEN)$(RM) $@ \ + && sed -f $(SH_DEPS_SED_SCRIPT) \ + -e "s|[@]VERSION[@]|$(VERSION)|" \ + -e "s|[@]RT_SEP[@]|$(RT_SEP)|" \ + -e "s|[@]PERL[@]|$(PERL)|" $(gropdf_dir)/pdfmom.pl \ + >$@ \ + && chmod +x $@ + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/gropdf/gropdf.pl b/src/devices/gropdf/gropdf.pl new file mode 100644 index 0000000..c65a105 --- /dev/null +++ b/src/devices/gropdf/gropdf.pl @@ -0,0 +1,3928 @@ +#!@PERL@ +# +# gropdf : PDF post processor for groff +# +# Copyright (C) 2011-2020 Free Software Foundation, Inc. +# Written by Deri James <deri@chuzzlewit.myzen.co.uk> +# +# 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 <http://www.gnu.org/licenses/>. + +use strict; +use warnings; +use Getopt::Long qw(:config bundling); + +use constant +{ + WIDTH => 0, + CHRCODE => 1, + PSNAME => 2, + ASSIGNED => 3, + USED => 4, +}; + +my $prog=$0; + +my $gotzlib=0; + +my $rc = eval +{ + require Compress::Zlib; + Compress::Zlib->import(); + 1; +}; + +if($rc) +{ + $gotzlib=1; +} +else +{ + Warn("Perl module 'Compress::Zlib' not available; cannot compress" + . " this PDF"); +} + +my %cfg; + +$cfg{GROFF_VERSION}='@VERSION@'; +$cfg{GROFF_FONT_PATH}='@GROFF_FONT_DIR@'; +$cfg{RT_SEP}='@RT_SEP@'; +binmode(STDOUT); + +my @obj; # Array of PDF objects +my $objct=0; # Count of Objects +my $fct=0; # Output count +my %fnt; # Used fonts +my $lct=0; # Input Line Count +my $src_name=''; +my %env; # Current environment +my %fontlst; # Fonts Loaded +my $rot=0; # Portrait +my %desc; # Contents of DESC +my %download; # Contents of downlopad file +my $pages; # Pointer to /Pages object +my $devnm='devpdf'; +my $cpage; # Pointer to current pages +my $cpageno=0; # Object no of current page +my $cat; # Pointer to catalogue +my $dests; # Pointer to Dests +my @mediabox=(0,0,595,842); +my @defaultmb=(0,0,595,842); +my $stream=''; # Current Text/Graphics stream +my $cftsz=10; # Current font sz +my $cft; # Current Font +my $lwidth=1; # current linewidth +my $linecap=1; +my $linejoin=1; +my $textcol=''; # Current groff text +my $fillcol=''; # Current groff fill +my $curfill=''; # Current PDF fill +my $strkcol=''; +my $curstrk=''; +my @lin=(); # Array holding current line of text +my @ahead=(); # Buffer used to hol the next line +my $mode='g'; # Graphic (g) or Text (t) mode; +my $xpos=0; # Current X position +my $ypos=0; # Current Y position +my $tmxpos=0; +my $kernadjust=0; +my $curkern=0; +my $widtbl; # Pointer to width table for current font size +my $origwidtbl; # Pointer to width table +my $krntbl; # Pointer to kern table +my $matrix="1 0 0 1"; +my $whtsz; # Current width of a space +my $poschg=0; # V/H pending +my $fontchg=0; # font change pending +my $tnum=2; # flatness of B-Spline curve +my $tden=3; # flatness of B-Spline curve +my $linewidth=40; +my $w_flg=0; +my $nomove=0; +my $pendmv=0; +my $gotT=0; +my $suppress=0; # Suppress processing? +my %incfil; # Included Files +my @outlev=([0,undef,0,0]); # Structure pdfmark /OUT entries +my $curoutlev=\@outlev; +my $curoutlevno=0; # Growth point for @curoutlev +my $Foundry=''; +my $xrev=0; # Reverse x direction of font +my $matrixchg=0; +my $wt=-1; +my $thislev=1; +my $mark=undef; +my $suspendmark=undef; +my $boxmax=0; +my %missing; # fonts in download files which are not found/readable + + + +my $n_flg=1; +my $pginsert=-1; # Growth point for kids array +my %pgnames; # 'names' of pages for switchtopage +my @outlines=(); # State of Bookmark Outlines at end of each page +my $custompaper=0; # Has there been an X papersize +my $textenccmap=''; # CMap for groff text.enc encoding +my @XOstream=(); +my @PageAnnots={}; +my $noslide=0; +my $transition={PAGE => {Type => '/Trans', S => '', D => 1, Dm => '/H', M => '/I', Di => 0, SS => 1.0, B => 0}, + BLOCK => {Type => '/Trans', S => '', D => 1, Dm => '/H', M => '/I', Di => 0, SS => 1.0, B => 0}}; +my $firstpause=0; +my $present=0; +my @bgstack; # Stack of background boxes +my $bgbox=''; # Draw commands for boxes on this page + +$noslide=1 if exists($ENV{GROPDF_NOSLIDE}) and $ENV{GROPDF_NOSLIDE}; + +my %ppsz=( + 'ledger'=>[1224,792], + 'legal'=>[612,1008], + 'letter'=>[612,792], + 'a0'=>[2384,3370], + 'a1'=>[1684,2384], + 'a2'=>[1191,1684], + 'a3'=>[842,1191], + 'a4'=>[595,842], + 'a5'=>[420,595], + 'a6'=>[297,420], + 'a7'=>[210,297], + 'a8'=>[148,210], + 'a9'=>[105,148], + 'a10'=>[73,105], + 'b0'=>[2835,4008], + 'b1'=>[2004,2835], + 'b2'=>[1417,2004], + 'b3'=>[1001,1417], + 'b4'=>[709,1001], + 'b5'=>[499,709], + 'b6'=>[354,499], + 'c0'=>[2599,3677], + 'c1'=>[1837,2599], + 'c2'=>[1298,1837], + 'c3'=>[918,1298], + 'c4'=>[649,918], + 'c5'=>[459,649], + 'c6'=>[323,459], + 'com10'=>[297,684], +); + +my $ucmap=<<'EOF'; +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (Adobe) +/Ordering (UCS) +/Supplement 0 +>> def +/CMapName /Adobe-Identity-UCS def +/CMapType 2 def +1 begincodespacerange +<0000> <FFFF> +endcodespacerange +2 beginbfrange +<008b> <008f> [<00660066> <00660069> <0066006c> <006600660069> <00660066006C>] +<00ad> <00ad> <002d> +endbfrange +endcmap +CMapName currentdict /CMap defineresource pop +end +end +EOF + +sub usage +{ + my $stream = *STDOUT; + my $had_error = shift; + $stream = *STDERR if $had_error; + print $stream +"usage: $prog [-dels] [-F font-directory] [-I inclusion-directory]" . +" [-p paper-format] [-u [cmap-file]] [-y foundry] [file ...]\n" . +"usage: $prog {-v | --version}\n" . +"usage: $prog --help\n"; + if (!$had_error) + { + print $stream "\n" . +"Translate the output of troff(1) into Portable Document Format.\n" . +"See the gropdf(1) manual page.\n"; + } + exit($had_error); +} + +my $fd; +my $frot; +my $fpsz; +my $embedall=0; +my $debug=0; +my $want_help=0; +my $version=0; +my $stats=0; +my $unicodemap; +my @idirs; + +if (!GetOptions('F=s' => \$fd, 'I=s' => \@idirs, 'l' => \$frot, + 'p=s' => \$fpsz, 'd!' => \$debug, 'help' => \$want_help, + 'v' => \$version, 'version' => \$version, + 'e' => \$embedall, 'y=s' => \$Foundry, 's' => \$stats, + 'u:s' => \$unicodemap)) +{ + &usage(1); +} + +unshift(@idirs,'.'); + +&usage(0) if ($want_help); + +if ($version) +{ + print "GNU gropdf (groff) version $cfg{GROFF_VERSION}\n"; + exit; +} + +if (defined($unicodemap)) +{ + if ($unicodemap eq '') + { + $ucmap=''; + } + elsif (-r $unicodemap) + { + local $/; + open(F,"<$unicodemap") or Die("failed to open '$unicodemap'"); + ($ucmap)=(<F>); + close(F); + } + else + { + Warn("failed to find '$unicodemap'; ignoring"); + } +} + +# Search for 'font directory': paths in -f opt, shell var +# GROFF_FONT_PATH, default paths + +my $fontdir=$cfg{GROFF_FONT_PATH}; +$fontdir=$ENV{GROFF_FONT_PATH}.$cfg{RT_SEP}.$fontdir if exists($ENV{GROFF_FONT_PATH}); +$fontdir=$fd.$cfg{RT_SEP}.$fontdir if defined($fd); + +$rot=90 if $frot; +$matrix="0 1 -1 0" if $frot; + +LoadDownload(); +LoadDesc(); + +my $unitwidth=$desc{unitwidth}; + +$env{FontHT}=0; +$env{FontSlant}=0; +MakeMatrix(); + +my $possiblesizes = $desc{papersize}; +$possiblesizes = $fpsz if $fpsz; +my $papersz; +for $papersz ( split(" ", lc($possiblesizes).' #duff#') ) +{ + # No valid papersize found? + if ($papersz eq '#duff#') + { + Warn("ignoring unrecognized paper format(s) '$possiblesizes'"); + last; + } + + # Check for "/etc/papersize" + elsif (substr($papersz,0,1) eq '/' and -r $papersz) + { + if (open(P,"<$papersz")) + { + while (<P>) + { + chomp; + s/# .*//; + next if $_ eq ''; + $papersz=lc($_); + last; + } + close(P); + } + } + + # Allow height,width specified directly in centimeters, inches, or points. + if ($papersz=~m/([\d.]+)([cipP]),([\d.]+)([cipP])/) + { + @defaultmb=@mediabox=(0,0,ToPoints($3,$4),ToPoints($1,$2)); + last; + } + # Look $papersz up as a name such as "a4" or "letter". + elsif (exists($ppsz{$papersz})) + { + @defaultmb=@mediabox=(0,0,$ppsz{$papersz}->[0],$ppsz{$papersz}->[1]); + last; + } + # Check for a landscape version + elsif (substr($papersz,-1) eq 'l' and exists($ppsz{substr($papersz,0,-1)})) + { + # Note 'legal' ends in 'l' but will be caught above + @defaultmb=@mediabox=(0,0,$ppsz{substr($papersz,0,-1)}->[1],$ppsz{substr($papersz,0,-1)}->[0]); + last; + } + + # If we get here, $papersz was invalid, so try the next one. +} + +my (@dt)=localtime($ENV{SOURCE_DATE_EPOCH} || time); +my $dt=PDFDate(\@dt); + +my %info=('Creator' => "(groff version $cfg{GROFF_VERSION})", + 'Producer' => "(gropdf version $cfg{GROFF_VERSION})", + 'ModDate' => "($dt)", + 'CreationDate' => "($dt)"); +map { $_="< ".$_."\0" } @ARGV; + +while (<>) +{ + chomp; + s/\r$//; + $lct++; + + do # The ahead buffer behaves like 'ungetc' + {{ + if (scalar(@ahead)) + { + $_=shift(@ahead); + } + + + my $cmd=substr($_,0,1); + next if $cmd eq '#'; # just a comment + my $lin=substr($_,1); + + while ($cmd eq 'w') + { + $cmd=substr($lin,0,1); + $lin=substr($lin,1); + $w_flg=1 if $gotT; + } + + $lin=~s/^\s+//; +# $lin=~s/\s#.*?$//; # remove comment + $stream.="\% $_\n" if $debug; + + do_x($lin),next if ($cmd eq 'x'); + next if $suppress; + do_p($lin),next if ($cmd eq 'p'); + do_f($lin),next if ($cmd eq 'f'); + do_s($lin),next if ($cmd eq 's'); + do_m($lin),next if ($cmd eq 'm'); + do_D($lin),next if ($cmd eq 'D'); + do_V($lin),next if ($cmd eq 'V'); + do_v($lin),next if ($cmd eq 'v'); + do_t($lin),next if ($cmd eq 't'); + do_u($lin),next if ($cmd eq 'u'); + do_C($lin),next if ($cmd eq 'C'); + do_c($lin),next if ($cmd eq 'c'); + do_N($lin),next if ($cmd eq 'N'); + do_h($lin),next if ($cmd eq 'h'); + do_H($lin),next if ($cmd eq 'H'); + do_n($lin),next if ($cmd eq 'n'); + + my $tmp=scalar(@ahead); + }} until scalar(@ahead) == 0; + +} + +exit 0 if $lct==0; + +if ($cpageno > 0) +{ + my $trans='BLOCK'; + + $trans='PAGE' if $firstpause; + + if (scalar(@XOstream)) + { + MakeXO() if $stream; + $stream=join("\n",@XOstream)."\n"; + } + + my %t=%{$transition->{$trans}}; + $cpage->{MediaBox}=\@mediabox if $custompaper; + $cpage->{Trans}=FixTrans(\%t) if $t{S}; + + if ($#PageAnnots >= 0) + { + @{$cpage->{Annots}}=@PageAnnots; + } + + if ($#bgstack > -1 or $bgbox) + { + my $box="q 1 0 0 1 0 0 cm "; + + foreach my $bg (@bgstack) + { + # 0=$bgtype # 1=stroke 2=fill. 4=page + # 1=$strkcol + # 2=$fillcol + # 3=(Left,Top,Right,bottom,LineWeight) + # 4=Start ypos + # 5=Endypos + # 6=Line Weight + + my $pg=$bg->[3] || \@mediabox; + + $bg->[5]=$pg->[3]; # box is continuing to next page + $box.=DrawBox($bg); + $bg->[4]=$pg->[1]; # will continue from page top + } + + $stream=$box.$bgbox."Q\n".$stream; + $bgbox=''; + } + + $boxmax=0; + PutObj($cpageno); + OutStream($cpageno+1); +} + +$cat->{PageMode}='/FullScreen' if $present; + +PutOutlines(\@outlev); + +PutObj(1); + +my $info=BuildObj(++$objct,\%info); + +PutObj($objct); + +foreach my $fontno (sort keys %fontlst) +{ + my $o=$fontlst{$fontno}->{FNT}; + + foreach my $ch (@{$o->{NO}}) + { + my $psname=$o->{NAM}->{$ch->[1]}->[PSNAME] || '/.notdef'; + my $wid=$o->{NAM}->{$ch->[1]}->[WIDTH] || 0; + + push(@{$o->{DIFF}},$psname); + push(@{$o->{WIDTH}},$wid); + last if $#{$o->{DIFF}} >= 256; + } + unshift(@{$o->{DIFF}},0); + my $p=GetObj($fontlst{$fontno}->{OBJ}); + + if (exists($p->{LastChar}) and $p->{LastChar} > 255) + { + $p->{LastChar} = 255; + splice(@{$o->{DIFF}},257); + splice(@{$o->{WIDTH}},257); + } +} + +foreach my $o (3..$objct) +{ + PutObj($o) if (!exists($obj[$o]->{XREF})); +} + +#my $encrypt=BuildObj(++$objct,{'Filter' => '/Standard', 'V' => 1, 'R' => 2, 'P' => 252}); +#PutObj($objct); +PutObj(2); + +my $xrefct=$fct; + +$objct+=1; +print "xref\n0 $objct\n0000000000 65535 f \n"; + +foreach my $xr (@obj) +{ + next if !defined($xr); + printf("%010d 00000 n \n",$xr->{XREF}); +} + +print "trailer\n<<\n/Info $info\n/Root 1 0 R\n/Size $objct\n>>\nstartxref\n$fct\n\%\%EOF\n"; +print "\% Pages=$pages->{Count}\n" if $stats; + + +sub MakeMatrix +{ + my $fontxrev=shift||0; + my @mat=($frot)?(0,1,-1,0):(1,0,0,1); + + if (!$frot) + { + if ($env{FontHT} != 0) + { + $mat[3]=sprintf('%.3f',$env{FontHT}/$cftsz); + } + + if ($env{FontSlant} != 0) + { + my $slant=$env{FontSlant}; + $slant*=$env{FontHT}/$cftsz if $env{FontHT} != 0; + my $ang=rad($slant); + + $mat[2]=sprintf('%.3f',sin($ang)/cos($ang)); + } + + if ($fontxrev) + { + $mat[0]=-$mat[0]; + } + } + + $matrix=join(' ',@mat); + $matrixchg=1; +} + +sub PutOutlines +{ + my $o=shift; + my $outlines; + + if ($#{$o} > 0) + { + # We've got Outlines to deal with + my $openct=$curoutlev->[0]->[2]; + + while ($thislev-- > 1) + { + my $nxtoutlev=$curoutlev->[0]->[1]; + $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1; + $openct=0 if $nxtoutlev->[0]->[3]==-1; + $curoutlev=$nxtoutlev; + } + + $cat->{Outlines}=BuildObj(++$objct,{'Count' => abs($o->[0]->[0])+$o->[0]->[2]}); + $outlines=$obj[$objct]->{DATA}; + } + else + { + return; + } + + SetOutObj($o); + + $outlines->{First}=$o->[1]->[2]; + $outlines->{Last}=$o->[$#{$o}]->[2]; + + LinkOutObj($o,$cat->{Outlines}); +} + +sub SetOutObj +{ + my $o=shift; + + for my $j (1..$#{$o}) + { + my $ono=BuildObj(++$objct,$o->[$j]->[0]); + $o->[$j]->[2]=$ono; + + SetOutObj($o->[$j]->[1]) if $#{$o->[$j]->[1]} > -1; + } +} + +sub LinkOutObj +{ + my $o=shift; + my $parent=shift; + + for my $j (1..$#{$o}) + { + my $op=GetObj($o->[$j]->[2]); + + $op->{Next}=$o->[$j+1]->[2] if ($j < $#{$o}); + $op->{Prev}=$o->[$j-1]->[2] if ($j > 1); + $op->{Parent}=$parent; + + if ($#{$o->[$j]->[1]} > -1) + { + $op->{Count}=$o->[$j]->[1]->[0]->[2]*$o->[$j]->[1]->[0]->[3];# if exists($op->{Count}) and $op->{Count} > 0; + $op->{First}=$o->[$j]->[1]->[1]->[2]; + $op->{Last}=$o->[$j]->[1]->[$#{$o->[$j]->[1]}]->[2]; + LinkOutObj($o->[$j]->[1],$o->[$j]->[2]); + } + } +} + +sub GetObj +{ + my $ono=shift; + ($ono)=split(' ',$ono); + return($obj[$ono]->{DATA}); +} + + + +sub PDFDate +{ + my $dt=shift; + return(sprintf("D:%04d%02d%02d%02d%02d%02d%+03d'00'",$dt->[5]+1900,$dt->[4]+1,$dt->[3],$dt->[2],$dt->[1],$dt->[0],( localtime time() + 3600*( 12 - (gmtime)[2] ) )[2] - 12)); +} + +sub ToPoints +{ + my $num=shift; + my $unit=shift; + + if ($unit eq 'i') + { + return($num*72); + } + elsif ($unit eq 'c') + { + return int($num*72/2.54); + } + elsif ($unit eq 'm') # millimetres + { + return int($num*72/25.4); + } + elsif ($unit eq 'p') + { + return($num); + } + elsif ($unit eq 'P') + { + return($num*6); + } + elsif ($unit eq 'z') + { + return($num/$unitwidth); + } + else + { + Die("invalid scaling unit '$unit'"); + } +} + +sub LoadDownload +{ + my $f; + my $found=0; + + my (@dirs)=split($cfg{RT_SEP},$fontdir); + + foreach my $dir (@dirs) + { + $f=undef; + OpenFile(\$f,$dir,"download"); + next if !defined($f); + $found++; + + while (<$f>) + { + chomp; + s/#.*$//; + next if $_ eq ''; + my ($foundry,$name,$file)=split(/\t+/); + if (substr($file,0,1) eq '*') + { + next if !$embedall; + $file=substr($file,1); + } + + my $pth=$file; + $pth=$dir."/$devnm/$file" if substr($file,0,1) ne '/'; + + if (!-r $pth) + { + $missing{"$foundry $name"}="$dir/$devnm"; + next; + } + + $download{"$foundry $name"}=$file if !exists($download{"$foundry $name"}); + } + + close($f); + } + + Die("failed to open 'download' file") if !$found; +} + +sub OpenFile +{ + my $f=shift; + my $dirs=shift; + my $fnm=shift; + + if (substr($fnm,0,1) eq '/' or substr($fnm,1,1) eq ':') # dos + { + return if -r "$fnm" and open($$f,"<$fnm"); + } + + my (@dirs)=split($cfg{RT_SEP},$dirs); + + foreach my $dir (@dirs) + { + last if -r "$dir/$devnm/$fnm" and open($$f,"<$dir/$devnm/$fnm"); + } +} + +sub LoadDesc +{ + my $f; + + OpenFile(\$f,$fontdir,"DESC"); + Die("failed to open device description file 'DESC'") + if !defined($f); + + while (<$f>) + { + chomp; + s/#.*$//; + next if $_ eq ''; + my ($name,$prms)=split(' ',$_,2); + $desc{lc($name)}=$prms; + } + + close($f); + + foreach my $directive ('unitwidth', 'res', 'sizescale') + { + Die("device description file 'DESC' missing mandatory directive" + . " '$directive'") if !exists($desc{$directive}); + } + + foreach my $directive ('unitwidth', 'res', 'sizescale') + { + my $val=$desc{$directive}; + Die("device description file 'DESC' directive '$directive'" + . " value must be positive; got '$val'") + if ($val !~ m/^\d+$/ or $val <= 0); + } + + if (exists($desc{'hor'})) + { + my $hor=$desc{'hor'}; + Die("device horizontal motion quantum must be 1, got '$hor'") + if ($hor != 1); + } + + if (exists($desc{'vert'})) + { + my $vert=$desc{'vert'}; + Die("device vertical motion quantum must be 1, got '$vert'") + if ($vert != 1); + } + + my ($res,$ss)=($desc{'res'},$desc{'sizescale'}); + Die("device resolution must be a multiple of 72*sizescale, got" + . " '$res' ('sizescale'=$ss)") if (($res % ($ss * 72)) != 0); +} + +sub rad { $_[0]*3.14159/180 } + +my $InPicRotate=0; + +sub do_x +{ + my $l=shift; + my ($xcmd,@xprm)=split(' ',$l); + $xcmd=substr($xcmd,0,1); + + if ($xcmd eq 'T') + { + Warn("expecting a PDF pipe (got $xprm[0])") + if $xprm[0] ne substr($devnm,3); + } + elsif ($xcmd eq 'f') # Register Font + { + $xprm[1]="${Foundry}-$xprm[1]" if $Foundry ne ''; + LoadFont($xprm[0],$xprm[1]); + } + elsif ($xcmd eq 'F') # Source File (for errors) + { + $env{SourceFile}=$xprm[0]; + } + elsif ($xcmd eq 'H') # FontHT + { + $xprm[0]/=$unitwidth; + $xprm[0]=0 if $xprm[0] == $cftsz; + $env{FontHT}=$xprm[0]; + MakeMatrix(); + } + elsif ($xcmd eq 'S') # FontSlant + { + $env{FontSlant}=$xprm[0]; + MakeMatrix(); + } + elsif ($xcmd eq 'i') # Initialise + { + if ($objct == 0) + { + $objct++; + @defaultmb=@mediabox; + BuildObj($objct,{'Pages' => BuildObj($objct+1, + {'Kids' => [], + 'Count' => 0, + 'Type' => '/Pages', + 'Rotate' => $rot, + 'MediaBox' => \@defaultmb, + 'Resources' => + {'Font' => {}, + 'ProcSet' => ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI']} + } + ), + 'Type' => '/Catalog'}); + + $cat=$obj[$objct]->{DATA}; + $objct++; + $pages=$obj[2]->{DATA}; + Put("%PDF-1.4\n\x25\xe2\xe3\xcf\xd3\n"); + } + } + elsif ($xcmd eq 'X') + { + # There could be extended args + do + {{ + LoadAhead(1); + if (substr($ahead[0],0,1) eq '+') + { + $l.="\n".substr($ahead[0],1); + shift(@ahead); + } + }} until $#ahead==0; + + ($xcmd,@xprm)=split(' ',$l); + $xcmd=substr($xcmd,0,1); + + if ($xprm[0]=~m/^(.+:)(.+)/) + { + splice(@xprm,1,0,$2); + $xprm[0]=$1; + } + + my $par=join(' ',@xprm[1..$#xprm]); + + if ($xprm[0] eq 'ps:') + { + if ($xprm[1] eq 'invis') + { + $suppress=1; + } + elsif ($xprm[1] eq 'endinvis') + { + $suppress=0; + } + elsif ($par=~m/exec gsave currentpoint 2 copy translate (.+) rotate neg exch neg exch translate/) + { + # This is added by gpic to rotate a single object + + my $theta=-rad($1); + + IsGraphic(); + my ($curangle,$hyp)=RtoP($xpos,GraphY($ypos)); + my ($x,$y)=PtoR($theta+$curangle,$hyp); + my ($tx, $ty) = ($xpos - $x, GraphY($ypos) - $y); + if ($frot) { + ($tx, $ty) = ($tx * sin($theta) + $ty * -cos($theta), + $tx * -cos($theta) + $ty * -sin($theta)); + } + $stream.="q\n".sprintf("%.3f %.3f %.3f %.3f %.3f %.3f cm",cos($theta),sin($theta),-sin($theta),cos($theta),$tx,$ty)."\n"; + $InPicRotate=1; + } + elsif ($par=~m/exec grestore/ and $InPicRotate) + { + IsGraphic(); + $stream.="Q\n"; + $InPicRotate=0; + } + elsif ($par=~m/exec (\d) setlinejoin/) + { + IsGraphic(); + $linejoin=$1; + $stream.="$linejoin j\n"; + } + elsif ($par=~m/exec (\d) setlinecap/) + { + IsGraphic(); + $linecap=$1; + $stream.="$linecap J\n"; + } + elsif ($par=~m/exec %%%%PAUSE/i and !$noslide) + { + my $trans='BLOCK'; + + if ($firstpause) + { + $trans='PAGE'; + $firstpause=0; + } + MakeXO(); + NewPage($trans); + $present=1; + } + elsif ($par=~m/exec %%%%BEGINONCE/) + { + if ($noslide) + { + $suppress=1; + } + else + { + my $trans='BLOCK'; + + if ($firstpause) + { + $trans='PAGE'; + $firstpause=0; + } + MakeXO(); + NewPage($trans); + $present=1; + } + } + elsif ($par=~m/exec %%%%ENDONCE/) + { + if ($noslide) + { + $suppress=0; + } + else + { + MakeXO(); + NewPage('BLOCK'); + $cat->{PageMode}='/FullScreen'; + pop(@XOstream); + } + } + elsif ($par=~m/\[(.+) pdfmark/) + { + my $pdfmark=$1; + $pdfmark=~s((\d{4,6}) u)(sprintf("%.1f",$1/$desc{sizescale}))eg; + $pdfmark=~s(\\\[u00(..)\])(chr(hex($1)))eg; + $pdfmark=~s/\\n/\n/g; + + if ($pdfmark=~m/(.+) \/DOCINFO\s*$/s) + { + my @xwds=split(/ /,"<< $1 >>"); + my $docinfo=ParsePDFValue(\@xwds); + + foreach my $k (sort keys %{$docinfo}) + { + $info{$k}=$docinfo->{$k} if $k ne 'Producer'; + } + } + elsif ($pdfmark=~m/(.+) \/DOCVIEW\s*$/) + { + my @xwds=split(' ',"<< $1 >>"); + my $docview=ParsePDFValue(\@xwds); + + foreach my $k (sort keys %{$docview}) + { + $cat->{$k}=$docview->{$k} if !exists($cat->{$k}); + } + } + elsif ($pdfmark=~m/(.+) \/DEST\s*$/) + { + my @xwds=split(' ',"<< $1 >>"); + my $dest=ParsePDFValue(\@xwds); + $dest->{View}->[1]=GraphY($dest->{View}->[1]*-1); + unshift(@{$dest->{View}},"$cpageno 0 R"); + + if (!defined($dests)) + { + $cat->{Dests}=BuildObj(++$objct,{}); + $dests=$obj[$objct]->{DATA}; + } + + my $k=substr($dest->{Dest},1); + $dests->{$k}=$dest->{View}; + } + elsif ($pdfmark=~m/(.+) \/ANN\s*$/) + { + my $l=$1; + $l=~s/Color/C/; + $l=~s/Action/A/; + $l=~s/Title/T/; + $l=~s'/Subtype /URI'/S /URI'; + my @xwds=split(' ',"<< $l >>"); + my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds)); + my $annot=$obj[$objct]; + $annot->{DATA}->{Type}='/Annot'; + FixRect($annot->{DATA}->{Rect}); # Y origin to ll + FixPDFColour($annot->{DATA}); + push(@PageAnnots,$annotno); + } + elsif ($pdfmark=~m/(.+) \/OUT\s*$/) + { + my $t=$1; + $t=~s/\\\) /\\\\\) /g; + $t=~s/\\e/\\\\/g; + $t=~m/(^.*\/Title \()(.*)(\).*)/; + my ($pre,$title,$post)=($1,$2,$3); + $title=~s/(?<!\\)\(/\\\(/g; + $title=~s/(?<!\\)\)/\\\)/g; + my @xwds=split(' ',"<< $pre$title$post >>"); + my $out=ParsePDFValue(\@xwds); + + my $this=[$out,[]]; + + if (exists($out->{Level})) + { + my $lev=abs($out->{Level}); + my $levsgn=sgn($out->{Level}); + delete($out->{Level}); + + if ($lev > $thislev) + { + my $thisoutlev=$curoutlev->[$#{$curoutlev}]->[1]; + $thisoutlev->[0]=[0,$curoutlev,0,$levsgn]; + $curoutlev=$thisoutlev; + $curoutlevno=$#{$curoutlev}; + $thislev++; + } + elsif ($lev < $thislev) + { + my $openct=$curoutlev->[0]->[2]; + + while ($thislev > $lev) + { + my $nxtoutlev=$curoutlev->[0]->[1]; + $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1; + $openct=0 if $nxtoutlev->[0]->[3]==-1; + $curoutlev=$nxtoutlev; + $thislev--; + } + + $curoutlevno=$#{$curoutlev}; + } + +# push(@{$curoutlev},$this); + splice(@{$curoutlev},++$curoutlevno,0,$this); + $curoutlev->[0]->[2]++; + } + else + { + # This code supports old pdfmark.tmac, unused by pdf.tmac + while ($curoutlev->[0]->[0] == 0 and defined($curoutlev->[0]->[1])) + { + $curoutlev=$curoutlev->[0]->[1]; + } + + $curoutlev->[0]->[0]--; + $curoutlev->[0]->[2]++; + push(@{$curoutlev},$this); + + + if (exists($out->{Count}) and $out->{Count} != 0) + { + push(@{$this->[1]},[abs($out->{Count}),$curoutlev,0,sgn($out->{Count})]); + $curoutlev=$this->[1]; + + if ($out->{Count} > 0) + { + my $p=$curoutlev; + + while (defined($p)) + { + $p->[0]->[2]+=$out->{Count}; + $p=$p->[0]->[1]; + } + } + } + } + } + } + } + elsif (lc($xprm[0]) eq 'pdf:') + { + if (lc($xprm[1]) eq 'import') + { + my $fil=$xprm[2]; + my $llx=$xprm[3]; + my $lly=$xprm[4]; + my $urx=$xprm[5]; + my $ury=$xprm[6]; + my $wid=GetPoints($xprm[7]); + my $hgt=GetPoints($xprm[8])||-1; + my $mat=[1,0,0,1,0,0]; + + if (!exists($incfil{$fil})) + { + if ($fil=~m/\.pdf$/) + { + $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"import"); + } + elsif ($fil=~m/\.swf$/) + { + my $xscale=$wid/($urx-$llx+1); + my $yscale=($hgt<=0)?$xscale:($hgt/($ury-$lly+1)); + $hgt=($ury-$lly+1)*$yscale; + + if ($rot) + { + $mat->[3]=$xscale; + $mat->[0]=$yscale; + } + else + { + $mat->[0]=$xscale; + $mat->[3]=$yscale; + } + + $incfil{$fil}=LoadSWF($fil,[$llx,$lly,$urx,$ury],$mat); + } + else + { + Warn("unrecognized 'import' file type '$fil'"); + return undef; + } + } + + if (defined($incfil{$fil})) + { + IsGraphic(); + if ($fil=~m/\.pdf$/) + { + my $bbox=$incfil{$fil}->[1]; + my $xscale=d3($wid/($bbox->[2]-$bbox->[0]+1)); + my $yscale=d3(($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1]+1))); + $wid=($bbox->[2]-$bbox->[0])*$xscale; + $hgt=($bbox->[3]-$bbox->[1])*$yscale; + $ypos+=$hgt; + $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm"; + $stream.=" 0 1 -1 0 0 0 cm" if $rot; + $stream.=" /$incfil{$fil}->[0] Do Q\n"; + } + elsif ($fil=~m/\.swf$/) + { + $stream.=PutXY($xpos,$ypos)." m /$incfil{$fil} Do\n"; + } + } + } + elsif (lc($xprm[1]) eq 'pdfpic') + { + my $fil=$xprm[2]; + my $flag=uc($xprm[3]||'-L'); + my $wid=GetPoints($xprm[4])||-1; + my $hgt=GetPoints($xprm[5]||-1); + my $ll=GetPoints($xprm[6]||0); + my $mat=[1,0,0,1,0,0]; + + if (!exists($incfil{$fil})) + { + $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"pdfpic"); + } + + if (defined($incfil{$fil})) + { + IsGraphic(); + my $bbox=$incfil{$fil}->[1]; + $wid=($bbox->[2]-$bbox->[0]) if $wid <= 0; + my $xscale=d3($wid/($bbox->[2]-$bbox->[0])); + my $yscale=d3(($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1]))); + $xscale=($wid<=0)?$yscale:$xscale; + $xscale=$yscale if $yscale < $xscale; + $yscale=$xscale if $xscale < $yscale; + $wid=($bbox->[2]-$bbox->[0])*$xscale; + $hgt=($bbox->[3]-$bbox->[1])*$yscale; + + if ($flag eq '-C' and $ll > $wid) + { + $xpos+=int(($ll-$wid)/2); + } + elsif ($flag eq '-R' and $ll > $wid) + { + $xpos+=$ll-$wid; + } + + $ypos+=$hgt; + $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm"; + $stream.=" 0 1 -1 0 0 0 cm" if $rot; + $stream.=" /$incfil{$fil}->[0] Do Q\n"; + } + } + elsif (lc($xprm[1]) eq 'xrev') + { + $xrev=!$xrev; + } + elsif (lc($xprm[1]) eq 'markstart') + { + $mark={'rst' => ($xprm[2]+$xprm[4])/$unitwidth, 'rsb' => ($xprm[3]-$xprm[4])/$unitwidth, 'xpos' => $xpos-($xprm[4]/$unitwidth), + 'ypos' => $ypos, 'lead' => $xprm[4]/$unitwidth, 'pdfmark' => join(' ',@xprm[5..$#xprm])}; + } + elsif (lc($xprm[1]) eq 'markend') + { + PutHotSpot($xpos) if defined($mark); + $mark=undef; + } + elsif (lc($xprm[1]) eq 'marksuspend') + { + $suspendmark=$mark; + $mark=undef; + } + elsif (lc($xprm[1]) eq 'markrestart') + { + $mark=$suspendmark; + $suspendmark=undef; + } + elsif (lc($xprm[1]) eq 'pagename') + { + if ($pginsert > -1) + { + $pgnames{$xprm[2]}=$pages->{Kids}->[$pginsert]; + } + else + { + $pgnames{$xprm[2]}='top'; + } + } + elsif (lc($xprm[1]) eq 'switchtopage') + { + my $ba=$xprm[2]; + my $want=$xprm[3]; + + if ($pginsert > -1) + { + if (!defined($want) or $want eq '') + { + # no before/after + $want=$ba; + $ba='before'; + } + + if (!defined($ba) or $ba eq '' or $want eq 'bottom') + { + $pginsert=$#{$pages->{Kids}}; + } + elsif ($want eq 'top') + { + $pginsert=-1; + } + else + { + if (exists($pgnames{$want})) + { + my $ref=$pgnames{$want}; + + if ($ref eq 'top') + { + $pginsert=-1; + } + else + { + FIND: while (1) + { + foreach my $j (0..$#{$pages->{Kids}}) + { + if ($ref eq $pages->{Kids}->[$j]) + { + if ($ba eq 'before') + { + $pginsert=$j-1; + last FIND; + } + elsif ($ba eq 'after') + { + $pginsert=$j; + last FIND; + } + else + { + # XXX: indentation wince + Warn( +"expected 'switchtopage' parameter to be one of" +. "'top|bottom|before|after', got '$ba'"); + last FIND; + } + } + + } + + Warn("cannot find page ref '$ref'"); + last FIND + + } + } + } + else + { + Warn("cannot find page named '$want'"); + } + } + + if ($pginsert < 0) + { + ($curoutlev,$curoutlevno,$thislev)=(\@outlev,0,1); + } + else + { + ($curoutlev,$curoutlevno,$thislev)=(@{$outlines[$pginsert]}); + } + } + } + elsif (lc($xprm[1]) eq 'transition' and !$noslide) + { + if (uc($xprm[2]) eq 'PAGE' or uc($xprm[2] eq 'SLIDE')) + { + $transition->{PAGE}->{S}='/'.ucfirst($xprm[3]) if $xprm[3] and $xprm[3] ne '.'; + $transition->{PAGE}->{D}=$xprm[4] if $xprm[4] and $xprm[4] ne '.'; + $transition->{PAGE}->{Dm}='/'.$xprm[5] if $xprm[5] and $xprm[5] ne '.'; + $transition->{PAGE}->{M}='/'.$xprm[6] if $xprm[6] and $xprm[6] ne '.'; + $xprm[7]='/None' if $xprm[7] and uc($xprm[7]) eq 'NONE'; + $transition->{PAGE}->{Di}=$xprm[7] if $xprm[7] and $xprm[7] ne '.'; + $transition->{PAGE}->{SS}=$xprm[8] if $xprm[8] and $xprm[8] ne '.'; + $transition->{PAGE}->{B}=$xprm[9] if $xprm[9] and $xprm[9] ne '.'; + } + elsif (uc($xprm[2]) eq 'BLOCK') + { + $transition->{BLOCK}->{S}='/'.ucfirst($xprm[3]) if $xprm[3] and $xprm[3] ne '.'; + $transition->{BLOCK}->{D}=$xprm[4] if $xprm[4] and $xprm[4] ne '.'; + $transition->{BLOCK}->{Dm}='/'.$xprm[5] if $xprm[5] and $xprm[5] ne '.'; + $transition->{BLOCK}->{M}='/'.$xprm[6] if $xprm[6] and $xprm[6] ne '.'; + $xprm[7]='/None' if $xprm[7] and uc($xprm[7]) eq 'NONE'; + $transition->{BLOCK}->{Di}=$xprm[7] if $xprm[7] and $xprm[7] ne '.'; + $transition->{BLOCK}->{SS}=$xprm[8] if $xprm[8] and $xprm[8] ne '.'; + $transition->{BLOCK}->{B}=$xprm[9] if $xprm[9] and $xprm[9] ne '.'; + } + + $present=1; + } + elsif (lc($xprm[1]) eq 'background') + { + splice(@xprm,0,2); + my $type=shift(@xprm); +# print STDERR "ypos=$ypos\n"; + + if (lc($type) eq 'off') + { + my $sptr=$#bgstack; + if ($sptr > -1) + { + if ($sptr == 0 and $bgstack[0]->[0] & 4) + { + pop(@bgstack); + } + else + { + $bgstack[$sptr]->[5]=GraphY($ypos); + $bgbox=DrawBox(pop(@bgstack)).$bgbox; + } + } + } + elsif (lc($type) eq 'footnote') + { + my $t=GetPoints($xprm[0]); + $boxmax=($t<0)?abs($t):GraphY($t); + } + else + { + my $bgtype=0; + + foreach (@xprm) + { + $_=GetPoints($_); + } + + $bgtype|=2 if $type=~m/box/i; + $bgtype|=1 if $type=~m/fill/i; + $bgtype|=4 if $type=~m/page/i; + $bgtype=5 if $bgtype==4; + my $bgwt=$xprm[4]; + $bgwt=$xprm[0] if !defined($bgwt) and $#xprm == 0; + my (@bg)=(@xprm); + my $bg=\@bg; + + if (!defined($bg[3]) or $bgtype & 4) + { + $bg=undef; + } + else + { + FixRect($bg); + } + + if ($bgtype) + { + if ($bgtype & 4) + { + shift(@bgstack) if $#bgstack >= 0 and $bgstack[0]->[0] & 4; + unshift(@bgstack,[$bgtype,$strkcol,$fillcol,$bg,GraphY($ypos),GraphY($bg[3]||0),$bgwt || 0.4]); + } + else + { + push(@bgstack,[$bgtype,$strkcol,$fillcol,$bg,GraphY($ypos),GraphY($bg[3]||0),$bgwt || 0.4]); + } + } + } + } + } + elsif (lc(substr($xprm[0],0,9)) eq 'papersize') + { + my ($px,$py)=split(',',substr($xprm[0],10)); + $px=GetPoints($px); + $py=GetPoints($py); + @mediabox=(0,0,$px,$py); + my @mb=@mediabox; + $matrixchg=1; + $custompaper=1; + $cpage->{MediaBox}=\@mb; + } + } +} + +sub FixPDFColour +{ + my $o=shift; + my $a=$o->{C}; + my @r=(); + my $c=$a->[0]; + + if ($#{$a}==3) + { + if ($c > 1) + { + foreach my $j (0..2) + { + push(@r,sprintf("%1.3f",$a->[$j]/0xffff)); + } + + $o->{C}=\@r; + } + } + elsif (substr($c,0,1) eq '#') + { + if (length($c) == 7) + { + foreach my $j (0..2) + { + push(@r,sprintf("%1.3f",hex(substr($c,$j*2+1,2))/0xff)); + } + + $o->{C}=\@r; + } + elsif (length($c) == 14) + { + foreach my $j (0..2) + { + push(@r,sprintf("%1.3f",hex(substr($c,$j*4+2,4))/0xffff)); + } + + $o->{C}=\@r; + } + } +} + +sub PutHotSpot +{ + my $endx=shift; + my $l=$mark->{pdfmark}; + $l=~s/Color/C/; + $l=~s/Action/A/; + $l=~s'/Subtype /URI'/S /URI'; + $l=~s(\\\[u00(..)\])(chr(hex($1)))eg; + my @xwds=split(' ',"<< $l >>"); + my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds)); + my $annot=$obj[$objct]; + $annot->{DATA}->{Type}='/Annot'; + $annot->{DATA}->{Rect}=[$mark->{xpos},$mark->{ypos}-$mark->{rsb},$endx+$mark->{lead},$mark->{ypos}-$mark->{rst}]; + FixPDFColour($annot->{DATA}); + FixRect($annot->{DATA}->{Rect}); # Y origin to ll + push(@PageAnnots,$annotno); +} + +sub sgn +{ + return(1) if $_[0] > 0; + return(-1) if $_[0] < 0; + return(0); +} + +sub FixRect +{ + my $rect=shift; + + return if !defined($rect); + $rect->[1]=GraphY($rect->[1]); + $rect->[3]=GraphY($rect->[3]); + + if ($rot) + { + ($rect->[0],$rect->[1])=Rotate($rect->[0],$rect->[1]); + ($rect->[2],$rect->[3])=Rotate($rect->[2],$rect->[3]); + } +} + +sub Rotate +{ + my ($tx,$ty)=(@_); + my $theta=rad($rot); + + ($tx,$ty)=(d3($tx * cos(-$theta) - $ty * sin(-$theta)), + d3($tx * sin( $theta) + $ty * cos( $theta))); + return($tx,$ty); +} + +sub GetPoints +{ + my $val=shift; + + $val=ToPoints($1,$2) if ($val and $val=~m/(-?[\d.]+)([cipnz])/); + + return $val; +} + +# Although the PDF reference mentions XObject/Form as a way of +# incorporating an external PDF page into the current PDF, it seems not +# to work with any current PDF reader (although I am told (by Leonard +# Rosenthol, who helped author the PDF ISO standard) that Acroread 9 +# does support it, empirical observation shows otherwise!!). So... do +# it the hard way - full PDF parser and merge required objects!!! + +# sub BuildRef +# { +# my $fil=shift; +# my $bbox=shift; +# my $mat=shift; +# my $wid=($bbox->[2]-$bbox->[0])*$mat->[0]; +# my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3]; +# +# if (!open(PDF,"<$fil")) +# { +# Warn("failed to open '$fil'"); +# return(undef); +# } +# +# my (@f)=(<PDF>); +# +# close(PDF); +# +# $objct++; +# my $xonm="XO$objct"; +# +# $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject', +# 'Subtype' => '/Form', +# 'BBox' => $bbox, +# 'Matrix' => $mat, +# 'Resources' => $pages->{'Resources'}, +# 'Ref' => {'Page' => '1', +# 'F' => BuildObj($objct+1,{'Type' => '/Filespec', +# 'F' => "($fil)", +# 'EF' => {'F' => BuildObj($objct+2,{'Type' => '/EmbeddedFile'})} +# }) +# } +# }); +# +# $obj[$objct]->{STREAM}="q 1 0 0 1 0 0 cm +# q BT +# 1 0 0 1 0 0 Tm +# .5 g .5 G +# /F5 20 Tf +# (Proxy) Tj +# ET Q +# 0 0 m 72 0 l s +# Q\n"; +# +# # $obj[$objct]->{STREAM}=PutXY($xpos,$ypos)." m ".PutXY($xpos+$wid,$ypos)." l ".PutXY($xpos+$wid,$ypos+$hgt)." l ".PutXY($xpos,$ypos+$hgt)." l f\n"; +# $obj[$objct+2]->{STREAM}=join('',@f); +# PutObj($objct); +# PutObj($objct+1); +# PutObj($objct+2); +# $objct+=2; +# return($xonm); +# } + +sub LoadSWF +{ + my $fil=shift; + my $bbox=shift; + my $mat=shift; + my $wid=($bbox->[2]-$bbox->[0])*$mat->[0]; + my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3]; + my (@path)=split('/',$fil); + my $node=pop(@path); + + if (!open(PDF,"<$fil")) + { + Warn("failed to open SWF '$fil'"); + return(undef); + } + + my (@f)=(<PDF>); + + close(PDF); + + $objct++; + my $xonm="XO$objct"; + + $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject', 'BBox' => $bbox, 'Matrix' => $mat, 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject"}); + $obj[$objct]->{STREAM}=''; + PutObj($objct); + $objct++; + my $asset=BuildObj($objct,{'EF' => {'F' => BuildObj($objct+1,{})}, + 'F' => "($node)", + 'Type' => '/Filespec', + 'UF' => "($node)"}); + + PutObj($objct); + $objct++; + $obj[$objct]->{STREAM}=join('',@f); + PutObj($objct); + $objct++; + my $config=BuildObj($objct,{'Instances' => [BuildObj($objct+1,{'Params' => { 'Binding' => '/Background'}, 'Asset' => $asset})], + 'Subtype' => '/Flash'}); + + PutObj($objct); + $objct++; + PutObj($objct); + $objct++; + + my ($x,$y)=split(' ',PutXY($xpos,$ypos)); + + push(@{$cpage->{Annots}},BuildObj($objct,{'RichMediaContent' => {'Subtype' => '/Flash', 'Configurations' => [$config], 'Assets' => {'Names' => [ "($node)", $asset ] }}, + 'P' => "$cpageno 0 R", + 'RichMediaSettings' => { 'Deactivation' => { 'Condition' => '/PI', + 'Type' => '/RichMediaDeactivation'}, + 'Activation' => { 'Condition' => '/PV', + 'Type' => '/RichMediaActivation'}}, + 'F' => 68, + 'Subtype' => '/RichMedia', + 'Type' => '/Annot', + 'Rect' => "[ $x $y ".($x+$wid)." ".($y+$hgt)." ]", + 'Border' => [0,0,0]})); + + PutObj($objct); + + return $xonm; +} + +sub OpenInc +{ + my $fn=shift; + my $fnm=$fn; + my $F; + + if (substr($fnm,0,1) eq '/' or substr($fnm,1,1) eq ':') # dos + { + if (-r $fnm and open($F,"<$fnm")) + { + return($F,$fnm); + } + } + else + { + foreach my $dir (@idirs) + { + $fnm="$dir/$fn"; + + if (-r "$fnm" and open($F,"<$fnm")) + { + return($F,$fnm); + } + } + } + + return(undef,$fn); +} + +sub LoadPDF +{ + my $pdfnm=shift; + my $mat=shift; + my $wid=shift; + my $hgt=shift; + my $type=shift; + my $pdf; + my $pdftxt=''; + my $strmlen=0; + my $curobj=-1; + my $instream=0; + my $cont; + my $adj=0; + my $keepsep=$/; + + my ($PD,$PDnm)=OpenInc($pdfnm); + + if (!defined($PD)) + { + Warn("failed to open PDF '$pdfnm'"); + return undef; + } + + my $hdr=<$PD>; + + $/="\r",$adj=1 if (length($hdr) > 10); + + while (<$PD>) + { + chomp; + + s/\n//; + + if (m/endstream(\s+.*)?$/) + { + $instream=0; + $_="endstream"; + $_.=$1 if defined($1) + } + + next if $instream; + + if (m'/Length\s+(\d+)(\s+\d+\s+R)?') + { + if (!defined($2)) + { + $strmlen=$1; + } + else + { + $strmlen=0; + } + } + + if (m'^(\d+) \d+ obj') + { + $curobj=$1; + $pdf->[$curobj]->{OBJ}=undef; + } + + if (m'stream\s*$' and ! m/^endstream/) + { + if ($curobj > -1) + { + $pdf->[$curobj]->{STREAMPOS}=[tell($PD)+$adj,$strmlen]; + seek($PD,$strmlen,1); + $instream=1; + } + else + { + Warn("parsing PDF '$pdfnm' failed"); + return undef; + } + } + + s/%.*?$//; + $pdftxt.=$_.' '; + } + + close($PD); + + open(PD,"<$PDnm"); +# $pdftxt=~s/\]/ \]/g; + my (@pdfwds)=split(' ',$pdftxt); + my $wd; + my $root; + + while ($wd=nextwd(\@pdfwds),length($wd)) + { + if ($wd=~m/\d+/ and defined($pdfwds[1]) and $pdfwds[1]=~m/^obj(.*)/) + { + $curobj=$wd; + shift(@pdfwds); shift(@pdfwds); + unshift(@pdfwds,$1) if defined($1) and length($1); + $pdf->[$curobj]->{OBJ}=ParsePDFObj(\@pdfwds); + my $o=$pdf->[$curobj]; + + if (ref($o->{OBJ}) eq 'HASH' and exists($o->{OBJ}->{Type}) and $o->{OBJ}->{Type} eq '/ObjStm') + { + LoadStream($o,$pdf); + my $pos=$o->{OBJ}->{First}; + my $s=$o->{STREAM}; + my @o=split(' ',substr($s,0,$pos)); + substr($s,0,$pos)=''; + push(@o,-1,length($s)); + + for (my $j=0; $j<=$#o-2; $j+=2) + { + my @w=split(' ',substr($s,$o[$j+1],$o[$j+3]-$o[$j+1])); + $pdf->[$o[$j]]->{OBJ}=ParsePDFObj(\@w); + } + + $pdf->[$curobj]=undef; + } + + $root=$curobj if ref($pdf->[$curobj]->{OBJ}) eq 'HASH' and exists($pdf->[$curobj]->{OBJ}->{Type}) and $pdf->[$curobj]->{OBJ}->{Type} eq '/XRef'; + } + elsif ($wd eq 'trailer' and !exists($pdf->[0]->{OBJ})) + { + $pdf->[0]->{OBJ}=ParsePDFObj(\@pdfwds); + } + else + { +# print "Skip '$wd'\n"; + } + } + + $pdf->[0]=$pdf->[$root] if !defined($pdf->[0]); + my $catalog=${$pdf->[0]->{OBJ}->{Root}}; + my $page=FindPage(1,$pdf); + my $xobj=++$objct; + + # Load the streamas + + foreach my $o (@{$pdf}) + { + if (exists($o->{STREAMPOS}) and !exists($o->{STREAM})) + { + LoadStream($o,$pdf); + } + } + + close(PD); + + # Find BBox + my $BBox; + my $insmap={}; + + foreach my $k (qw( ArtBox TrimBox BleedBox CropBox MediaBox )) + { + $BBox=FindKey($pdf,$page,$k); + last if $BBox; + } + + $BBox=[0,0,595,842] if !defined($BBox); + + $wid=($BBox->[2]-$BBox->[0]+1) if $wid==0; + my $xscale=d3(abs($wid)/($BBox->[2]-$BBox->[0]+1)); + my $yscale=d3(($hgt<=0)?$xscale:(abs($hgt)/($BBox->[3]-$BBox->[1]+1))); + $hgt=($BBox->[3]-$BBox->[1]+1)*$yscale; + + if ($type eq "import") + { + $mat->[0]=$xscale; + $mat->[3]=$yscale; + } + + # Find Resource + + my $res=FindKey($pdf,$page,'Resources'); + my $xonm="XO$xobj"; + + # Map inserted objects to current PDF + + MapInsValue($pdf,$page,'',$insmap,$xobj,$pdf->[$page]->{OBJ}); +# +# Many PDFs include 'Resources' at the 'Page' level but if 'Resources' is held at a higher level (i.e 'Pages') +# then we need to include its objects as well. +# + MapInsValue($pdf,$page,'',$insmap,$xobj,$res) if !exists($pdf->[$page]->{OBJ}->{Resources}); + + # Copy Resources + + my %incres=%{$res}; + + $incres{ProcSet}=['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI']; + + ($mat->[4],$mat->[5])=split(' ',PutXY($xpos,$ypos)); + $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => '/XObject', 'BBox' => $BBox, 'Name' => "/$xonm", 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject", 'Resources' => \%incres}); + + if ($BBox->[0] != 0 or $BBox->[1] != 0) + { + my (@matrix)=(1,0,0,1,-$BBox->[0],-$BBox->[1]); + $obj[$xobj]->{DATA}->{Matrix}=\@matrix; + } + + BuildStream($xobj,$pdf,$pdf->[$page]->{OBJ}->{Contents}); + + $/=$keepsep; + return([$xonm,$BBox] ); +} + +sub LoadStream +{ + my $o=shift; + my $pdf=shift; + my $l; + + $l=$o->{OBJ}->{Length} if exists($o->{OBJ}->{Length}); + + $l=$pdf->[$$l]->{OBJ} if (defined($l) && ref($l) eq 'OBJREF'); + + Die("unable to determine length of stream \@$o->{STREAMPOS}->[0]") + if !defined($l); + + sysseek(PD,$o->{STREAMPOS}->[0],0); + Warn("failed to read all of the stream") + if $l != sysread(PD,$o->{STREAM},$l); + + if ($gotzlib and exists($o->{OBJ}->{'Filter'}) and $o->{OBJ}->{'Filter'} eq '/FlateDecode') + { + $o->{STREAM}=Compress::Zlib::uncompress($o->{STREAM}); + delete($o->{OBJ }->{'Filter'}); + } +} + +sub BuildStream +{ + my $xobj=shift; + my $pdf=shift; + my $val=shift; + my $strm=''; + my $objs; + my $refval=ref($val); + + if ($refval eq 'OBJREF') + { + push(@{$objs}, $val); + } + elsif ($refval eq 'ARRAY') + { + $objs=$val; + } + else + { + Warn("unexpected 'Contents'"); + } + + foreach my $o (@{$objs}) + { + $strm.="\n" if $strm; + $strm.=$pdf->[$$o]->{STREAM} if exists($pdf->[$$o]->{STREAM}); + } + + $obj[$xobj]->{STREAM}=$strm; +} + + +sub MapInsHash +{ + my $pdf=shift; + my $o=shift; + my $insmap=shift; + my $parent=shift; + my $val=shift; + + + foreach my $k (sort keys(%{$val})) + { + MapInsValue($pdf,$o,$k,$insmap,$parent,$val->{$k}) if $k ne 'Contents'; + } +} + +sub MapInsValue +{ + my $pdf=shift; + my $o=shift; + my $k=shift; + my $insmap=shift; + my $parent=shift; + my $val=shift; + my $refval=ref($val); + + if ($refval eq 'OBJREF') + { + if ($k ne 'Parent') + { + if (!exists($insmap->{IMP}->{$$val})) + { + $objct++; + $insmap->{CUR}->{$objct}=$$val; + $insmap->{IMP}->{$$val}=$objct; + $obj[$objct]->{DATA}=$pdf->[$$val]->{OBJ}; + $obj[$objct]->{STREAM}=$pdf->[$$val]->{STREAM} if exists($pdf->[$$val]->{STREAM}); + MapInsValue($pdf,$$val,'',$insmap,$o,$pdf->[$$val]->{OBJ}); + } + + $$val=$insmap->{IMP}->{$$val}; + } + else + { + $$val=$parent; + } + } + elsif ($refval eq 'ARRAY') + { + foreach my $v (@{$val}) + { + MapInsValue($pdf,$o,'',$insmap,$parent,$v) + } + } + elsif ($refval eq 'HASH') + { + MapInsHash($pdf,$o,$insmap,$parent,$val); + } + +} + +sub FindKey +{ + my $pdf=shift; + my $page=shift; + my $k=shift; + + if (exists($pdf->[$page]->{OBJ}->{$k})) + { + my $val=$pdf->[$page]->{OBJ}->{$k}; + $val=$pdf->[$$val]->{OBJ} if ref($val) eq 'OBJREF'; + return($val); + } + else + { + if (exists($pdf->[$page]->{OBJ}->{Parent})) + { + return(FindKey($pdf,${$pdf->[$page]->{OBJ}->{Parent}},$k)); + } + } + + return(undef); +} + +sub FindPage +{ + my $wantpg=shift; + my $pdf=shift; + my $catalog=${$pdf->[0]->{OBJ}->{Root}}; + my $pages=${$pdf->[$catalog]->{OBJ}->{Pages}}; + + return(NextPage($pdf,$pages,\$wantpg)); +} + +sub NextPage +{ + my $pdf=shift; + my $pages=shift; + my $wantpg=shift; + my $ret; + + if ($pdf->[$pages]->{OBJ}->{Type} eq '/Pages') + { + foreach my $kid (@{$pdf->[$pages]->{OBJ}->{Kids}}) + { + $ret=NextPage($pdf,$$kid,$wantpg); + last if $$wantpg<=0; + } + } + elsif ($pdf->[$pages]->{OBJ}->{Type} eq '/Page') + { + $$wantpg--; + $ret=$pages; + } + + return($ret); +} + +sub nextwd +{ + my $pdfwds=shift; + + my $wd=shift(@{$pdfwds}); + + return('') if !defined($wd); + + if ($wd=~m/^(.*?)(<<|>>|(?:(?<!\\)\[|\]))(.*)/) + { + if (defined($1) and length($1)) + { + unshift(@{$pdfwds},$3) if defined($3) and length($3); + unshift(@{$pdfwds},$2); + $wd=$1; + } + else + { + unshift(@{$pdfwds},$3) if defined($3) and length($3); + $wd=$2; + } + } + + return($wd); +} + +sub ParsePDFObj +{ + + my $pdfwds=shift; + my $rtn; + my $wd; + + while ($wd=nextwd($pdfwds),length($wd)) + { + if ($wd eq 'stream' or $wd eq 'endstream') + { + next; + } + elsif ($wd eq 'endobj' or $wd eq 'startxref') + { + last; + } + else + { + unshift(@{$pdfwds},$wd); + $rtn=ParsePDFValue($pdfwds); + } + } + + return($rtn); +} + +sub ParsePDFHash +{ + my $pdfwds=shift; + my $rtn={}; + my $wd; + + while ($wd=nextwd($pdfwds),length($wd)) + { + if ($wd eq '>>') + { + last; + } + + my (@w)=split('/',$wd,3); + + if ($w[0]) + { + Warn("PDF Dict Key '$wd' does not start with '/'"); + exit 1; + } + else + { + unshift(@{$pdfwds},"/$w[2]") if $w[2]; + $wd=$w[1]; + (@w)=split('\(',$wd,2); + $wd=$w[0]; + unshift(@{$pdfwds},"($w[1]") if defined($w[1]); + (@w)=split('\<',$wd,2); + $wd=$w[0]; + unshift(@{$pdfwds},"<$w[1]") if defined($w[1]); + + $rtn->{$wd}=ParsePDFValue($pdfwds); + } + } + + return($rtn); +} + +sub ParsePDFValue +{ + my $pdfwds=shift; + my $rtn; + my $wd=nextwd($pdfwds); + + if ($wd=~m/^\d+$/ and $pdfwds->[0]=~m/^\d+$/ and $pdfwds->[1]=~m/^R(\]|\>|\/)?/) + { + shift(@{$pdfwds}); + if (defined($1) and length($1)) + { + $pdfwds->[0]=substr($pdfwds->[0],1); + } + else + { + shift(@{$pdfwds}); + } + return(bless(\$wd,'OBJREF')); + } + + if ($wd eq '<<') + { + return(ParsePDFHash($pdfwds)); + } + + if ($wd eq '[') + { + return(ParsePDFArray($pdfwds)); + } + + if ($wd=~m/(.*?)(\(.*)$/) + { + if (defined($1) and length($1)) + { + unshift(@{$pdfwds},$2); + $wd=$1; + } + else + { + return(ParsePDFString($wd,$pdfwds)); + } + } + + if ($wd=~m/(.*?)(\<.*)$/) + { + if (defined($1) and length($1)) + { + unshift(@{$pdfwds},$2); + $wd=$1; + } + else + { + return(ParsePDFHexString($wd,$pdfwds)); + } + } + + if ($wd=~m/(.+?)(\/.*)$/) + { + if (defined($2) and length($2)) + { + unshift(@{$pdfwds},$2); + $wd=$1; + } + } + + return($wd); +} + +sub ParsePDFString +{ + my $wd=shift; + my $rtn=''; + my $pdfwds=shift; + my $lev=0; + + while (length($wd)) + { + $rtn.=' ' if length($rtn); + + while ($wd=~m/(?<!\\)\(/g) {$lev++;} + while ($wd=~m/(?<!\\)\)/g) {$lev--;} + + + if ($lev<=0 and $wd=~m/^(.*?\))([^)]+)$/) + { + unshift(@{$pdfwds},$2) if defined($2) and length($2); + $wd=$1; + } + + $rtn.=$wd; + + last if $lev <= 0; + + $wd=nextwd($pdfwds); + } + + return($rtn); +} + +sub ParsePDFHexString +{ + my $wd=shift; + my $rtn=''; + my $pdfwds=shift; + my $lev=0; + + if ($wd=~m/^(<.+?>)(.*)/) + { + unshift(@{$pdfwds},$2) if defined($2) and length($2); + $rtn=$1; + } + + return($rtn); +} + +sub ParsePDFArray +{ + my $pdfwds=shift; + my $rtn=[]; + my $wd; + + while (1) + { + $wd=ParsePDFValue($pdfwds); + last if $wd eq ']' or length($wd)==0; + push(@{$rtn},$wd); + } + + return($rtn); +} + +sub Warn +{ + Msg(0,(@_)); +} + +sub Die +{ + Msg(1,(@_)); +} + +sub Msg +{ + my ($fatal,$msg)=@_; + + print STDERR "$prog:"; + print STDERR "$env{SourceFile}:" if exists($env{SourceFile}); + print STDERR " "; + + if ($fatal) + { + print STDERR "fatal error: "; + } + else + { + print STDERR "warning: "; + } + + print STDERR "$msg\n"; + exit 1 if $fatal; +} + +sub PutXY +{ + my ($x,$y)=(@_); + + if ($frot) + { + return(d3($y)." ".d3($x)); + } + else + { + $y=$mediabox[3]-$y; + return(d3($x)." ".d3($y)); + } +} + +sub GraphY +{ + my $y=shift; + + if ($frot) + { + return($y); + } + else + { + return($mediabox[3]-$y); + } +} + +sub Put +{ + my $msg=shift; + + print $msg; + $fct+=length($msg); +} + +sub PutObj +{ + my $ono=shift; + my $msg="$ono 0 obj "; + $obj[$ono]->{XREF}=$fct; + if (exists($obj[$ono]->{STREAM})) + { + if ($gotzlib && !$debug && !exists($obj[$ono]->{DATA}->{'Filter'})) + { + $obj[$ono]->{STREAM}=Compress::Zlib::compress($obj[$ono]->{STREAM}); + $obj[$ono]->{DATA}->{'Filter'}='/FlateDecode'; + } + + $obj[$ono]->{DATA}->{'Length'}=length($obj[$ono]->{STREAM}); + } + PutField(\$msg,$obj[$ono]->{DATA}); + PutStream(\$msg,$ono) if exists($obj[$ono]->{STREAM}); + Put($msg."endobj\n"); +} + +sub PutStream +{ + my $msg=shift; + my $ono=shift; + + # We could 'flate' here + $$msg.="stream\n$obj[$ono]->{STREAM}endstream\n"; +} + +sub PutField +{ + my $pmsg=shift; + my $fld=shift; + my $term=shift||"\n"; + my $typ=ref($fld); + + if ($typ eq '') + { + $$pmsg.="$fld$term"; + } + elsif ($typ eq 'ARRAY') + { + $$pmsg.='['; + foreach my $cell (@{$fld}) + { + PutField($pmsg,$cell,' '); + } + $$pmsg.="]$term"; + } + elsif ($typ eq 'HASH') + { + $$pmsg.='<< '; + foreach my $key (sort keys %{$fld}) + { + $$pmsg.="/$key "; + PutField($pmsg,$fld->{$key}); + } + $$pmsg.=">>$term"; + } + elsif ($typ eq 'OBJREF') + { + $$pmsg.="$$fld 0 R$term"; + } +} + +sub BuildObj +{ + my $ono=shift; + my $val=shift; + + $obj[$ono]->{DATA}=$val; + + return("$ono 0 R "); +} + +sub LoadFont +{ + my $fontno=shift; + my $fontnm=shift; + my $ofontnm=$fontnm; + + return $fontlst{$fontno}->{OBJ} if (exists($fontlst{$fontno})); + + my $f; + OpenFile(\$f,$fontdir,"$fontnm"); + + if (!defined($f) and $Foundry) + { + # Try with no foundry + $fontnm=~s/.*?-//; + OpenFile(\$f,$fontdir,$fontnm); + } + + Die("unable to open font '$ofontnm' for mounting") if !defined($f); + + my $foundry=''; + $foundry=$1 if $fontnm=~m/^(.*?)-/; + my $stg=1; + my %fnt; + my @fntbbox=(0,0,0,0); + my $capheight=0; + my $lastchr=0; + my $lastnm; + my $t1flags=0; + my $fixwid=-1; + my $ascent=0; + my $charset=''; + + while (<$f>) + { + chomp; + + s/^ +//; + s/^#.*// if $stg == 1; + next if $_ eq ''; + + if ($stg == 1) + { + my ($key,$val)=split(' ',$_,2); + + $key=lc($key); + $stg=2,next if $key eq 'kernpairs'; + $stg=3,next if lc($_) eq 'charset'; + + $fnt{$key}=$val + } + elsif ($stg == 2) + { + $stg=3,next if lc($_) eq 'charset'; + + my ($ch1,$ch2,$k)=split; +# $fnt{KERN}->{$ch1}->{$ch2}=$k; + } + else + { + my (@r)=split; + my (@p)=split(',',$r[1]); + + if ($r[1] eq '"') + { + $fnt{NAM}->{$r[0]}=[@{$fnt{NAM}->{$lastnm}}]; + next; + } + + $r[3]=oct($r[3]) if substr($r[3],0,1) eq '0'; + $r[0]='u0020' if $r[3] == 32; + $r[0]="u00".hex($r[3]) if $r[0] eq '---'; +# next if $r[3] >255; + $r[4]=$r[0] if !defined($r[4]); + $fnt{NAM}->{$r[0]}=[$p[0],$r[3],'/'.$r[4],$r[3],0]; + $fnt{NO}->[$r[3]]=[$r[0],$r[0]]; + $lastnm=$r[0]; + $lastchr=$r[3] if $r[3] > $lastchr; + $fixwid=$p[0] if $fixwid == -1; + $fixwid=-2 if $fixwid > 0 and $p[0] != $fixwid; + + $fntbbox[1]=-$p[2] if defined($p[2]) and -$p[2] < $fntbbox[1]; + $fntbbox[2]=$p[0] if $p[0] > $fntbbox[2]; + $fntbbox[3]=$p[1] if defined($p[1]) and $p[1] > $fntbbox[3]; + $ascent=$p[1] if defined($p[1]) and $p[1] > $ascent and $r[3] >= 32 and $r[3] < 128; + $charset.='/'.$r[4] if defined($r[4]); + $capheight=$p[1] if length($r[4]) == 1 and $r[4] ge 'A' and $r[4] le 'Z' and $p[1] > $capheight; + } + } + + close($f); + + foreach my $j (0..$lastchr) + { + $fnt{NO}->[$j]=['',''] if !defined($fnt{NO}->[$j]); + } + + my $fno=0; + my $slant=0; + $fnt{DIFF}=[]; + $fnt{WIDTH}=[]; + $fnt{NAM}->{''}=[0,-1,'/.notdef',-1,0]; + $slant=-$fnt{'slant'} if exists($fnt{'slant'}); + $fnt{'spacewidth'}=700 if !exists($fnt{'spacewidth'}); + + $t1flags|=2**0 if $fixwid > -1; + $t1flags|=(exists($fnt{'special'}))?2**2:2**5; + $t1flags|=2**6 if $slant != 0; + my $fontkey="$foundry $fnt{internalname}"; + + if (exists($download{$fontkey})) + { + # Not a Base Font + my ($l1,$l2,$l3,$t1stream)=GetType1($download{$fontkey}); + Warn("incorrect font format for '$fontkey' ($l1)") + if !defined($t1stream); + $fno=++$objct; + $fontlst{$fontno}->{OBJ}=BuildObj($objct, + {'Type' => '/Font', + 'Subtype' => '/Type1', + 'BaseFont' => '/'.$fnt{internalname}, + 'Widths' => $fnt{WIDTH}, + 'FirstChar' => 0, + 'LastChar' => $lastchr, + 'Encoding' => BuildObj($objct+1, + {'Type' => '/Encoding', + 'Differences' => $fnt{DIFF} + } + ), + 'FontDescriptor' => BuildObj($objct+2, + {'Type' => '/FontDescriptor', + 'FontName' => '/'.$fnt{internalname}, + 'Flags' => $t1flags, + 'FontBBox' => \@fntbbox, + 'ItalicAngle' => $slant, + 'Ascent' => $ascent, + 'Descent' => $fntbbox[1], + 'CapHeight' => $capheight, + 'StemV' => 0, +# 'CharSet' => "($charset)", + 'FontFile' => BuildObj($objct+3, + {'Length1' => $l1, + 'Length2' => $l2, + 'Length3' => $l3 + } + ) + } + ) + } + ); + + $objct+=3; + $fontlst{$fontno}->{NM}='/F'.$fontno; + $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ}; + $fontlst{$fontno}->{FNT}=\%fnt; + $obj[$objct]->{STREAM}=$t1stream; + + } + else + { + if (exists($missing{$fontkey})) + { + Warn("The download file in '$missing{$fontkey}' " + . " has erroneous entry for '$fnt{internalname} ($ofontnm)'"); + } + else + { + Warn("unable to embed font file for '$fnt{internalname}'" + . " ($ofontnm) (missing entry in 'download' file?)") + if $embedall; + } + $fno=++$objct; + $fontlst{$fontno}->{OBJ}=BuildObj($objct, + {'Type' => '/Font', + 'Subtype' => '/Type1', + 'BaseFont' => '/'.$fnt{internalname}, + 'Widths' => $fnt{WIDTH}, + 'FirstChar' => 0, + 'LastChar' => $lastchr, + 'Encoding' => BuildObj($objct+1, + {'Type' => '/Encoding', + 'Differences' => $fnt{DIFF} + } + ), + 'FontDescriptor' => BuildObj($objct+2, + {'Type' => '/FontDescriptor', + 'FontName' => '/'.$fnt{internalname}, + 'Flags' => $t1flags, + 'FontBBox' => \@fntbbox, + 'ItalicAngle' => $slant, + 'Ascent' => $ascent, + 'Descent' => $fntbbox[1], + 'CapHeight' => $capheight, + 'StemV' => 0, + 'CharSet' => "($charset)", + } + ) + } + ); + + $objct+=2; + $fontlst{$fontno}->{NM}='/F'.$fontno; + $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ}; + $fontlst{$fontno}->{FNT}=\%fnt; + } + + if (defined($fnt{encoding}) and $fnt{encoding} eq 'text.enc' and $ucmap ne '') + { + if ($textenccmap eq '') + { + $textenccmap = BuildObj($objct+1,{}); + $objct++; + $obj[$objct]->{STREAM}=$ucmap; + } + $obj[$fno]->{DATA}->{'ToUnicode'}=$textenccmap; + } + +# PutObj($fno); +# PutObj($fno+1); +# PutObj($fno+2) if defined($obj[$fno+2]); +# PutObj($fno+3) if defined($obj[$fno+3]); +} + +sub GetType1 +{ + my $file=shift; + my ($l1,$l2,$l3); # Return lengths + my ($head,$body,$tail); # Font contents + my $f; + + OpenFile(\$f,$fontdir,"$file"); + Die("unable to open font '$file' for embedding") if !defined($f); + + $head=GetChunk($f,1,"currentfile eexec"); + $body=GetChunk($f,2,"00000000") if !eof($f); + $tail=GetChunk($f,3,"cleartomark") if !eof($f); + + $l1=length($head); + $l2=length($body); + $l3=length($tail); + + return($l1,$l2,$l3,"$head$body$tail"); +} + +sub GetChunk +{ + my $F=shift; + my $segno=shift; + my $ascterm=shift; + my ($type,$hdr,$chunk,@msg); + binmode($F); + my $enc="ascii"; + + while (1) + { + # There may be multiple chunks of the same type + + my $ct=read($F,$hdr,2); + + if ($ct==2) + { + if (substr($hdr,0,1) eq "\x80") + { + # binary chunk + + my $chunktype=ord(substr($hdr,1,1)); + $enc="binary"; + + if (defined($type) and $type != $chunktype) + { + seek($F,-2,1); + last; + } + + $type=$chunktype; + return if $chunktype == 3; + + $ct=read($F,$hdr,4); + Die("failed to read binary segment length") if $ct != 4; + my $sl=unpack('V',$hdr); + my $data; + my $chk=read($F,$data,$sl); + Die("failed to read binary segment") if $chk != $sl; + $chunk.=$data; + } + else + { + # ascii chunk + + my $hex=0; + seek($F,-2,1); + my $ct=0; + + while (1) + { + my $lin=<$F>; + + last if !$lin; + + $hex=1,$enc.=" hex" if $segno == 2 and !$ct and $lin=~m/^[A-F0-9a-f]{4,4}/; + + if ($segno !=2 and $lin=~m/^(.*$ascterm\n?)(.*)/) + { + $chunk.=$1; + seek($F,-length($2)-1,1) if $2; + last; + } + elsif ($segno == 2 and $lin=~m/^(.*?)($ascterm.*)/) + { + $chunk.=$1; + seek($F,-length($2)-1,1) if $2; + last; + } + + chomp($lin), $lin=pack('H*',$lin) if $hex; + $chunk.=$lin; $ct++; + } + + last; + } + } + else + { + push(@msg,"Failed to read 2 header bytes"); + } + } + + return $chunk; +} + +sub OutStream +{ + my $ono=shift; + + IsGraphic(); + $stream.="Q\n"; + $obj[$ono]->{STREAM}=$stream; + $obj[$ono]->{DATA}->{Length}=length($stream); + $stream=''; + PutObj($ono); +} + +sub do_p +{ + my $trans='BLOCK'; + + $trans='PAGE' if $firstpause; + NewPage($trans); + @XOstream=(); + @PageAnnots=(); + $firstpause=1; +} + +sub FixTrans +{ + my $t=shift; + my $style=$t->{S}; + + if ($style) + { + delete($t->{Dm}) if $style ne '/Split' and $style ne '/Blinds'; + delete($t->{M}) if !($style eq '/Split' or $style eq '/Box' or $style eq '/Fly'); + delete($t->{Di}) if !($style eq '/Wipe' or $style eq '/Glitter' or $style eq '/Fly' or $style eq '/Cover' or $style eq '/Uncover' or $style eq '/Push') or ($style eq '/Fly' and $t->{Di} eq '/None' and $t->{SS} != 1); + delete($t->{SS}) if !($style eq '/Fly'); + delete($t->{B}) if !($style eq '/Fly'); + } + + return($t); +} + +sub NewPage +{ + my $trans=shift; + # Start of pages + + if ($cpageno > 0) + { + if ($#XOstream>=0) + { + MakeXO() if $stream; + $stream=join("\n",@XOstream,''); + } + + my %t=%{$transition->{$trans}}; + $cpage->{MediaBox}=\@mediabox if $custompaper; + $cpage->{Trans}=FixTrans(\%t) if $t{S}; + + if ($#PageAnnots >= 0) + { + @{$cpage->{Annots}}=@PageAnnots; + } + + if ($#bgstack > -1 or $bgbox) + { + my $box="q 1 0 0 1 0 0 cm "; + + foreach my $bg (@bgstack) + { + # 0=$bgtype # 1=stroke 2=fill. 4=page + # 1=$strkcol + # 2=$fillcol + # 3=(Left,Top,Right,bottom,LineWeight) + # 4=Start ypos + # 5=Endypos + # 6=Line Weight + + my $pg=$bg->[3] || \@defaultmb; + + $bg->[5]=$pg->[3]; # box is continuing to next page + $box.=DrawBox($bg); + $bg->[4]=$pg->[1]; # will continue from page top + } + + $stream=$box.$bgbox."Q\n".$stream; + $bgbox=''; + $boxmax=0; + } + + PutObj($cpageno); + OutStream($cpageno+1); + } + + $cpageno=++$objct; + + my $thispg=BuildObj($objct, + {'Type' => '/Page', + 'Group' => {'CS' => '/DeviceRGB', 'S' => '/Transparency'}, + 'Parent' => '2 0 R', + 'Contents' => [ BuildObj($objct+1, + {'Length' => 0} + ) ], + } + ); + + splice(@{$pages->{Kids}},++$pginsert,0,$thispg); + splice(@outlines,$pginsert,0,[$curoutlev,$#{$curoutlev}+1,$thislev]); + + $objct+=1; + $cpage=$obj[$cpageno]->{DATA}; + $pages->{'Count'}++; + $stream="q 1 0 0 1 0 0 cm\n$linejoin J\n$linecap j\n0.4 w\n"; + $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne ''; + $mode='g'; + $curfill=''; +# @mediabox=@defaultmb; +} + +sub DrawBox +{ + my $bg=shift; + my $res=''; + my $pg=$bg->[3] || \@mediabox; + $bg->[4]=$pg->[1], $bg->[5]=$pg->[3] if $bg->[0] & 4; + my $bot=$bg->[5]; + $bot=$boxmax if $boxmax > $bot; + my $wid=$pg->[2]-$pg->[0]; + my $dep=$bot-$bg->[4]; + + $res="$bg->[1] $bg->[2] $bg->[6] w\n"; + $res.="$pg->[0] $bg->[4] $wid $dep re f " if $bg->[0] & 1; + $res.="$pg->[0] $bg->[4] $wid $dep re s " if $bg->[0] & 2; + return("$res\n"); +} + +sub MakeXO +{ + $stream.="%mode=$mode\n"; + IsGraphic(); + $stream.="Q\n"; + my $xobj=++$objct; + my $xonm="XO$xobj"; + $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => '/XObject', 'BBox' => \@mediabox, 'Name' => "/$xonm", 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject"}); + $obj[$xobj]->{STREAM}=$stream; + $stream=''; + push(@XOstream,"q") if $#XOstream==-1; + push(@XOstream,"/$xonm Do"); +} + +sub do_f +{ + my $par=shift; + my $fnt=$fontlst{$par}->{FNT}; + +# IsText(); + $cft="$par"; + $fontchg=1; +# $stream.="/F$cft $cftsz Tf\n" if $cftsz; + $widtbl=CacheWid($par); + $origwidtbl=[]; + + foreach my $w (@{$fnt->{NO}}) + { + push(@{$origwidtbl},$fnt->{NAM}->{$w->[1]}->[WIDTH]); + } + +# $krntbl=$fnt->{KERN}; +} + +sub CacheWid +{ + my $par=shift; + + if (!defined($fontlst{$par}->{CACHE}->{$cftsz})) + { + $fontlst{$par}->{CACHE}->{$cftsz}=BuildCache($fontlst{$par}->{FNT}); + } + + return($fontlst{$par}->{CACHE}->{$cftsz}); +} + +sub BuildCache +{ + my $fnt=shift; + my @cwid; + $origwidtbl=[]; + + foreach my $w (@{$fnt->{NO}}) + { + my $wid=(defined($w) and defined($w->[1]))?$fnt->{NAM}->{$w->[1]}->[WIDTH]:0; + push(@cwid,$wid*$cftsz); + push(@{$origwidtbl},$wid); + } + + return(\@cwid); +} + +sub IsText +{ + if ($mode eq 'g') + { + $xpos+=$pendmv/$unitwidth; + $stream.="q BT\n$matrix ".PutXY($xpos,$ypos)." Tm\n"; + $poschg=0; + $fontchg=0; + $pendmv=0; + $matrixchg=0; + $tmxpos=$xpos; + $stream.=$textcol."\n", $curfill=$textcol if $textcol ne $curfill; + if (defined($cft)) + { + $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz; + $stream.="/F$cft $cftsz Tf\n"; + } + $stream.="$curkern Tc\n"; + } + + if ($poschg or $matrixchg) + { + PutLine(0) if $matrixchg; + $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0; + $tmxpos=$xpos; + $matrixchg=0; + $stream.="$curkern Tc\n"; + } + + if ($fontchg) + { + PutLine(0); + $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz; + $stream.="/F$cft $cftsz Tf\n" if $cftsz and defined($cft); + $fontchg=0; + } + + $mode='t'; +} + +sub IsGraphic +{ + if ($mode eq 't') + { + PutLine(); + $stream.="ET Q\n"; + $xpos+=($pendmv-$nomove)/$unitwidth; + $pendmv=0; + $nomove=0; + $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne $curstrk; + $curfill=$fillcol; + } + $mode='g'; +} + +sub do_s +{ + my $par=shift; + $par/=$unitwidth; + + if ($par != $cftsz and defined($cft)) + { + PutLine(); + $cftsz=$par; + Set_LWidth() if $lwidth < 1; +# $stream.="/F$cft $cftsz Tf\n"; + $fontchg=1; + $widtbl=CacheWid($cft); + } + else + { + $cftsz=$par; + Set_LWidth() if $lwidth < 1; + } +} + +sub Set_LWidth +{ + IsGraphic(); + $stream.=((($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000)." w\n"; + return; +} + +sub do_m +{ + # Groff uses /m[] for text & graphic stroke, and /M[] (DF?) for graphic fill. + # PDF uses G/RG/K for graphic stroke, and g/rg/k for text & graphic fill. + # + # This means that we must maintain g/rg/k state separately for text colour & graphic fill (this is + # probably why 'gs' maintains separate graphic states for text & graphics when distilling PS -> PDF). + # + # To facilitate this:- + # + # $textcol = current groff stroke colour + # $fillcol = current groff fill colour + # $curfill = current PDF fill colour + + my $par=shift; + my $mcmd=substr($par,0,1); + + $par=substr($par,1); + $par=~s/^ +//; + +# IsGraphic(); + + $textcol=set_col($mcmd,$par,0); + $strkcol=set_col($mcmd,$par,1); + + if ($mode eq 't') + { + PutLine(); + $stream.=$textcol."\n"; + $curfill=$textcol; + } + else + { + $stream.="$strkcol\n"; + $curstrk=$strkcol; + } +} + +sub set_col +{ + my $mcmd=shift; + my $par=shift; + my $upper=shift; + my @oper=('g','k','rg'); + + @oper=('G','K','RG') if $upper; + + if ($mcmd eq 'd') + { + # default colour + return("0 $oper[0]"); + } + + my (@c)=split(' ',$par); + + if ($mcmd eq 'c') + { + # Text CMY + return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535)." 0 $oper[1]"); + } + elsif ($mcmd eq 'k') + { + # Text CMYK + return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535).' '.d3($c[3]/65535)." $oper[1]"); + } + elsif ($mcmd eq 'g') + { + # Text Grey + return(d3($c[0]/65535)." $oper[0]"); + } + elsif ($mcmd eq 'r') + { + # Text RGB0 + return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535)." $oper[2]"); + } +} + +sub do_D +{ + my $par=shift; + my $Dcmd=substr($par,0,1); + + $par=substr($par,1); + $xpos+=$pendmv/$unitwidth; + $pendmv=0; + + IsGraphic(); + + if ($Dcmd eq 'F') + { + my $mcmd=substr($par,0,1); + + $par=substr($par,1); + $par=~s/^ +//; + + $fillcol=set_col($mcmd,$par,0); + $stream.="$fillcol\n"; + $curfill=$fillcol; + } + elsif ($Dcmd eq 'f') + { + my $mcmd=substr($par,0,1); + + $par=substr($par,1); + $par=~s/^ +//; + ($par)=split(' ',$par); + + if ($par >= 0 and $par <= 1000) + { + $fillcol=set_col('g',int((1000-$par)*65535/1000),0); + } + else + { + $fillcol=lc($textcol); + } + + $stream.="$fillcol\n"; + $curfill=$fillcol; + } + elsif ($Dcmd eq '~') + { + # B-Spline + my (@p)=split(' ',$par); + my ($nxpos,$nypos); + + foreach my $p (@p) { $p/=$unitwidth; } + $stream.=PutXY($xpos,$ypos)." m\n"; + $xpos+=($p[0]/2); + $ypos+=($p[1]/2); + $stream.=PutXY($xpos,$ypos)." l\n"; + + for (my $i=0; $i < $#p-1; $i+=2) + { + $nxpos=(($p[$i]*$tnum)/(2*$tden)); + $nypos=(($p[$i+1]*$tnum)/(2*$tden)); + $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." "; + $nxpos=($p[$i]/2 + ($p[$i+2]*($tden-$tnum))/(2*$tden)); + $nypos=($p[$i+1]/2 + ($p[$i+3]*($tden-$tnum))/(2*$tden)); + $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." "; + $nxpos=(($p[$i]-$p[$i]/2) + $p[$i+2]/2); + $nypos=(($p[$i+1]-$p[$i+1]/2) + $p[$i+3]/2); + $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." c\n"; + $xpos+=$nxpos; + $ypos+=$nypos; + } + + $xpos+=($p[$#p-1]-$p[$#p-1]/2); + $ypos+=($p[$#p]-$p[$#p]/2); + $stream.=PutXY($xpos,$ypos)." l\nS\n"; + $poschg=1; + } + elsif ($Dcmd eq 'p' or $Dcmd eq 'P') + { + # Polygon + my (@p)=split(' ',$par); + my ($nxpos,$nypos); + + foreach my $p (@p) { $p/=$unitwidth; } + $stream.=PutXY($xpos,$ypos)." m\n"; + + for (my $i=0; $i < $#p; $i+=2) + { + $xpos+=($p[$i]); + $ypos+=($p[$i+1]); + $stream.=PutXY($xpos,$ypos)." l\n"; + } + + if ($Dcmd eq 'p') + { + $stream.="s\n"; + } + else + { + $stream.="f\n"; + } + $poschg=1; + } + elsif ($Dcmd eq 'c') + { + # Stroke circle + $par=substr($par,1); + my (@p)=split(' ',$par); + + DrawCircle($p[0],$p[0]); + $stream.="s\n"; + $poschg=1; + } + elsif ($Dcmd eq 'C') + { + # Fill circle + $par=substr($par,1); + my (@p)=split(' ',$par); + + DrawCircle($p[0],$p[0]); + $stream.="f\n"; + $poschg=1; + } + elsif ($Dcmd eq 'e') + { + # Stroke ellipse + $par=substr($par,1); + my (@p)=split(' ',$par); + + DrawCircle($p[0],$p[1]); + $stream.="s\n"; + $poschg=1; + } + elsif ($Dcmd eq 'E') + { + # Fill ellipse + $par=substr($par,1); + my (@p)=split(' ',$par); + + DrawCircle($p[0],$p[1]); + $stream.="f\n"; + $poschg=1; + } + elsif ($Dcmd eq 'l') + { + # Line To + $par=substr($par,1); + my (@p)=split(' ',$par); + + foreach my $p (@p) { $p/=$unitwidth; } + $stream.=PutXY($xpos,$ypos)." m\n"; + $xpos+=$p[0]; + $ypos+=$p[1]; + $stream.=PutXY($xpos,$ypos)." l\n"; + + $stream.="S\n"; + $poschg=1; + } + elsif ($Dcmd eq 't') + { + # Line Thickness + $par=substr($par,1); + my (@p)=split(' ',$par); + + foreach my $p (@p) { $p/=$unitwidth; } + # $xpos+=$p[0]*100; # WTF!!! + #int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000; + $p[0]=(($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000 if $p[0] < 0; + $lwidth=$p[0]; + $stream.="$p[0] w\n"; + $poschg=1; + $xpos+=$lwidth; + } + elsif ($Dcmd eq 'a') + { + # Arc + $par=substr($par,1); + my (@p)=split(' ',$par); + my $rad180=3.14159; + my $rad360=$rad180*2; + my $rad90=$rad180/2; + + foreach my $p (@p) { $p/=$unitwidth; } + + # Documentation is wrong. Groff does not use Dh1,Dv1 as centre of the circle! + + my $centre=adjust_arc_centre(\@p); + + # Using formula here : http://www.tinaja.com/glib/bezcirc2.pdf + # First calculate angle between start and end point + + my ($startang,$r)=RtoP(-$centre->[0],$centre->[1]); + my ($endang,$r2)=RtoP(($p[0]+$p[2])-$centre->[0],-($p[1]+$p[3]-$centre->[1])); + $endang+=$rad360 if $endang < $startang; + my $totang=($endang-$startang)/4; # do it in 4 pieces + + # Now 1 piece + + my $x0=cos($totang/2); + my $y0=sin($totang/2); + my $x3=$x0; + my $y3=-$y0; + my $x1=(4-$x0)/3; + my $y1=((1-$x0)*(3-$x0))/(3*$y0); + my $x2=$x1; + my $y2=-$y1; + + # Rotate to start position and draw 4 pieces + + foreach my $j (0..3) + { + PlotArcSegment($totang/2+$startang+$j*$totang,$r,$xpos+$centre->[0],GraphY($ypos+$centre->[1]),$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3); + } + + $xpos+=$p[0]+$p[2]; + $ypos+=$p[1]+$p[3]; + + $poschg=1; + } +} + +sub deg +{ + return int($_[0]*180/3.14159); +} + +sub adjust_arc_centre +{ + # Taken from geometry.cpp + + # We move the center along a line parallel to the line between + # the specified start point and end point so that the center + # is equidistant between the start and end point. + # It can be proved (using Lagrange multipliers) that this will + # give the point nearest to the specified center that is equidistant + # between the start and end point. + + my $p=shift; + my @c; + my $x = $p->[0] + $p->[2]; # (x, y) is the end point + my $y = $p->[1] + $p->[3]; + my $n = $x*$x + $y*$y; + if ($n != 0) + { + $c[0]= $p->[0]; + $c[1] = $p->[1]; + my $k = .5 - ($c[0]*$x + $c[1]*$y)/$n; + $c[0] += $k*$x; + $c[1] += $k*$y; + return(\@c); + } + else + { + return(undef); + } +} + + +sub PlotArcSegment +{ + my ($ang,$r,$transx,$transy,$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3)=@_; + my $cos=cos($ang); + my $sin=sin($ang); + my @mat=($cos,$sin,-$sin,$cos,0,0); + my $lw=$lwidth/$r; + + $stream.="q $r 0 0 $r $transx $transy cm ".join(' ',@mat)." cm $lw w $x0 $y0 m $x1 $y1 $x2 $y2 $x3 $y3 c S Q\n"; +} + +sub DrawCircle +{ + my $hd=shift; + my $vd=shift; + my $hr=$hd/2/$unitwidth; + my $vr=$vd/2/$unitwidth; + my $kappa=0.5522847498; + $hd/=$unitwidth; + $vd/=$unitwidth; + + + $stream.=PutXY(($xpos+$hd),$ypos)." m\n"; + $stream.=PutXY(($xpos+$hd),($ypos+$vr*$kappa))." ".PutXY(($xpos+$hr+$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos+$hr),($ypos+$vr))." c\n"; + $stream.=PutXY(($xpos+$hr-$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos),($ypos+$vr*$kappa))." ".PutXY(($xpos),($ypos))." c\n"; + $stream.=PutXY(($xpos),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hr-$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hr),($ypos-$vr))." c\n"; + $stream.=PutXY(($xpos+$hr+$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hd),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hd),($ypos))." c\n"; + $xpos+=$hd; + + $poschg=1; +} + +sub FindCircle +{ + my ($x1,$y1,$x2,$y2,$x3,$y3)=@_; + my ($Xo, $Yo); + + my $x=$x2+$x3; + my $y=$y2+$y3; + my $n=$x**2+$y**2; + + if ($n) + { + my $k=.5-($x2*$x + $y2*$y)/$n; + return(sqrt($n),$x2+$k*$x,$y2+$k*$y); + } + else + { + return(-1); + } + +} + +sub PtoR +{ + my ($theta,$r)=@_; + + return($r*cos($theta),$r*sin($theta)); +} + +sub RtoP +{ + my ($x,$y)=@_; + + return(atan2($y,$x),sqrt($x**2+$y**2)); +} + +sub PutLine +{ + + my $f=shift; + + IsText() if !defined($f); + + return if (scalar(@lin) == 0) or (!defined($lin[0]->[0]) and $#lin == 0); + +# $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug; + $pendmv-=$nomove; + $lin[$#lin]->[1]=-$pendmv/$cftsz if ($pendmv != 0); + + foreach my $wd (@lin) + { + next if !defined($wd->[0]); + $wd->[0]=~s/\\/\\\\/g; + $wd->[0]=~s/\(/\\(/g; + $wd->[0]=~s/\)/\\)/g; + $wd->[0]=~s/!\|!\|/\\/g; + $wd->[1]=d3($wd->[1]); + } + + if (0) + { + if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0)) + { + $stream.="($lin[0]->[0]) Tj\n"; + } + else + { + $stream.="["; + + foreach my $wd (@lin) + { + $stream.="($wd->[0]) " if defined($wd->[0]); + $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0; + } + + $stream.="] TJ\n"; + } + } + else + { + if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0)) + { + $stream.="0 Tw ($lin[0]->[0]) Tj\n"; + } + else + { + if ($wt>=-1 or $#lin == 0 or $lin[0]->[1]>=0) + { + $stream.="0 Tw ["; + + foreach my $wd (@lin) + { + $stream.="($wd->[0]) " if defined($wd->[0]); + $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0; + } + + $stream.="] TJ\n"; + } + else + { + # $stream.="\%dg 0 Tw ["; + # + # foreach my $wd (@lin) + # { + # $stream.="($wd->[0]) " if defined($wd->[0]); + # $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0; + # } + # + # $stream.="] TJ\n"; + # + # my $wt=$lin[0]->[1]||0; + + # while ($wt < -$whtsz/$cftsz) + # { + # $wt+=$whtsz/$cftsz; + # } + + $stream.=sprintf( "%.3f Tw ",-($whtsz+$wt*$cftsz)/$unitwidth-$curkern ); + if (!defined($lin[0]->[0]) and defined($lin[0]->[1])) + { + $stream.="[ $lin[0]->[1] ("; + shift @lin; + } + else + { + $stream.="[("; + } + + foreach my $wd (@lin) + { + my $wwt=$wd->[1]||0; + + while ($wwt <= $wt+.1) + { + $wwt-=$wt; + $wd->[0].=' '; + } + + if (abs($wwt) < .1 or $wwt == 0) + { + $stream.="$wd->[0]" if defined($wd->[0]); + } + else + { + $wwt=sprintf("%.3f",$wwt); + $stream.="$wd->[0]) $wwt (" if defined($wd->[0]); + } + } + $stream.=")] TJ\n"; + } + } + } + + @lin=(); + $xpos+=$pendmv/$unitwidth; + $pendmv=0; + $nomove=0; + $wt=-1; +} + +sub d3 +{ + return(sprintf("%.3f",shift || 0)); +} + +sub LoadAhead +{ + my $no=shift; + + foreach my $j (1..$no) + { + my $lin=<>; + chomp($lin); + $lin=~s/\r$//; + $lct++; + + push(@ahead,$lin); + $stream.="%% $lin\n" if $debug; + } +} + +sub do_V +{ + my $par=shift; + + if ($mode eq 't') + { + PutLine(); + } + else + { + $xpos+=$pendmv/$unitwidth; + $pendmv=0; + } + + $ypos=$par/$unitwidth; + + LoadAhead(1); + + if (substr($ahead[0],0,1) eq 'H') + { + $xpos=substr($ahead[0],1)/$unitwidth; + + $nomove=$pendmv=0; + @ahead=(); + + } + + $poschg=1; +} + +sub do_v +{ + my $par=shift; + + PutLine() if $mode eq 't'; + + $ypos+=$par/$unitwidth; + + $poschg=1; +} + +sub TextWid +{ + my $txt=shift; + my $fnt=shift; + my $w=0; + my $ck=0; + + foreach my $c (split('',$txt)) + { + my $cn=ord($c); + $widtbl->[$cn]=$origwidtbl->[$cn]*$cftsz if !defined($widtbl->[$cn]); + $w+=$widtbl->[$cn]; + } + + $ck=length($txt)*$curkern; + + return(($w/$unitwidth)+$ck); +} + +sub do_t +{ + my $par=shift; + my $fnt=$fontlst{$cft}->{FNT}; + + if ($kernadjust != $curkern) + { + PutLine(); + $stream.="$kernadjust Tc\n"; + $curkern=$kernadjust; + } + + my $par2=$par; + $par2=~s/^!\|!\|(\d\d\d)/chr(oct($1))/e; + + foreach my $j (0..length($par2)-1) + { + my $cn=ord(substr($par2,$j,1)); + my $chnm=$fnt->{NAM}->{$fnt->{NO}->[$cn]->[1]}; + + if ($chnm->[USED]==0) + { + $chnm->[USED]=1; + } + elsif ($fnt->{NO}->[$cn]->[0] ne $fnt->{NO}->[$cn]->[1]) + { + # A glyph has already been remapped to this char, so find a spare + + my $cn2=RemapChr($cn,$fnt,$fnt->{NO}->[$cn]->[0]); + $stream.="% MMM Remap $cn to $cn2\n" if $debug; + + if ($cn2) + { + substr($par2,$j,1)=chr($cn2); + + if ($par=~m/^!\|!\|(\d\d\d)/) + { + substr($par,4,3)=sprintf("%03o",$cn2); + } + else + { + substr($par,$j,1)=chr($cn2); + } + } + } + } + my $wid=TextWid($par2,$fnt); + + $par=reverse(split('',$par)) if $xrev and $par!~m/^!\|!\|(\d\d\d)/; + + if ($n_flg and defined($mark)) + { + $mark->{ypos}=$ypos; + $mark->{xpos}=$xpos; + } + + $n_flg=0; + IsText(); + + $xpos+=$wid; + $xpos+=($pendmv-$nomove)/$unitwidth; + + $stream.="% == '$par'=$wid 'xpos=$xpos\n" if $debug; + + # $pendmv = 'h' move since last 't' + # $nomove = width of char(s) added by 'C', 'N' or 'c' + # $w-flg = 'w' seen since last t + + if ($fontchg) + { + PutLine(); + $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz; + $stream.="/F$cft $cftsz Tf\n", $fontchg=0 if $fontchg && defined($cft); + } + + $gotT=1; + + $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug; + +# if ($w_flg && $#lin > -1) +# { +# $lin[$#lin]->[0].=' '; +# $pendmv-=$whtsz; +# $dontglue=1 if $pendmv==0; +# } + + $wt=-$pendmv/$cftsz if $w_flg and $wt==-1; + $pendmv-=$nomove; + $nomove=0; + $w_flg=0; + + if ($xrev) + { + PutLine(0) if $#lin > -1; + MakeMatrix(1); + $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0; + $stream.="$curkern Tc\n"; + $stream.="0 Tw "; + $stream.="($par) Tj\n"; + MakeMatrix(); + $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0; + $matrixchg=0; + $stream.="$curkern Tc\n"; + return; + } + + if ($pendmv) + { + if ($#lin == -1) + { + push(@lin,[undef,-$pendmv/$cftsz]); + } + else + { + $lin[$#lin]->[1]=-$pendmv/$cftsz; + } + + push(@lin,[$par,undef]); +# $xpos+=$pendmv/$unitwidth; + $pendmv=0 + } + else + { + if ($#lin == -1) + { + push(@lin,[$par,undef]); + } + else + { + $lin[$#lin]->[0].=$par; + } + } +} + +sub do_u +{ + my $par=shift; + + $par=m/([+-]?\d+) (.*)/; + $kernadjust=$1/$unitwidth; + do_t($2); + $kernadjust=0; +} + +sub do_h +{ + $pendmv+=shift; +} + +sub do_H +{ + my $par=shift; + + if ($mode eq 't') + { + PutLine(); + } + else + { + $xpos+=$pendmv/$unitwidth; + $pendmv=0; + } + + my $newx=$par/$unitwidth; + $stream.=sprintf("%.3f",$newx-$tmxpos)." 0 Td\n" if $mode eq 't'; + $tmxpos=$xpos=$newx; + $pendmv=$nomove=0; +} + +sub do_C +{ + my $par=shift; + + my ($par2,$nm)=FindChar($par); + + do_t($par2); + $nomove=$fontlst{$cft}->{FNT}->{NAM}->{$par}->[WIDTH]*$cftsz ; +} + +sub FindChar +{ + my $chnm=shift; + my $fnt=$fontlst{$cft}->{FNT}; + + if (exists($fnt->{NAM}->{$chnm})) + { + my $ch=$fnt->{NAM}->{$chnm}->[ASSIGNED]; + $ch=RemapChr($ch,$fnt,$chnm) if ($ch > 255); + $fnt->{NAM}->{$chnm}->[USED]=0 if $fnt->{NO}->[$ch]->[1] eq $chnm; + + return(($ch<32)?sprintf("!|!|%03o",$ch):chr($ch),$widtbl->[$ch]); + } + else + { + return(' '); + } +} + +sub RemapChr +{ + my $ch=shift; + my $fnt=shift; + my $chnm=shift; + my $unused=0; + + foreach my $un (0..$#{$fnt->{NO}}) + { + next if $un >= 139 and $un <= 144; + $unused=$un,last if $fnt->{NO}->[$un]->[1] eq ''; + } + + if (!$unused) + { + foreach my $un (128..255) + { + next if $un >= 139 and $un <= 144; + my $glyph=$fnt->{NO}->[$un]->[1]; + $unused=$un,last if $fnt->{NAM}->{$glyph}->[USED] == 0; + } + } + + if ($unused && $unused <= 255) + { + my $glyph=$fnt->{NO}->[$unused]->[1]; + delete($fontlst{$cft}->{CACHE}->{$cftsz}); + $fnt->{NAM}->{$chnm}->[ASSIGNED]=$unused; + $fnt->{NAM}->{$chnm}->[USED]=1; + $fnt->{NO}->[$unused]->[1]=$chnm; + $widtbl=CacheWid($cft); + + $stream.="% AAA Assign $chnm ($ch) to $unused\n" if $debug; + + $ch=$unused; + return($ch); + } + else + { + Warn("too many glyphs used in font '$cft'"); + return(32); + } +} + +sub do_c +{ + my $par=shift; + + push(@ahead,substr($par,1)); + $par=substr($par,0,1); + my $ch=ord($par); + do_N($ch); +} + +sub do_N +{ + my $par=shift; + my $fnt=$fontlst{$cft}->{FNT}; + + if (!defined($fnt->{NO}->[$par])) + { + Warn("no chr($par) in font $fnt->{internalname}"); + return; + } + + my $chnm=$fnt->{NO}->[$par]->[0]; + do_C($chnm); +} + +sub do_n +{ + $gotT=0; + PutLine(0); + $pendmv=$nomove=0; + $n_flg=1; + @lin=(); + PutHotSpot($xpos) if defined($mark); +} + +1; + +# Local Variables: +# fill-column: 72 +# mode: CPerl +# End: +# vim: set cindent noexpandtab shiftwidth=4 softtabstop=4 textwidth=72: diff --git a/src/devices/gropdf/pdfmom.1.man b/src/devices/gropdf/pdfmom.1.man new file mode 100644 index 0000000..08d789c --- /dev/null +++ b/src/devices/gropdf/pdfmom.1.man @@ -0,0 +1,229 @@ +.TH pdfmom @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +pdfmom \- produce PDF documents using the +.I mom +macro package for +.I groff +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 2012-2020 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_pdfmom_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY pdfmom +.RB [ \-Tpdf ] +.RI [ groff-options ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY pdfmom +.B \-Tps +.RI [ pdfroff-options ] +.RI [ groff-options ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY pdfmom +.B \-v +. +.SY pdfmom +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +.I pdfmom +is a wrapper around +.MR groff @MAN1EXT@ +that facilitates the production of PDF documents from files +formatted with the +.I mom +macros. +. +. +.P +.I pdfmom +prints to the standard output, +so output must usually be redirected to a destination file. +. +The size of the final PDF can be reduced by piping the output +through +.MR ps2pdf 1 . +. +. +.P +If called with the +.B \-Tpdf +option (which is the default), +.I pdfmom +processes files using +.IR groff 's +native PDF driver, +.MR gropdf @MAN1EXT@ . +. +If +.B \-Tps +is given, +processing is passed over to +.IR pdfroff , +which uses +.IR groff 's +PostScript driver. +. +In either case, +multiple runs of the source file are performed in order to satisfy any +forward references in the document. +. +. +.P +.I pdfmom +accepts all the same options as +.IR groff . +. +If +.B \-Tps +is given, +the options associated with +.I pdfroff +are accepted as well. +. +When +.I pdfmom +calls +.IR pdfroff , +the options +.RB \[lq] "\-mpdfmark \-mom \-\-no\-toc" \[rq] +options are implied and should not be given on the command line. +. +Equally, +it is not necessary to supply the +.B \-mom +or +.B "\-m\~mom" +options when +.B \-Tps +is absent. +. +. +.P +PDF integration with the +.I mom +macros is discussed in full in the manual +\[lq]Producing PDFs with +.I groff +and +.IR mom \[rq], +which was itself produced with +.IR pdfmom . +. +. +.P +If called with the +.B \-v +or +.B \-\-version +options, +.I pdfmom +displays its version information and exits. +. +. +.\" ==================================================================== +.SH Authors +.\" ==================================================================== +. +.I pdfmom +was written by +.MT deri@\:chuzzlewit\:.myzen\:.co\:.uk +Deri James +.ME +and +.MT peter@\:schaffter\:.ca +Peter Schaffter +.ME , +and is maintained by James. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.TP +.I @PDFDOCDIR@/\:mom\-pdf.pdf +\[lq]Producing PDFs with +.I groff +and +.IR mom \[rq], +by Deri James and Peter Schaffter. +. +This file, +together with its source, +.IR mom\-pdf.mom , +is part of the +.I groff +distribution. +. +. +.P +.MR groff @MAN1EXT@ , +.MR gropdf @MAN1EXT@ , +.MR pdfroff @MAN1EXT@ , +.MR ps2pdf 1 +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_pdfmom_1_man_C] +.do rr *groff_pdfmom_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/gropdf/pdfmom.pl b/src/devices/gropdf/pdfmom.pl new file mode 100644 index 0000000..89977d4 --- /dev/null +++ b/src/devices/gropdf/pdfmom.pl @@ -0,0 +1,150 @@ +#!@PERL@ +# +# pdfmom : Frontend to run groff -mom to produce PDFs +# Deri James : Friday 16 Mar 2012 +# + +# Copyright (C) 2012-2020 Free Software Foundation, Inc. +# Written by Deri James <deri@chuzzlewit.myzen.co.uk> +# +# 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 <http://www.gnu.org/licenses/>. + +use strict; +use warnings; +use File::Temp qw/tempfile/; +my @cmd; +my $dev='pdf'; +my $preconv=''; +my $readstdin=1; +my $RT_SEP='@RT_SEP@'; + +$ENV{PATH}=$ENV{GROFF_BIN_PATH}.$RT_SEP.$ENV{PATH} if exists($ENV{GROFF_BIN_PATH}); +$ENV{TMPDIR}=$ENV{GROFF_TMPDIR} if exists($ENV{GROFF_TMPDIR}); + +while (my $c=shift) +{ + $c=~s/(?<!\\)"/\\"/g; + + if (substr($c,0,2) eq '-T') + { + if (length($c) > 2) + { + $dev=substr($c,2); + } + else + { + $dev=shift; + } + next; + } + elsif (substr($c,0,2) eq '-K') + { + if (length($c) > 2) + { + $preconv=$c; + } + else + { + $preconv=$c; + $preconv.=shift; + } + next; + } + elsif (substr($c,0,2) eq '-k') + { + $preconv=$c; + next; + } + elsif ($c eq '-z' or $c eq '-Z') + { + $dev=$c; + next; + } + elsif ($c eq '-v' or $c eq '--version') + { + print "GNU pdfmom (groff) version @VERSION@\n"; + exit; + } + elsif (substr($c,0,1) eq '-') + { + if (length($c) > 1) + { + push(@cmd,"\"$c\""); + push(@cmd,"'".(shift)."'") if length($c)==2 and index('dDfFIKLmMnoPrwW',substr($c,-1)) >= 0; + } + else + { + # Just a '-' + + push(@cmd,$c); + $readstdin=2; + } + } + else + { + # Got a filename? + + push(@cmd,"\"$c\""); + $readstdin=0 if $readstdin == 1; + + } + +} + +my $cmdstring=' '.join(' ',@cmd).' '; + +if ($readstdin) +{ + my ($fh,$tmpfn)=tempfile('pdfmom-XXXXX', UNLINK=>1); + + while (<STDIN>) + { + print $fh ($_); + } + + close($fh); + + $cmdstring=~s/ - / $tmpfn / if $readstdin == 2; + $cmdstring.=" $tmpfn " if $readstdin == 1; +} + +if ($dev eq 'pdf') +{ + system("groff -Tpdf -dLABEL.REFS=1 -mom -z $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | groff -Tpdf -dPDF.EXPORT=1 -dLABEL.REFS=1 -mom -z - $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | groff -Tpdf -mom $preconv - $cmdstring"); +} +elsif ($dev eq 'ps') +{ + system("groff -Tpdf -dLABEL.REFS=1 -mom -z $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | pdfroff -mpdfmark -mom --no-toc - $preconv $cmdstring"); +} +elsif ($dev eq '-z') # pseudo dev - just compile for warnings +{ + system("groff -Tpdf -mom -z $cmdstring"); +} +elsif ($dev eq '-Z') # pseudo dev - produce troff output +{ + system("groff -Tpdf -mom -Z $cmdstring"); +} +else +{ + print STDERR "Not compatible with device '-T $dev'\n"; + exit 1; +} + +# Local Variables: +# fill-column: 72 +# mode: CPerl +# End: +# vim: set cindent noexpandtab shiftwidth=2 softtabstop=2 textwidth=72: diff --git a/src/devices/grops/TODO b/src/devices/grops/TODO new file mode 100644 index 0000000..eab8f83 --- /dev/null +++ b/src/devices/grops/TODO @@ -0,0 +1,24 @@ +Read PFB files directly. + +Generate %%For comment. + +Generate %%Title comment. + +Angles in arc command: don't generate more digits after the decimal +point than are necessary. + +Possibly generate BoundingBox comment. + +Per font composite character mechanism (sufficient for fractions). + +Consider whether we ought to do rounding of graphical objects other +than lines. What's the point? + +Error messages should refer to output page number. + +Search for downloadable fonts using their PostScript names if not +found in download file. + +Separate path for searching for downloadable font files. + +Clip to the BoundingBox when importing documents. diff --git a/src/devices/grops/grops.1.man b/src/devices/grops/grops.1.man new file mode 100644 index 0000000..d0ec21d --- /dev/null +++ b/src/devices/grops/grops.1.man @@ -0,0 +1,1831 @@ +.TH grops @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +grops \- +.I groff +output driver for PostScript +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1989-2018, 2020 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_grops_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.\" This macro definition is poor style from a portability standpoint, +.\" but it's a good test and demonstration of the standard font +.\" repertoire for the devices where it has any effect at all, and so +.\" should be retained. +.de FT +. if '\\*(.T'ps' .ft \\$1 +. if '\\*(.T'pdf' .ft \\$1 +.. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY grops +.RB [ \-glm ] +.RB [ \-b\~\c +.IR brokenness-flags ] +.RB [ \-c\~\c +.IR num-copies ] +.RB [ \-F\~\c +.IR font-directory ] +.RB [ \-I\~\c +.IR inclusion-directory ] +.RB [ \-p\~\c +.IR paper-format ] +.RB [ \-P\~\c +.IR prologue-file ] +.RB [ \-w\~\c +.IR rule-thickness ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY grops +.B \-\-help +.YS +. +. +.SY grops +.B \-v +. +.SY grops +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU +.I roff +PostScript output driver translates the output of +.MR @g@troff @MAN1EXT@ +into PostScript. +. +Normally, +.I grops +is invoked by +.MR groff @MAN1EXT@ +when the latter is given the +.RB \[lq] \-T\~ps \[rq] +option. +. +(In this installation, +.B @DEVICE@ +is the default output device.) +. +Use +.IR groff 's +.B \-P +option to pass any options shown above to +.IR grops . +. +If no +.I file +arguments are given, +or if +.I file +is \[lq]\-\[rq], +.I grotty +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.P +When called with multiple +.I file +arguments, +.I grops +doesn't produce a valid document structure +(one conforming to the Document Structuring Conventions). +. +To print such concatenated output, +it is necessary to deactivate DSC handling in the printing program or +previewer. +. +. +.P +See section \[lq]Font installation\[rq] below for a guide to installing +fonts for +.IR grops . +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.BI \-b\~ n +Work around problems with spoolers, +previewers, +and older printers. +. +Normally, +.I grops +produces output at PostScript \%LanguageLevel\~2 that conforms to +version 3.0 of the Document Structuring Conventions. +. +Some software and devices can't handle such a data stream. +. +The value +.RI of\~ n +determines what +.I grops +does to make its output acceptable to such consumers. +. +If +.I n +is +.BR 0 , +.I grops +employs no workarounds, +which is the default; +it can be changed by modifying the +.B broken +directive in +.IR grops 's +.I DESC +file. +. +. +.IP +Add\~1 to suppress generation of +.B %%Begin\%Document\%Setup +and +.B %%End\%Document\%Setup +comments; +this is needed for early versions of TranScript that get confused by +anything between the +.B %%End\%Prolog +comment and the first +.B %%Page +comment. +. +. +.IP +Add\~2 to omit lines in included files beginning with +.BR %!\& , +which confuse Sun's +.I pageview +previewer. +. +. +.IP +Add\~4 to omit lines in included files beginning with +.BR %%Page , +.B %%Trailer +and +.BR %%End\%Prolog ; +this is needed for spoolers that don't understand +.B %%Begin\%Document +and +.B %%End\%Document +comments. +. +. +.IP +Add\~8 to write +.B %!PS\-Adobe\-2.0 +rather than +.B %!PS\-Adobe\-3.0 +as the first line of the PostScript output; +this is needed when using Sun's Newsprint with a printer that requires +page reversal. +. +. +.IP +Add\~16 to omit media size information +(that is, +output neither a +.B %%Document\%Media +comment nor the +.B setpagedevice +PostScript command). +. +This was the behavior of +.I groff +1.18.1 and earlier; +it is +needed for older printers that don't understand PostScript +\%LanguageLevel\~2, +and is also necessary if the output is further processed to produce an +EPS file; +see subsection \[lq]Escapsulated PostScript\[rq] below. +. +. +.TP +.BI \-c\~ n +Output +.I n +copies of each page. +. +. +.TP +.BI \-F\~ dir +Prepend directory +.RI dir /dev name +to the search path for +font and device description and PostScript prologue files; +.I name +is the name of the device, +usually +.BR ps . +. +. +.TP +.B \-g +Generate PostScript code to guess the page length. +. +The guess is correct only if the imageable area is vertically centered +on the page. +. +This option allows you to generate documents that can be printed on both +U.S.\& letter and A4 paper formats without change. +. +. +.TP +.BI \-I\~ dir +Search the directory +.I dir +for files named in +.B \[rs]X\[aq]ps: file\[aq] +and +.B \[rs]X\[aq]ps: import\[aq] +escape sequences. +. +.B \-I +may be specified more than once; +each +.I dir +is searched in the given order. +. +To search the current working directory before others, +add +.RB \[lq] "\-I .\&" \[rq] +at the desired place; +it is otherwise searched last. +. +. +.TP +.B \-l +Use landscape orientation rather than portrait. +. +. +.TP +.B \-m +Turn on manual feed for the document. +. +. +.TP +.BI \-p\~ fmt +Set physical dimensions of output medium, +overriding the +.BR \%papersize , +.BR \%paperlength , +and +.B \%paperwidth +directives in the +.I DESC +file. +. +.I fmt +can be any argument accepted by the +.B \%papersize +directive; +see +.MR groff_font @MAN5EXT@ . +. +. +.TP +.BI \-P\~ prologue +Use the file +.IR prologue , +sought in the +.I groff +font search path, +as the PostScript prologue, +overriding the default +(see section \[lq]Files\[rq] below) +and the environment variable +.I GROPS_PROLOGUE. +. +. +.TP +.BI \-w\~ n +Draw rules (lines) with a thickness of +.IR n \~thousandths +of an em. +. +The default thickness is +.B 40 +(0.04\~em). +. +. +.\" ==================================================================== +.SH Usage +.\" ==================================================================== +. +The input to +.I grops +must be in the format output by +.MR @g@troff @MAN1EXT@ , +described in +.MR groff_out @MAN5EXT@ . +. +In addition, +the device and font description files for the device used must meet +certain requirements. +. +The device resolution must be an integer multiple of\~72 times the +.BR sizescale . +. +The device description file must contain a valid paper format; +see +.MR groff_font @MAN5EXT@ . +. +Each font description file must contain a directive +. +.RS +.EX +.RI internalname\~ psname +.EE +.RE +. +which says that the PostScript name of the font is +.IR psname . +. +. +.P +A font description file may also contain a directive +. +.RS +.EX +.RI encoding\~ enc-file +.EE +.RE +. +which says that +the PostScript font should be reencoded using the encoding described in +.IR enc-file ; +this file should consist of a sequence of lines of the form +. +. +.RS +.EX +.I pschar code +.EE +.RE +. +where +.I pschar +is the PostScript name of the character, +and +.I code +is its position in the encoding expressed as a decimal integer; +valid values are in the range 0 to\~255. +. +Lines starting with +.B # +and blank lines are ignored. +. +The code for each character given in the font description file must +correspond to the code for the character in encoding file, +or to the code in the default encoding for the font if the PostScript +font is not to be reencoded. +. +This code can be used with the +.B \[rs]N +escape sequence in +.I @g@troff +to select the character, +even if it does not have a +.I groff +glyph name. +. +Every character in the font description file must exist in the +PostScript font, +and the widths given in the font description file must match the widths +used in the PostScript font. +. +.I grops +assumes that a character with a +.I groff +name of +.B space +is blank +(makes no marks on the page); +it can make use of such a character to generate more efficient and +compact PostScript output. +. +. +.P +.I grops +is able to display all glyphs in a PostScript font; +it is not limited to 256 of them. +. +.I enc-file +(or the default encoding if no encoding file is specified) +just defines the +order of glyphs for the first 256 characters; +all other glyphs are accessed with additional encoding vectors which +.I grops +produces on the fly. +. +. +.P +.I grops +can embed fonts in a document that are necessary to render it; +this is called \[lq]downloading\[rq]. +. +Such fonts must be in PFA format. +. +Use +.MR pfbtops @MAN1EXT@ +to convert a Type\~1 font in PFB format. +. +Downloadable fonts must be listed a +.I download +file containing lines of the form +. +.RS +.EX +.I psname file +.EE +.RE +. +where +.I psname +is the PostScript name of the font, +and +.I file +is the name of the file containing it; +lines beginning with +.B # +and blank lines are ignored; +fields may be separated by tabs or spaces. +. +.I file +is sought using the same mechanism as that for +.I groff +font description files. +. +The +.I download +file itself is also sought using this mechanism; +currently, +only the first matching file found in the device and font description +search path is used. +. +. +.P +If the file containing a downloadable font or imported document +conforms to the Adobe Document Structuring Conventions, +then +.I grops +interprets any comments in the files sufficiently to ensure that its +own output is conforming. +. +It also supplies any needed font resources that are listed in the +.I download +file +as well as any needed file resources. +. +It is also able to handle inter-resource dependencies. +. +For example, +suppose that you have a downloadable font called Garamond, +and also a downloadable font called Garamond-Outline which depends on +Garamond +(typically it would be defined to copy Garamond's font dictionary, +and change the PaintType), +then it is necessary for Garamond to appear before Garamond-Outline in +the PostScript document. +. +.I grops +handles this automatically provided that the downloadable font file +for Garamond-Outline indicates its dependence on Garamond by means of +the Document Structuring Conventions, +for example by beginning with the following lines. +. +.RS +.EX +%!PS\-Adobe\-3.0 Resource\-Font +%%DocumentNeededResources: font Garamond +%%EndComments +%%IncludeResource: font Garamond +.EE +.RE +. +In this case, +both Garamond and Garamond-Outline would need to be listed +in the +.I download +file. +. +A downloadable font should not include its own name in a +.B %%Document\%Supplied\%Resources +comment. +. +. +.P +.I grops +does not interpret +.B %%Document\%Fonts +comments. +. +The +.BR %%Document\%Needed\%Resources , +.BR %%Document\%Supplied\%Resources , +.BR %%Include\%Resource , +.BR %%Begin\%Resource , +and +.B %%End\%Resource +comments +(or possibly the old +.BR %%Document\%Needed\%Fonts , +.BR %%Document\%Supplied\%Fonts , +.BR %%Include\%Font , +.BR %%Begin\%Font , +and +.B %%End\%Font +comments) +should be used. +. +. +.P +The default stroke and fill color is black. +. +For colors defined in the \[lq]rgb\[rq] color space, +.B setrgbcolor +is used; +for \[lq]cmy\[rq] and \[lq]cmyk\[rq], +.BR setcmykcolor ; +and for \[lq]gray\[rq], +.BR setgray . +. +.B setcmykcolor +is a PostScript \%LanguageLevel\~2 command and thus not available on +some older printers. +. +. +.\" ==================================================================== +.SS Typefaces +.\" ==================================================================== +. +.P +Styles called +.BR R , +.BR I , +.BR B , +and +.B BI +mounted at font positions 1 to\~4. +. +Text fonts are grouped into families +.BR A , +.BR BM , +.BR C , +.BR H , +.BR HN , +.BR N , +.BR P , +.RB and\~ T , +each having members in each of these styles. +. +. +.RS +.TP +.B AR +.FT AR +AvantGarde-Book +.FT +. +.TQ +.B AI +.FT AI +AvantGarde-BookOblique +.FT +. +.TQ +.B AB +.FT AB +AvantGarde-Demi +.FT +. +.TQ +.B ABI +.FT ABI +AvantGarde-DemiOblique +.FT +. +.TQ +.B BMR +.FT BMR +Bookman-Light +.FT +. +.TQ +.B BMI +.FT BMI +Bookman-LightItalic +.FT +. +.TQ +.B BMB +.FT BMB +Bookman-Demi +.FT +. +.TQ +.B BMBI +.FT BMBI +Bookman-DemiItalic +.FT +. +.TQ +.B CR +.FT CR +Courier +.FT +. +.TQ +.B CI +.FT CI +Courier-Oblique +.FT +. +.TQ +.B CB +.FT CB +Courier-Bold +.FT +. +.TQ +.B CBI +.FT CBI +Courier-BoldOblique +.FT +. +.TQ +.B HR +.FT HR +Helvetica +.FT +. +.TQ +.B HI +.FT HI +Helvetica-Oblique +.FT +. +.TQ +.B HB +.FT HB +Helvetica-Bold +.FT +. +.TQ +.B HBI +.FT HBI +Helvetica-BoldOblique +.FT +. +.TQ +.B HNR +.FT HNR +Helvetica-Narrow +.FT +. +.TQ +.B HNI +.FT HNI +Helvetica-Narrow-Oblique +.FT +. +.TQ +.B HNB +.FT HNB +Helvetica-Narrow-Bold +.FT +. +.TQ +.B HNBI +.FT HNBI +Helvetica-Narrow-BoldOblique +.FT +. +.TQ +.B NR +.FT NR +NewCenturySchlbk-Roman +.FT +. +.TQ +.B NI +.FT NI +NewCenturySchlbk-Italic +.FT +. +.TQ +.B NB +.FT NB +NewCenturySchlbk-Bold +.FT +. +.TQ +.B NBI +.FT NBI +NewCenturySchlbk-BoldItalic +.FT +. +.TQ +.B PR +.FT PR +Palatino-Roman +.FT +. +.TQ +.B PI +.FT PI +Palatino-Italic +.FT +. +.TQ +.B PB +.FT PB +Palatino-Bold +.FT +. +.TQ +.B PBI +.FT PBI +Palatino-BoldItalic +.FT +. +.TQ +.B TR +.FT TR +Times-Roman +.FT +. +.TQ +.B TI +.FT TI +Times-Italic +.FT +. +.TQ +.B TB +.FT TB +Times-Bold +.FT +. +.TQ +.B TBI +.FT TBI +Times-BoldItalic +.FT +.RE +. +. +.br +.ne 2v +.P +Another text font is not a member of a family. +. +. +.RS +.TP +.B ZCMI +.FT ZCMI +ZapfChancery-MediumItalic +.FT +.RE +. +. +.P +Special fonts include +.BR S , +the PostScript Symbol font; +.BR ZD , +Zapf Dingbats; +.B SS +(slanted symbol), +which contains oblique forms of lowercase Greek letters derived from +Symbol; +.BR EURO , +which offers a Euro glyph for use with old devices lacking it; +and +.BR ZDR , +a reversed version of ZapfDingbats +(with symbols flipped about the vertical axis). +. +Most glyphs in these fonts are unnamed and must be accessed using +.BR \[rs]N . +. +The last three are not standard PostScript fonts, +but supplied by +.I groff +and therefore included in the default +.I download +file. +. +. +.\" ==================================================================== +.SS "Device control commands" +.\" ==================================================================== +. +.I grops +recognizes device control commands produced by the +.B \[rs]X +escape sequence, +but interprets only those that begin with a +.RB \[lq] ps: \[rq] +tag. +. +. +.TP +.BI "\[rs]X\[aq]ps: exec\~" code \[aq] +.RS +Execute the arbitrary PostScript commands +.IR code . +. +The PostScript +.I \%currentpoint +is set to the +.I groff +drawing position when the +.B \[rs]X +escape sequence is interpreted before executing +.IR code . +. +The origin is at the top left corner of the page; +.IR x \~coordinates +increase to the right, +and +.IR y \~coordinates +down the page. +. +A +.RB procedure\~ u +is defined that converts +.I groff +basic units to the coordinate system in effect +(provided the user doesn't change the scale). +. +For example, +. +.RS +.EX +\&.nr x 1i +\[rs]X\[aq]ps: exec \[rs]nx u 0 rlineto stroke\[aq] +.EE +.RE +. +draws a horizontal line one inch long. +. +.I code +may make changes to the graphics state, +but any changes persist only to the end of the page. +. +A dictionary containing the definitions specified by the +.B def +and +.B mdef +commands is on top of the dictionary stack. +. +If your code adds definitions to this dictionary, +you should allocate space for them using +.RB \[lq] "\[rs]X\[aq]ps: mdef\~" +.IB n \[aq]\c +\[rq]. +. +Any definitions persist only until the end of the page. +. +If you use the +.B \[rs]Y +escape sequence with an argument that names a macro, +.I code +can extend over multiple lines. +. +For example, +. +.RS +.EX +\&.nr x 1i +\&.de y +\&ps: exec +\&\[rs]nx u 0 rlineto +\&stroke +\&.. +\&\[rs]Yy +.EE +.RE +. +is another way to draw a horizontal line one inch long. +. +The single backslash before +.RB \[lq] nx \[rq]\[em]the +only reason to use a register while defining the macro +.RB \[lq] y \[rq]\[em]is +to convert a user-specified dimension +.RB \[lq] 1i \[rq] +to +.I groff +basic units which are in turn converted to PostScript units with the +.B u +procedure. +. +. +.P +.I grops +wraps user-specified PostScript code into a dictionary, +nothing more. +. +In particular, +it doesn't start and end the inserted code with +.B save +and +.BR restore , +respectively. +. +This must be supplied by the user, +if necessary. +.RE +. +. +.TP +.BI "\[rs]X\[aq]ps: file\~" name \[aq] +This is the same as the +.B exec +command except that the PostScript code is read from file +.IR name . +. +. +.TP +.BI "\[rs]X\[aq]ps: def\~" code \[aq] +Place a PostScript definition contained in +.I code +in the prologue. +. +There should be at most one definition per +.B \[rs]X +command. +. +Long definitions can be split over several +.B \[rs]X +commands; +all the +.I code +arguments are simply joined together separated by newlines. +. +The definitions are placed in a dictionary which is automatically +pushed on the dictionary stack when an +.B exec +command is executed. +. +If you use the +.B \[rs]Y +escape sequence with an argument that names a macro, +.I code +can extend over multiple lines. +. +. +.TP +.BI "\[rs]X\[aq]ps: mdef\~" "n code" \[aq] +Like +.BR def , +except that +.I code +may contain up to +.IR n \~definitions. +. +.I grops +needs to know how many definitions +.I code +contains +so that it can create an appropriately sized PostScript dictionary +to contain them. +. +. +.TP +.BI "\[rs]X\[aq]ps: import\~" "file llx lly urx ury width\~"\c +.RI [ height ]\c +.B \[aq] +Import a PostScript graphic from +.IR file . +. +The arguments +.IR llx , +.IR lly , +.IR urx , +and +.I ury +give the bounding box of the graphic in the default PostScript +coordinate system. +. +They should all be integers: +.I llx +and +.I lly +are the +.I x +and +.IR y \~coordinates +of the lower left corner of the graphic; +.I urx +and +.I ury +are the +.I x +and +.IR y \~coordinates +of the upper right corner of the graphic; +.I width +and +.I height +are integers that give the desired width and height in +.I groff +basic units of the graphic. +. +. +.IP +The graphic is scaled so that it has this width and height +and translated so that the lower left corner of the graphic is +located at the position associated with +.B \[rs]X +command. +. +If the height argument is omitted it is scaled uniformly in the +.I x +and +.IR y \~axes +so that it has the specified width. +. +. +.IP +The contents of the +.B \[rs]X +command are not interpreted by +.IR @g@troff , +so vertical space for the graphic is not automatically added, +and the +.I width +and +.I height +arguments are not allowed to have attached scaling indicators. +. +. +.IP +If the PostScript file complies with the Adobe Document Structuring +Conventions and contains a +.B %%Bounding\%Box +comment, +then the bounding box can be automatically extracted from within +.I groff +input by using the +.B psbb +request. +. +. +.IP +See +.MR groff_tmac @MAN5EXT@ +for a description of the +.B PSPIC +macro which provides a convenient high-level interface for inclusion of +PostScript graphics. +. +. +.TP +.B \[rs]X\[aq]ps: invis\[aq] +.TQ +.B \[rs]X\[aq]ps: endinvis\[aq] +No output is generated for text and drawing commands +that are bracketed with these +.B \[rs]X +commands. +. +These commands are intended for use when output from +.I @g@troff +is previewed before being processed with +.IR grops ; +if the previewer is unable to display certain characters +or other constructs, +then other substitute characters or constructs can be used for +previewing by bracketing them with these +.B \[rs]X +commands. +. +. +.RS +.P +For example, +.I \%gxditview +is not able to display a proper +.B \[rs][em] +character because the standard X11 fonts do not provide it; +this problem can be overcome by executing the following +request +. +. +.IP +.EX +\&.char \[rs][em] \[rs]X\[aq]ps: invis\[aq]\[rs] +\[rs]Z\[aq]\[rs]v\[aq]-.25m\[aq]\[rs]h\[aq].05m\[aq]\c +\[rs]D\[aq]l .9m 0\[aq]\[rs]h\[aq].05m\[aq]\[aq]\[rs] +\[rs]X\[aq]ps: endinvis\[aq]\[rs][em] +.EE +. +. +.P +In this case, +.I \%gxditview +is unable to display the +.B \[rs][em] +character and draws the line, +whereas +.I grops +prints the +.B \[rs][em] +character +and ignores the line +(this code is already in file +.IR Xps.tmac , +which is loaded if a document intended for +.I grops +is previewed with +.IR \%gxditview ). +.RE +. +. +.P +If a PostScript procedure +.B BPhook +has been defined via a +.RB \[lq] "ps: def" \[rq] +or +.RB \[lq] "ps: mdef" \[rq] +device control command, +it is executed at the beginning of every page +(before anything is drawn or written by +.IR groff ). +. +For example, +to underlay the page contents with the word \[lq]DRAFT\[rq] in light +gray, +you might use +. +. +.RS +.P +.EX +\&.de XX +ps: def +/BPhook +{ gsave .9 setgray clippath pathbbox exch 2 copy + .5 mul exch .5 mul translate atan rotate pop pop + /NewCenturySchlbk-Roman findfont 200 scalefont setfont + (DRAFT) dup stringwidth pop \-.5 mul \-70 moveto show + grestore } +def +\&.. +\&.devicem XX +.EE +.RE +. +. +.P +Or, +to cause lines and polygons to be drawn with square linecaps and mitered +linejoins instead of the round linecaps and linejoins normally used by +.IR grops , +use +. +.RS +.EX +\&.de XX +ps: def +/BPhook { 2 setlinecap 0 setlinejoin } def +\&.. +\&.devicem XX +.EE +.RE +. +(square linecaps, +as opposed to butt linecaps +.RB (\[lq] "0 setlinecap" \[rq]), +give true corners in boxed tables even though the lines are drawn +unconnected). +. +. +.\" ==================================================================== +.SS "Encapsulated PostScript" +.\" ==================================================================== +. +.I grops +itself doesn't emit bounding box information. +. +The following script, +.IR groff2eps , +produces an EPS file. +. +. +.RS +.P +.EX +#! /bin/sh +groff \-P\-b16 "$1" > "$1".ps +gs \-dNOPAUSE \-sDEVICE=bbox \-\- "$1".ps 2> "$1".bbox +sed \-e "/\[ha]%%Orientation/r $1.bbox" \[rs] + \-e "/\[ha]%!PS\-Adobe\-3.0/s/$/ EPSF\-3.0/" "$1".ps > "$1".eps +rm "$1".ps "$1".bbox +.EE +.RE +. +. +.P +You can then use +.RB \[lq] "groff2eps foo" \[rq] +to convert file +.I foo +to +.IR foo.eps . +. +. +.\" ==================================================================== +.SS "TrueType and other font formats" +.\" ==================================================================== +. +TrueType fonts can be used with +.I grops +if converted first to Type\~42 format, +a PostScript wrapper equivalent to the PFA format described in +.MR pfbtops @MAN1EXT@ . +. +Several methods exist to generate a Type\~42 wrapper; +some of them involve the use of a PostScript interpreter such as +Ghostscript\[em]see +.MR gs 1 . +. +. +.P +One approach is to use +.UR https://fontforge.org/ +FontForge +.UE , +a font editor that can convert most outline font formats. +. +Here's an example of using the Roboto Slab Serif font with +.IR groff . +. +Several variables are used so that you can more easily adapt it into +your own script. +. +. +.RS 4 +.P +.EX +MAP=@FONTDIR@/devps/generate/text.map +TTF=/usr/share/fonts/truetype/roboto/slab/RobotoSlab\-Regular.ttf +BASE=$(basename \[dq]$TTF\[dq]) +INT=${BASE%.ttf} +PFA=$INT.pfa +AFM=$INT.afm +GFN=RSR +DIR=$HOME/.local/groff/font +mkdir \-p \[dq]$DIR\[dq]/devps +fontforge \-lang=ff \-c \[dq]Open(\[rs]\[dq]$TTF\[rs]\[dq]);\[rs] +\tGenerate(\[rs]\[dq]$DIR/devps/$PFA\[rs]\[dq]);\[dq] +afmtodit \[dq]$DIR/devps/$AFM\[dq] \[dq]$MAP\[dq] \ +\[dq]$DIR/devps/$GFN\[dq] +printf \[dq]$BASE\[rs]t$PFA\[rs]n\[dq] >> \[dq]$DIR/devps/download\[dq] +.EE +.RE +. +. +.P +.I fontforge +and +.I afmtodit +may generate warnings depending on the attributes of the font. +. +The test procedure is simple. +. +. +.RS 4 +.P +.EX +printf \[dq].ft RSR\[rs]nHello, world!\[rs]n\[dq] | groff \-F \ +\[dq]$DIR\[dq] > hello.ps +.EE +.RE +. +. +.P +Once you're satisfied that the font works, +you may want to generate any available related styles +(for instance, +Roboto Slab +also has \[lq]Bold\[rq], +\[lq]Light\[rq], +and +\[lq]Thin\[rq] +styles) +and set up +.I GROFF_FONT_PATH +in your environment to include the directory you keep the generated +fonts in so that you don't have to use the +.B \-F +option. +. +. +.\" ==================================================================== +.SH "Font installation" +.\" ==================================================================== +. +The following is a step-by-step font installation guide for +.I grops. +. +. +.IP \[bu] 2n +Convert your font to something +.I groff +understands. +. +This is a PostScript Type\~1 font in PFA format or a PostScript +Type\~42 font, +together with an AFM file. +. +A PFA file begins as follows. +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +%!PS\-AdobeFont\-1.0: +.EE +.RE +. +A PFB file contains this string as well, +preceded by some non-printing bytes. +. +If your font is in PFB format, +use +.IR groff 's +.MR pfbtops @MAN1EXT@ +program to convert it to PFA. +. +For TrueType and other font formats, +we recommend +.IR fontforge , +which can convert most outline font formats. +. +A Type\~42 font file begins as follows. +. +.RS +.EX +%!PS\-TrueTypeFont +.EE +.RE +. +This is a wrapper format for TrueType fonts. +. +Old PostScript printers might not support them +(that is, +they might not have a built-in TrueType font interpreter). +. +In the following steps, +we will consider the use of CTAN's +.UR https://\:ctan.org/\:tex\-archive/\:fonts/\:brushscr +BrushScriptX-Italic +.UE +font in PFA format. +.RE \" now restore left margin +. +. +.IP \[bu] +Convert the AFM file to a +.I groff +font description file with the +.MR afmtodit @MAN1EXT@ +program. +. +For instance, +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +$ \c +.B afmtodit BrushScriptX\-Italic.afm text.map BSI +.EE +.RE +. +converts the Adobe Font Metric file +.I BrushScriptX\-Italic.afm +to the +.I groff +font description file +.IR BSI . +.RE \" now restore left margin +. +. +.IP +If you have a font family which provides regular upright (roman), +bold, +italic, +and +bold-italic styles +(where \[lq]italic\[rq] may be \[lq]oblique\[rq] or \[lq]slanted\[rq]), +we recommend using the letters +.BR R , +.BR B , +.BR I , +and +.BR BI , +respectively, +as suffixes to the +.I groff +font family name to enable +.IR groff 's +font family and style selection features. +. +An example is +.IR groff 's +built-in support for Times: +the font family +name is abbreviated as +.BR T , +and the +.I groff +font names are therefore +.BR TR , +.BR TB , +.BR TI , +and +.BR TBI . +. +In our example, +however, +the BrushScriptX font is available in a single style only, +italic. +. +. +.IP \[bu] +Install the +.I groff +font description file(s) in a +.I devps +subdirectory in the search path that +.I groff +uses for device and font file descriptions. +. +See the +.I GROFF_FONT_PATH +entry in section \[lq]Environment\[rq] of +.MR @g@troff @MAN1EXT@ +for the current value of the font search path. +. +While +.I groff +doesn't directly use AFM files, +it is a good idea to store them alongside its font description files. +. +. +.IP \[bu] +Register fonts in the +.I devps/download +file so they can be located for embedding in PostScript files +.I grops +generates. +. +Only the first +.I download +file encountered in the font search path is read. +. +If in doubt, +copy the default +.I download +file +(see section \[lq]Files\[rq] below) +to the first directory in the font search path and add your fonts there. +. +The PostScript font name used by +.I grops +is stored in the +.B internalname +field in the +.I groff +font description file. +. +(This name does not necessarily resemble the font's file name.) +. +We add the following line to +.IR download . +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +BrushScriptX\-Italic\[->]BrushScriptX\-Italic.pfa +.EE +.RE \" but only one to get back to it +. +A tab character, +depicted as \[->], +separates the fields. +.RE \" now restore left margin +. +. +.IP \[bu] +Test the selection and embedding of the new font. +. +.RS +.RS \" two RS calls to get inboard of IP indentation +.EX +printf "\[rs]\[rs]f[BSI]Hello, world!\[rs]n" \ +| groff \-T ps \-P \-e >hello.ps +see hello.pdf +.EE +.RE +.RE \" now restore left margin +. +. +.\" ==================================================================== +.SH "Old fonts" +.\" ==================================================================== +. +.I groff +versions 1.19.2 and earlier contained descriptions of a slightly +different set of the base 35 PostScript level 2 fonts defined by Adobe. +. +The older set has 229 glyphs and a larger set of kerning pairs; +the newer one has 314 glyphs and includes the Euro glyph. +. +For backwards compatibility, +these old font descriptions are also installed in the +.I @OLDFONTDIR@/\:\%devps +directory. +. +. +.P +To use them, +make sure that +.I grops +finds the fonts before the default system fonts +(with the same names): +either give +.I grops +the +.B \-F +command-line option, +. +.RS +.EX +$ \c +.B groff \-Tps \-P\-F \-P@OLDFONTDIR@ \c +\&.\|.\|. +.EE +.RE +. +or add the directory to +.IR groff 's +font and device description search path environment variable, +. +.RS +.EX +$ \c +.B GROFF_FONT_PATH=\:@OLDFONTDIR@ \[rs] +.RS +.B groff \-Tps \c +\&.\|.\|. +.RE +.EE +.RE +. +when the command runs. +. +. +.br +.ne 3v +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +A list of directories in which to seek the selected output device's +directory of device and font description files. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.TP +.I GROPS_PROLOGUE +If this is set to +.IR foo , +then +.I grops +uses the file +.I foo +(in the font path) instead of the default prologue file +.IR prologue . +. +The option +.B \-P +overrides this environment variable. +. +. +.TP +.I SOURCE_DATE_EPOCH +A timestamp +(expressed as seconds since the Unix epoch) +to use as the output creation timestamp in place of the current time. +. +The time is converted to human-readable form using +.MR ctime 3 +and recorded in a PostScript comment. +. +. +.TP +.I TZ +The time zone to use when converting the current time +(or value of +.IR SOURCE_DATE_EPOCH ) +to human-readable form; +see +.MR tzset 3 . +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @FONTDIR@/\:\%devps/\:DESC +describes the +.B ps +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devps/ F +describes the font known +.RI as\~ F +on device +.BR ps . +. +. +.TP +.I @FONTDIR@/\:\%devps/\:\%download +lists fonts available for embedding within the PostScript document +(or download to the device). +. +. +.TP +.I @FONTDIR@/\:\%devps/\:\%prologue +is the default PostScript prologue prefixed to every output file. +. +. +.TP +.I @FONTDIR@/\:\%devps/\:text.enc +describes the encoding scheme used by most PostScript Type\~1 fonts; +the +.B \%encoding +directive of +font description files for the +.B ps +device refers to it. +. +. +.TP +.I @MACRODIR@/\:ps.tmac +defines macros for use with the +.B ps +output device. +. +It is automatically loaded by +.I troffrc +when the +.B ps +output device is selected. +. +. +.TP +.I @MACRODIR@/\:pspic.tmac +defines the +.B PSPIC +macro for embedding images in a document; +see +.MR groff_tmac @MAN5EXT@ . +. +It is automatically loaded by +.I troffrc. +. +. +.TP +.I @MACRODIR@/psold.tmac +provides replacement glyphs for text fonts that lack complete coverage +of the ISO Latin-1 character set; +using it, +.I groff +can produce glyphs like eth (\[Sd]) and thorn (\[Tp]) that older +PostScript printers do not natively support. +. +. +.P +.I grops +creates temporary files using the template +.RI \[lq] grops XXXXXX\[rq]; +see +.MR groff @MAN1EXT@ +for details on their storage location. +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.UR http://\:partners\:.adobe\:.com/\:public/\:developer/\:en/\:ps/\ +\:5001\:.DSC_Spec\:.pdf +PostScript Language Document Structuring Conventions Specification +.UE +. +. +.P +.MR afmtodit @MAN1EXT@ , +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ , +.MR pfbtops @MAN1EXT@ , +.MR groff_char @MAN7EXT@ , +.MR groff_font @MAN5EXT@ , +.MR groff_out @MAN5EXT@ , +.MR groff_tmac @MAN5EXT@ +. +. +.\" Clean up. +.rm FT +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_grops_1_man_C] +.do rr *groff_grops_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/grops/grops.am b/src/devices/grops/grops.am new file mode 100644 index 0000000..cb5532a --- /dev/null +++ b/src/devices/grops/grops.am @@ -0,0 +1,38 @@ +# Copyright (C) 2014-2020 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +bin_PROGRAMS += grops +grops_SOURCES = \ + src/devices/grops/ps.cpp \ + src/devices/grops/psrm.cpp \ + src/devices/grops/ps.h +grops_LDADD = $(LIBM) \ + libdriver.a \ + libgroff.a \ + lib/libgnu.a +man1_MANS += src/devices/grops/grops.1 +EXTRA_DIST += \ + src/devices/grops/grops.1.man \ + src/devices/grops/psfig.diff \ + src/devices/grops/TODO + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/grops/ps.cpp b/src/devices/grops/ps.cpp new file mode 100644 index 0000000..807945f --- /dev/null +++ b/src/devices/grops/ps.cpp @@ -0,0 +1,1894 @@ +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +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 <http://www.gnu.org/licenses/>. */ + +/* + * PostScript documentation: + * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf + * http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf + */ + +#include "lib.h" // PI +#include "driver.h" +#include "stringclass.h" +#include "cset.h" +#include "nonposix.h" +#include "paper.h" +#include "curtime.h" + +#include "ps.h" +#include <time.h> + +#ifdef NEED_DECLARATION_PUTENV +extern "C" { + int putenv(const char *); +} +#endif /* NEED_DECLARATION_PUTENV */ + +extern "C" const char *Version_string; + +// search path defaults to the current directory +search_path include_search_path(0, 0, 0, 1); + +static int landscape_flag = 0; +static int manual_feed_flag = 0; +static int ncopies = 1; +static int linewidth = -1; +// Non-zero means generate PostScript code that guesses the paper +// length using the imageable area. +static int guess_flag = 0; +static double user_paper_length = 0; +static double user_paper_width = 0; + +// Non-zero if -b was specified on the command line. +static int bflag = 0; +unsigned broken_flags = 0; + +// Non-zero means we need the CMYK extension for PostScript Level 1 +static int cmyk_flag = 0; + +#define DEFAULT_LINEWIDTH 40 /* in ems/1000 */ +#define MAX_LINE_LENGTH 72 +#define FILL_MAX 1000 + +const char *const dict_name = "grops"; +const char *const defs_dict_name = "DEFS"; +const int DEFS_DICT_SPARE = 50; + +double degrees(double r) +{ + return r*180.0/PI; +} + +double radians(double d) +{ + return d*PI/180.0; +} + +// This is used for testing whether a character should be output in the +// PostScript file using \nnn, so we really want the character to be +// less than 0200. + +inline int is_ascii(char c) +{ + return (unsigned char)c < 0200; +} + +ps_output::ps_output(FILE *f, int n) +: fp(f), col(0), max_line_length(n), need_space(0), fixed_point(0) +{ +} + +ps_output &ps_output::set_file(FILE *f) +{ + fp = f; + col = 0; + return *this; +} + +ps_output &ps_output::copy_file(FILE *infp) +{ + int c; + while ((c = getc(infp)) != EOF) + putc(c, fp); + return *this; +} + +ps_output &ps_output::end_line() +{ + if (col != 0) { + putc('\n', fp); + col = 0; + need_space = 0; + } + return *this; +} + +ps_output &ps_output::special(const char *s) +{ + if (s == 0 || *s == '\0') + return *this; + if (col != 0) { + putc('\n', fp); + col = 0; + } + fputs(s, fp); + if (strchr(s, '\0')[-1] != '\n') + putc('\n', fp); + need_space = 0; + return *this; +} + +ps_output &ps_output::simple_comment(const char *s) +{ + if (col != 0) + putc('\n', fp); + putc('%', fp); + putc('%', fp); + fputs(s, fp); + putc('\n', fp); + col = 0; + need_space = 0; + return *this; +} + +ps_output &ps_output::begin_comment(const char *s) +{ + if (col != 0) + putc('\n', fp); + putc('%', fp); + putc('%', fp); + fputs(s, fp); + col = 2 + strlen(s); + return *this; +} + +ps_output &ps_output::end_comment() +{ + if (col != 0) { + putc('\n', fp); + col = 0; + } + need_space = 0; + return *this; +} + +ps_output &ps_output::comment_arg(const char *s) +{ + int len = strlen(s); + if (col + len + 1 > max_line_length) { + putc('\n', fp); + fputs("%%+", fp); + col = 3; + } + putc(' ', fp); + fputs(s, fp); + col += len + 1; + return *this; +} + +ps_output &ps_output::set_fixed_point(int n) +{ + assert(n >= 0 && n <= 10); + fixed_point = n; + return *this; +} + +ps_output &ps_output::put_delimiter(char c) +{ + if (col + 1 > max_line_length) { + putc('\n', fp); + col = 0; + } + putc(c, fp); + col++; + need_space = 0; + return *this; +} + +ps_output &ps_output::put_string(const char *s, int n) +{ + int len = 0; + int i; + for (i = 0; i < n; i++) { + char c = s[i]; + if (is_ascii(c) && csprint(c)) { + if (c == '(' || c == ')' || c == '\\') + len += 2; + else + len += 1; + } + else + len += 4; + } + if (len > n*2) { + if (col + n*2 + 2 > max_line_length && n*2 + 2 <= max_line_length) { + putc('\n', fp); + col = 0; + } + if (col + 1 > max_line_length) { + putc('\n', fp); + col = 0; + } + putc('<', fp); + col++; + for (i = 0; i < n; i++) { + if (col + 2 > max_line_length) { + putc('\n', fp); + col = 0; + } + fprintf(fp, "%02x", s[i] & 0377); + col += 2; + } + putc('>', fp); + col++; + } + else { + if (col + len + 2 > max_line_length && len + 2 <= max_line_length) { + putc('\n', fp); + col = 0; + } + if (col + 2 > max_line_length) { + putc('\n', fp); + col = 0; + } + putc('(', fp); + col++; + for (i = 0; i < n; i++) { + char c = s[i]; + if (is_ascii(c) && csprint(c)) { + if (c == '(' || c == ')' || c == '\\') + len = 2; + else + len = 1; + } + else + len = 4; + if (col + len + 1 > max_line_length) { + putc('\\', fp); + putc('\n', fp); + col = 0; + } + switch (len) { + case 1: + putc(c, fp); + break; + case 2: + putc('\\', fp); + putc(c, fp); + break; + case 4: + fprintf(fp, "\\%03o", c & 0377); + break; + default: + assert(0); + } + col += len; + } + putc(')', fp); + col++; + } + need_space = 0; + return *this; +} + +ps_output &ps_output::put_number(int n) +{ + char buf[1 + INT_DIGITS + 1]; + sprintf(buf, "%d", n); + int len = strlen(buf); + if (col > 0 && col + len + need_space > max_line_length) { + putc('\n', fp); + col = 0; + need_space = 0; + } + if (need_space) { + putc(' ', fp); + col++; + } + fputs(buf, fp); + col += len; + need_space = 1; + return *this; +} + +ps_output &ps_output::put_fix_number(int i) +{ + const char *p = if_to_a(i, fixed_point); + int len = strlen(p); + if (col > 0 && col + len + need_space > max_line_length) { + putc('\n', fp); + col = 0; + need_space = 0; + } + if (need_space) { + putc(' ', fp); + col++; + } + fputs(p, fp); + col += len; + need_space = 1; + return *this; +} + +ps_output &ps_output::put_float(double d) +{ + char buf[128]; + sprintf(buf, "%.4f", d); + int last = strlen(buf) - 1; + while (buf[last] == '0') + last--; + if (buf[last] == '.') + last--; + buf[++last] = '\0'; + if (col > 0 && col + last + need_space > max_line_length) { + putc('\n', fp); + col = 0; + need_space = 0; + } + if (need_space) { + putc(' ', fp); + col++; + } + fputs(buf, fp); + col += last; + need_space = 1; + return *this; +} + +ps_output &ps_output::put_symbol(const char *s) +{ + int len = strlen(s); + if (col > 0 && col + len + need_space > max_line_length) { + putc('\n', fp); + col = 0; + need_space = 0; + } + if (need_space) { + putc(' ', fp); + col++; + } + fputs(s, fp); + col += len; + need_space = 1; + return *this; +} + +ps_output &ps_output::put_color(unsigned int c) +{ + char buf[128]; + sprintf(buf, "%.3g", double(c) / double(color::MAX_COLOR_VAL)); + int len = strlen(buf); + if (col > 0 && col + len + need_space > max_line_length) { + putc('\n', fp); + col = 0; + need_space = 0; + } + if (need_space) { + putc(' ', fp); + col++; + } + fputs(buf, fp); + col += len; + need_space = 1; + return *this; +} + +ps_output &ps_output::put_literal_symbol(const char *s) +{ + int len = strlen(s); + if (col > 0 && col + len + 1 > max_line_length) { + putc('\n', fp); + col = 0; + } + putc('/', fp); + fputs(s, fp); + col += len + 1; + need_space = 1; + return *this; +} + +class ps_font : public font { + ps_font(const char *); +public: + int encoding_index; + char *encoding; + char *reencoded_name; + ~ps_font(); + void handle_unknown_font_command(const char *command, const char *arg, + const char *filename, int lineno); + static ps_font *load_ps_font(const char *); +}; + +ps_font *ps_font::load_ps_font(const char *s) +{ + ps_font *f = new ps_font(s); + if (!f->load()) { + delete f; + return 0; + } + return f; +} + +ps_font::ps_font(const char *nm) +: font(nm), encoding_index(-1), encoding(0), reencoded_name(0) +{ +} + +ps_font::~ps_font() +{ + free(encoding); + delete[] reencoded_name; +} + +void ps_font::handle_unknown_font_command(const char *command, const char *arg, + const char *filename, int lineno) +{ + if (strcmp(command, "encoding") == 0) { + if (arg == 0) + error_with_file_and_line(filename, lineno, + "'encoding' command requires an argument"); + else + encoding = strsave(arg); + } +} + +static void handle_unknown_desc_command(const char *command, const char *arg, + const char *filename, int lineno) +{ + if (strcmp(command, "broken") == 0) { + if (arg == 0) + error_with_file_and_line(filename, lineno, + "'broken' command requires an argument"); + else if (!bflag) + broken_flags = atoi(arg); + } +} + +struct subencoding { + font *p; + unsigned int num; + int idx; + char *subfont; + const char *glyphs[256]; + subencoding *next; + + subencoding(font *, unsigned int, int, subencoding *); + ~subencoding(); +}; + +subencoding::subencoding(font *f, unsigned int n, int ix, subencoding *s) +: p(f), num(n), idx(ix), subfont(0), next(s) +{ + for (int i = 0; i < 256; i++) + glyphs[i] = 0; +} + +subencoding::~subencoding() +{ + delete[] subfont; +} + +struct style { + font *f; + subencoding *sub; + int point_size; + int height; + int slant; + style(); + style(font *, subencoding *, int, int, int); + int operator==(const style &) const; + int operator!=(const style &) const; +}; + +style::style() : f(0) +{ +} + +style::style(font *p, subencoding *s, int sz, int h, int sl) +: f(p), sub(s), point_size(sz), height(h), slant(sl) +{ +} + +int style::operator==(const style &s) const +{ + return (f == s.f + && sub == s.sub + && point_size == s.point_size + && height == s.height + && slant == s.slant); +} + +int style::operator!=(const style &s) const +{ + return !(*this == s); +} + +class ps_printer : public printer { + FILE *tempfp; + ps_output out; + int res; + glyph *space_glyph; + int pages_output; + int paper_length; + int equalise_spaces; + enum { SBUF_SIZE = 256 }; + char sbuf[SBUF_SIZE]; + int sbuf_len; + int sbuf_start_hpos; + int sbuf_vpos; + int sbuf_end_hpos; + int sbuf_space_width; + int sbuf_space_count; + int sbuf_space_diff_count; + int sbuf_space_code; + int sbuf_kern; + style sbuf_style; + color sbuf_color; // the current PS color + style output_style; + subencoding *subencodings; + int output_hpos; + int output_vpos; + int output_draw_point_size; + int line_thickness; + int output_line_thickness; + unsigned char output_space_code; + enum { MAX_DEFINED_STYLES = 50 }; + style defined_styles[MAX_DEFINED_STYLES]; + int ndefined_styles; + int next_encoding_index; + int next_subencoding_index; + string defs; + int ndefs; + resource_manager rm; + int invis_count; + + void flush_sbuf(); + void set_style(const style &); + void set_space_code(unsigned char); + int set_encoding_index(ps_font *); + subencoding *set_subencoding(font *, glyph *, unsigned char *); + char *get_subfont(subencoding *, const char *); + void do_exec(char *, const environment *); + void do_import(char *, const environment *); + void do_def(char *, const environment *); + void do_mdef(char *, const environment *); + void do_file(char *, const environment *); + void do_invis(char *, const environment *); + void do_endinvis(char *, const environment *); + void set_line_thickness_and_color(const environment *); + void fill_path(const environment *); + void encode_fonts(); + void encode_subfont(subencoding *); + void define_encoding(const char *, int); + void reencode_font(ps_font *); + void set_color(color *, int = 0); + + const char *media_name(); + int media_width(); + int media_height(); + void media_set(); + +public: + ps_printer(double); + ~ps_printer(); + void set_char(glyph *, font *, const environment *, int, const char *); + void draw(int, int *, int, const environment *); + void begin_page(int); + void end_page(int); + void special(char *, const environment *, char); + font *make_font(const char *); + void end_of_line(); +}; + +// 'pl' is in inches +ps_printer::ps_printer(double pl) +: out(0, MAX_LINE_LENGTH), + pages_output(0), + sbuf_len(0), + subencodings(0), + output_hpos(-1), + output_vpos(-1), + line_thickness(-1), + ndefined_styles(0), + next_encoding_index(0), + next_subencoding_index(0), + ndefs(0), + invis_count(0) +{ + tempfp = xtmpfile(); + out.set_file(tempfp); + if (linewidth < 0) + linewidth = DEFAULT_LINEWIDTH; + if (font::hor != 1) + fatal("device horizontal motion quantum must be 1, got %1", + font::hor); + if (font::vert != 1) + fatal("device vertical motion quantum must be 1, got %1", + font::vert); + if (font::res % (font::sizescale*72) != 0) + fatal("device resolution must be a multiple of 72*'sizescale', got" + " %1 ('sizescale'=%2)", font::res, font::sizescale); + int r = font::res; + int point = 0; + while (r % 10 == 0) { + r /= 10; + point++; + } + res = r; + out.set_fixed_point(point); + space_glyph = name_to_glyph("space"); + if (pl == 0) + paper_length = font::paperlength; + else + paper_length = int(pl * font::res + 0.5); + if (paper_length == 0) + paper_length = 11 * font::res; + equalise_spaces = font::res >= 72000; +} + +int ps_printer::set_encoding_index(ps_font *f) +{ + if (f->encoding_index >= 0) + return f->encoding_index; + for (font_pointer_list *p = font_list; p; p = p->next) + if (p->p != f) { + char *encoding = ((ps_font *)p->p)->encoding; + int encoding_index = ((ps_font *)p->p)->encoding_index; + if (encoding != 0 && encoding_index >= 0 + && strcmp(f->encoding, encoding) == 0) { + return f->encoding_index = encoding_index; + } + } + return f->encoding_index = next_encoding_index++; +} + +subencoding *ps_printer::set_subencoding(font *f, glyph *g, + unsigned char *codep) +{ + unsigned int idx = f->get_code(g); + *codep = idx % 256; + unsigned int num = idx >> 8; + if (num == 0) + return 0; + subencoding *p = 0; + for (p = subencodings; p; p = p->next) + if (p->p == f && p->num == num) + break; + if (p == 0) + p = subencodings = new subencoding(f, num, next_subencoding_index++, + subencodings); + p->glyphs[*codep] = f->get_special_device_encoding(g); + return p; +} + +char *ps_printer::get_subfont(subencoding *sub, const char *stem) +{ + assert(sub != 0); + if (!sub->subfont) { + char *tem = new char[strlen(stem) + 2 + INT_DIGITS + 1]; + sprintf(tem, "%s@@%d", stem, sub->idx); + sub->subfont = tem; + } + return sub->subfont; +} + +void ps_printer::set_char(glyph *g, font *f, const environment *env, int w, + const char *) +{ + if (g == space_glyph || invis_count > 0) + return; + unsigned char code; + subencoding *sub = set_subencoding(f, g, &code); + style sty(f, sub, env->size, env->height, env->slant); + if (sty.slant != 0) { + if (sty.slant > 80 || sty.slant < -80) { + error("silly slant '%1' degrees", sty.slant); + sty.slant = 0; + } + } + if (sbuf_len > 0) { + if (sbuf_len < SBUF_SIZE + && sty == sbuf_style + && sbuf_vpos == env->vpos + && sbuf_color == *env->col) { + if (sbuf_end_hpos == env->hpos) { + sbuf[sbuf_len++] = code; + sbuf_end_hpos += w + sbuf_kern; + return; + } + if (sbuf_len == 1 && sbuf_kern == 0) { + sbuf_kern = env->hpos - sbuf_end_hpos; + sbuf_end_hpos = env->hpos + sbuf_kern + w; + sbuf[sbuf_len++] = code; + return; + } + /* If sbuf_end_hpos - sbuf_kern == env->hpos, we are better off + starting a new string. */ + if (sbuf_len < SBUF_SIZE - 1 && env->hpos >= sbuf_end_hpos + && (sbuf_kern == 0 || sbuf_end_hpos - sbuf_kern != env->hpos)) { + if (sbuf_space_code < 0) { + if (f->contains(space_glyph) && !sub) { + sbuf_space_code = f->get_code(space_glyph); + sbuf_space_width = env->hpos - sbuf_end_hpos; + sbuf_end_hpos = env->hpos + w + sbuf_kern; + sbuf[sbuf_len++] = sbuf_space_code; + sbuf[sbuf_len++] = code; + sbuf_space_count++; + return; + } + } + else { + int diff = env->hpos - sbuf_end_hpos - sbuf_space_width; + if (diff == 0 || (equalise_spaces && (diff == 1 || diff == -1))) { + sbuf_end_hpos = env->hpos + w + sbuf_kern; + sbuf[sbuf_len++] = sbuf_space_code; + sbuf[sbuf_len++] = code; + sbuf_space_count++; + if (diff == 1) + sbuf_space_diff_count++; + else if (diff == -1) + sbuf_space_diff_count--; + return; + } + } + } + } + flush_sbuf(); + } + sbuf_len = 1; + sbuf[0] = code; + sbuf_end_hpos = env->hpos + w; + sbuf_start_hpos = env->hpos; + sbuf_vpos = env->vpos; + sbuf_style = sty; + sbuf_space_code = -1; + sbuf_space_width = 0; + sbuf_space_count = sbuf_space_diff_count = 0; + sbuf_kern = 0; + if (sbuf_color != *env->col) + set_color(env->col); +} + +static char *make_encoding_name(int encoding_index) +{ + static char buf[3 + INT_DIGITS + 1]; + sprintf(buf, "ENC%d", encoding_index); + return buf; +} + +static char *make_subencoding_name(int subencoding_index) +{ + static char buf[6 + INT_DIGITS + 1]; + sprintf(buf, "SUBENC%d", subencoding_index); + return buf; +} + +const char *const WS = " \t\n\r"; + +void ps_printer::define_encoding(const char *encoding, int encoding_index) +{ + char *vec[256]; + int i; + for (i = 0; i < 256; i++) + vec[i] = 0; + char *path; + FILE *fp = font::open_file(encoding, &path); + if (fp == 0) + fatal("can't open encoding file '%1'", encoding); + int lineno = 1; + const int BUFFER_SIZE = 512; + char buf[BUFFER_SIZE]; + while (fgets(buf, BUFFER_SIZE, fp) != 0) { + char *p = buf; + while (csspace(*p)) + p++; + if (*p != '#' && *p != '\0' && (p = strtok(buf, WS)) != 0) { + char *q = strtok(0, WS); + int n = 0; // pacify compiler + if (q == 0 || sscanf(q, "%d", &n) != 1 || n < 0 || n >= 256) + fatal_with_file_and_line(path, lineno, "bad second field"); + vec[n] = new char[strlen(p) + 1]; + strcpy(vec[n], p); + } + lineno++; + } + free(path); + out.put_literal_symbol(make_encoding_name(encoding_index)) + .put_delimiter('['); + for (i = 0; i < 256; i++) { + if (vec[i] == 0) + out.put_literal_symbol(".notdef"); + else { + out.put_literal_symbol(vec[i]); + delete[] vec[i]; + } + } + out.put_delimiter(']') + .put_symbol("def"); + fclose(fp); +} + +void ps_printer::reencode_font(ps_font *f) +{ + out.put_literal_symbol(f->reencoded_name) + .put_symbol(make_encoding_name(f->encoding_index)) + .put_literal_symbol(f->get_internal_name()) + .put_symbol("RE"); +} + +void ps_printer::encode_fonts() +{ + if (next_encoding_index == 0) + return; + char *done_encoding = new char[next_encoding_index]; + for (int i = 0; i < next_encoding_index; i++) + done_encoding[i] = 0; + for (font_pointer_list *f = font_list; f; f = f->next) { + int encoding_index = ((ps_font *)f->p)->encoding_index; + if (encoding_index >= 0) { + assert(encoding_index < next_encoding_index); + if (!done_encoding[encoding_index]) { + done_encoding[encoding_index] = 1; + define_encoding(((ps_font *)f->p)->encoding, encoding_index); + } + reencode_font((ps_font *)f->p); + } + } + delete[] done_encoding; +} + +void ps_printer::encode_subfont(subencoding *sub) +{ + assert(sub != 0); + out.put_literal_symbol(make_subencoding_name(sub->idx)) + .put_delimiter('['); + for (int i = 0; i < 256; i++) + { + if (sub->glyphs[i]) + out.put_literal_symbol(sub->glyphs[i]); + else + out.put_literal_symbol(".notdef"); + } + out.put_delimiter(']') + .put_symbol("def"); +} + +void ps_printer::set_style(const style &sty) +{ + char buf[1 + INT_DIGITS + 1]; + for (int i = 0; i < ndefined_styles; i++) + if (sty == defined_styles[i]) { + sprintf(buf, "F%d", i); + out.put_symbol(buf); + return; + } + if (ndefined_styles >= MAX_DEFINED_STYLES) + ndefined_styles = 0; + sprintf(buf, "F%d", ndefined_styles); + out.put_literal_symbol(buf); + const char *psname = sty.f->get_internal_name(); + if (psname == 0) + fatal("no internalname specified for font '%1'", sty.f->get_name()); + char *encoding = ((ps_font *)sty.f)->encoding; + if (sty.sub == 0) { + if (encoding != 0) { + char *s = ((ps_font *)sty.f)->reencoded_name; + if (s == 0) { + int ei = set_encoding_index((ps_font *)sty.f); + char *tem = new char[strlen(psname) + 1 + INT_DIGITS + 1]; + sprintf(tem, "%s@%d", psname, ei); + psname = tem; + ((ps_font *)sty.f)->reencoded_name = tem; + } + else + psname = s; + } + } + else + psname = get_subfont(sty.sub, psname); + out.put_fix_number((font::res/(72*font::sizescale))*sty.point_size); + if (sty.height != 0 || sty.slant != 0) { + int h = sty.height == 0 ? sty.point_size : sty.height; + h *= font::res/(72*font::sizescale); + int c = int(h*tan(radians(sty.slant)) + .5); + out.put_fix_number(c) + .put_fix_number(h) + .put_literal_symbol(psname) + .put_symbol("MF"); + } + else { + out.put_literal_symbol(psname) + .put_symbol("SF"); + } + defined_styles[ndefined_styles++] = sty; +} + +void ps_printer::set_color(color *col, int fill) +{ + sbuf_color = *col; + unsigned int components[4]; + char s[3]; + color_scheme cs = col->get_components(components); + s[0] = fill ? 'F' : 'C'; + s[2] = 0; + switch (cs) { + case DEFAULT: // black + out.put_symbol("0"); + s[1] = 'g'; + break; + case RGB: + out.put_color(Red) + .put_color(Green) + .put_color(Blue); + s[1] = 'r'; + break; + case CMY: + col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black); + // fall through + case CMYK: + out.put_color(Cyan) + .put_color(Magenta) + .put_color(Yellow) + .put_color(Black); + s[1] = 'k'; + cmyk_flag = 1; + break; + case GRAY: + out.put_color(Gray); + s[1] = 'g'; + break; + } + out.put_symbol(s); +} + +void ps_printer::set_space_code(unsigned char c) +{ + out.put_literal_symbol("SC") + .put_number(c) + .put_symbol("def"); +} + +void ps_printer::end_of_line() +{ + flush_sbuf(); + // this ensures that we do an absolute motion to the beginning of a line + output_vpos = output_hpos = -1; +} + +void ps_printer::flush_sbuf() +{ + enum { + NONE, + RELATIVE_H, + RELATIVE_V, + RELATIVE_HV, + ABSOLUTE + } motion = NONE; + int space_flag = 0; + if (sbuf_len == 0) + return; + if (output_style != sbuf_style) { + set_style(sbuf_style); + output_style = sbuf_style; + } + int extra_space = 0; + if (output_hpos < 0 || output_vpos < 0) + motion = ABSOLUTE; + else { + if (output_hpos != sbuf_start_hpos) + motion = RELATIVE_H; + if (output_vpos != sbuf_vpos) { + if (motion != NONE) + motion = RELATIVE_HV; + else + motion = RELATIVE_V; + } + } + if (sbuf_space_code >= 0) { + int w = sbuf_style.f->get_width(space_glyph, sbuf_style.point_size); + if (w + sbuf_kern != sbuf_space_width) { + if (sbuf_space_code != output_space_code) { + set_space_code(sbuf_space_code); + output_space_code = sbuf_space_code; + } + space_flag = 1; + extra_space = sbuf_space_width - w - sbuf_kern; + if (sbuf_space_diff_count > sbuf_space_count/2) + extra_space++; + else if (sbuf_space_diff_count < -(sbuf_space_count/2)) + extra_space--; + } + } + if (space_flag) + out.put_fix_number(extra_space); + if (sbuf_kern != 0) + out.put_fix_number(sbuf_kern); + out.put_string(sbuf, sbuf_len); + char command_array[] = {'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T'}; + char sym[2]; + sym[0] = command_array[motion*4 + space_flag + 2*(sbuf_kern != 0)]; + sym[1] = '\0'; + switch (motion) { + case NONE: + break; + case ABSOLUTE: + out.put_fix_number(sbuf_start_hpos) + .put_fix_number(sbuf_vpos); + break; + case RELATIVE_H: + out.put_fix_number(sbuf_start_hpos - output_hpos); + break; + case RELATIVE_V: + out.put_fix_number(sbuf_vpos - output_vpos); + break; + case RELATIVE_HV: + out.put_fix_number(sbuf_start_hpos - output_hpos) + .put_fix_number(sbuf_vpos - output_vpos); + break; + default: + assert(0); + } + out.put_symbol(sym); + output_hpos = sbuf_end_hpos; + output_vpos = sbuf_vpos; + sbuf_len = 0; +} + +void ps_printer::set_line_thickness_and_color(const environment *env) +{ + if (line_thickness < 0) { + if (output_draw_point_size != env->size) { + // we ought to check for overflow here + int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000; + out.put_fix_number(lw) + .put_symbol("LW"); + output_draw_point_size = env->size; + output_line_thickness = -1; + } + } + else { + if (output_line_thickness != line_thickness) { + out.put_fix_number(line_thickness) + .put_symbol("LW"); + output_line_thickness = line_thickness; + output_draw_point_size = -1; + } + } + if (sbuf_color != *env->col) + set_color(env->col); +} + +void ps_printer::fill_path(const environment *env) +{ + if (sbuf_color == *env->fill) + out.put_symbol("FL"); + else + set_color(env->fill, 1); +} + +void ps_printer::draw(int code, int *p, int np, const environment *env) +{ + if (invis_count > 0) + return; + flush_sbuf(); + int fill_flag = 0; + switch (code) { + case 'C': + fill_flag = 1; + // fall through + case 'c': + // troff adds an extra argument to C + if (np != 1 && !(code == 'C' && np == 2)) { + error("1 argument required for circle"); + break; + } + out.put_fix_number(env->hpos + p[0]/2) + .put_fix_number(env->vpos) + .put_fix_number(p[0]/2) + .put_symbol("DC"); + if (fill_flag) + fill_path(env); + else { + set_line_thickness_and_color(env); + out.put_symbol("ST"); + } + break; + case 'l': + if (np != 2) { + error("2 arguments required for line"); + break; + } + set_line_thickness_and_color(env); + out.put_fix_number(p[0] + env->hpos) + .put_fix_number(p[1] + env->vpos) + .put_fix_number(env->hpos) + .put_fix_number(env->vpos) + .put_symbol("DL"); + break; + case 'E': + fill_flag = 1; + // fall through + case 'e': + if (np != 2) { + error("2 arguments required for ellipse"); + break; + } + out.put_fix_number(p[0]) + .put_fix_number(p[1]) + .put_fix_number(env->hpos + p[0]/2) + .put_fix_number(env->vpos) + .put_symbol("DE"); + if (fill_flag) + fill_path(env); + else { + set_line_thickness_and_color(env); + out.put_symbol("ST"); + } + break; + case 'P': + fill_flag = 1; + // fall through + case 'p': + { + if (np & 1) { + error("even number of arguments required for polygon"); + break; + } + if (np == 0) { + error("no arguments for polygon"); + break; + } + out.put_fix_number(env->hpos) + .put_fix_number(env->vpos) + .put_symbol("MT"); + for (int i = 0; i < np; i += 2) + out.put_fix_number(p[i]) + .put_fix_number(p[i+1]) + .put_symbol("RL"); + out.put_symbol("CL"); + if (fill_flag) + fill_path(env); + else { + set_line_thickness_and_color(env); + out.put_symbol("ST"); + } + break; + } + case '~': + { + if (np & 1) { + error("even number of arguments required for spline"); + break; + } + if (np == 0) { + error("no arguments for spline"); + break; + } + out.put_fix_number(env->hpos) + .put_fix_number(env->vpos) + .put_symbol("MT"); + out.put_fix_number(p[0]/2) + .put_fix_number(p[1]/2) + .put_symbol("RL"); + /* tnum/tden should be between 0 and 1; the closer it is to 1 + the tighter the curve will be to the guiding lines; 2/3 + is the standard value */ + const int tnum = 2; + const int tden = 3; + for (int i = 0; i < np - 2; i += 2) { + out.put_fix_number((p[i]*tnum)/(2*tden)) + .put_fix_number((p[i + 1]*tnum)/(2*tden)) + .put_fix_number(p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden)) + .put_fix_number(p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden)) + .put_fix_number((p[i] - p[i]/2) + p[i + 2]/2) + .put_fix_number((p[i + 1] - p[i + 1]/2) + p[i + 3]/2) + .put_symbol("RC"); + } + out.put_fix_number(p[np - 2] - p[np - 2]/2) + .put_fix_number(p[np - 1] - p[np - 1]/2) + .put_symbol("RL"); + set_line_thickness_and_color(env); + out.put_symbol("ST"); + } + break; + case 'a': + { + if (np != 4) { + error("4 arguments required for arc"); + break; + } + set_line_thickness_and_color(env); + double c[2]; + if (adjust_arc_center(p, c)) + out.put_fix_number(env->hpos + int(c[0])) + .put_fix_number(env->vpos + int(c[1])) + .put_fix_number(int(sqrt(c[0]*c[0] + c[1]*c[1]))) + .put_float(degrees(atan2(-c[1], -c[0]))) + .put_float(degrees(atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0]))) + .put_symbol("DA"); + else + out.put_fix_number(p[0] + p[2] + env->hpos) + .put_fix_number(p[1] + p[3] + env->vpos) + .put_fix_number(env->hpos) + .put_fix_number(env->vpos) + .put_symbol("DL"); + } + break; + 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; + } + line_thickness = p[0]; + } + break; + default: + error("unrecognised drawing command '%1'", char(code)); + break; + } + output_hpos = output_vpos = -1; +} + +const char *ps_printer::media_name() +{ + return "Default"; +} + +int ps_printer::media_width() +{ + /* + * NOTE: + * Although paper dimensions are defined as a pair of real numbers, + * it seems to be a common convention to round to the nearest + * PostScript unit. For example, A4 is really 595.276 by 841.89 but + * we use 595 by 842. + * + * This is probably a good compromise, especially since the + * PostScript definition specifies that media matching should be done + * within a tolerance of 5 units. + */ + return int(user_paper_width ? user_paper_width*72.0 + 0.5 + : font::paperwidth*72.0/font::res + 0.5); +} + +int ps_printer::media_height() +{ + return int(user_paper_length ? user_paper_length*72.0 + 0.5 + : paper_length*72.0/font::res + 0.5); +} + +void ps_printer::media_set() +{ + /* + * The setpagedevice implies an erasepage and initgraphics, and + * must thus precede any descriptions for a particular page. + * + * NOTE: + * This does not work with ps2pdf when there are included eps + * segments that contain PageSize/setpagedevice. + * This might be a bug in ghostscript -- must be investigated. + * Using setpagedevice in an .eps is really the wrong concept, anyway. + * + * NOTE: + * For the future, this is really the place to insert other + * media selection features, like: + * MediaColor + * MediaPosition + * MediaType + * MediaWeight + * MediaClass + * TraySwitch + * ManualFeed + * InsertSheet + * Duplex + * Collate + * ProcessColorModel + * etc. + */ + if (!(broken_flags & (USE_PS_ADOBE_2_0|NO_PAPERSIZE))) { + out.begin_comment("BeginFeature:") + .comment_arg("*PageSize") + .comment_arg(media_name()) + .end_comment(); + int w = media_width(); + int h = media_height(); + if (w > 0 && h > 0) + // warning to user is done elsewhere + fprintf(out.get_file(), + "<< /PageSize [ %d %d ] /ImagingBBox null >> setpagedevice\n", + w, h); + out.simple_comment("EndFeature"); + } +} + +void ps_printer::begin_page(int n) +{ + out.begin_comment("Page:") + .comment_arg(i_to_a(n)); + out.comment_arg(i_to_a(++pages_output)) + .end_comment(); + output_style.f = 0; + output_space_code = 32; + output_draw_point_size = -1; + output_line_thickness = -1; + output_hpos = output_vpos = -1; + ndefined_styles = 0; + out.simple_comment("BeginPageSetup"); + +#if 0 + /* + * NOTE: + * may decide to do this once per page + */ + media_set(); +#endif + + out.put_symbol("BP") + .simple_comment("EndPageSetup"); + if (sbuf_color != default_color) + set_color(&sbuf_color); +} + +void ps_printer::end_page(int) +{ + flush_sbuf(); + set_color(&default_color); + out.put_symbol("EP"); + if (invis_count != 0) { + error("missing 'endinvis' command"); + invis_count = 0; + } +} + +font *ps_printer::make_font(const char *nm) +{ + return ps_font::load_ps_font(nm); +} + +ps_printer::~ps_printer() +{ + out.simple_comment("Trailer") + .put_symbol("end") + .simple_comment("EOF"); + if (fseek(tempfp, 0L, 0) < 0) + fatal("fseek on temporary file failed"); + fputs("%!PS-Adobe-", stdout); + fputs((broken_flags & USE_PS_ADOBE_2_0) ? "2.0" : "3.0", stdout); + putchar('\n'); + out.set_file(stdout); + if (cmyk_flag) + out.begin_comment("Extensions:") + .comment_arg("CMYK") + .end_comment(); + out.begin_comment("Creator:") + .comment_arg("groff") + .comment_arg("version") + .comment_arg(Version_string) + .end_comment(); + { + fputs("%%CreationDate: ", out.get_file()); +#ifdef LONG_FOR_TIME_T + long +#else + time_t +#endif + t = current_time(); + fputs(ctime(&t), out.get_file()); + } + for (font_pointer_list *f = font_list; f; f = f->next) { + ps_font *psf = (ps_font *)(f->p); + rm.need_font(psf->get_internal_name()); + } + rm.print_header_comments(out); + out.begin_comment("Pages:") + .comment_arg(i_to_a(pages_output)) + .end_comment(); + out.begin_comment("PageOrder:") + .comment_arg("Ascend") + .end_comment(); + if (!(broken_flags & NO_PAPERSIZE)) { + int w = media_width(); + int h = media_height(); + if (w > 0 && h > 0) + fprintf(out.get_file(), + "%%%%DocumentMedia: %s %d %d %d %s %s\n", + media_name(), // tag name of media + w, // media width + h, // media height + 0, // weight in g/m2 + "()", // paper color, e.g. white + "()" // preprinted form type + ); + else { + if (h <= 0) + // see ps_printer::ps_printer + warning("bad paper height, defaulting to 11i"); + if (w <= 0) + warning("bad paper width"); + } + } + out.begin_comment("Orientation:") + .comment_arg(landscape_flag ? "Landscape" : "Portrait") + .end_comment(); + if (ncopies != 1) { + out.end_line(); + fprintf(out.get_file(), "%%%%Requirements: numcopies(%d)\n", ncopies); + } + out.simple_comment("EndComments"); + if (!(broken_flags & NO_PAPERSIZE)) { + /* gv works fine without this one, but it really should be there. */ + out.simple_comment("BeginDefaults"); + fprintf(out.get_file(), "%%%%PageMedia: %s\n", media_name()); + out.simple_comment("EndDefaults"); + } + out.simple_comment("BeginProlog"); + rm.output_prolog(out); + if (!(broken_flags & NO_SETUP_SECTION)) { + out.simple_comment("EndProlog"); + out.simple_comment("BeginSetup"); + } +#if 1 + /* + * Define paper (i.e., media) size for entire document here. + * This allows ps2pdf to correctly determine page size, for instance. + */ + media_set(); +#endif + rm.document_setup(out); + out.put_symbol(dict_name) + .put_symbol("begin"); + if (ndefs > 0) + ndefs += DEFS_DICT_SPARE; + out.put_literal_symbol(defs_dict_name) + .put_number(ndefs + 1) + .put_symbol("dict") + .put_symbol("def"); + out.put_symbol(defs_dict_name) + .put_symbol("begin"); + out.put_literal_symbol("u") + .put_delimiter('{') + .put_fix_number(1) + .put_symbol("mul") + .put_delimiter('}') + .put_symbol("bind") + .put_symbol("def"); + defs += '\0'; + out.special(defs.contents()); + out.put_symbol("end"); + if (ncopies != 1) + out.put_literal_symbol("#copies") + .put_number(ncopies) + .put_symbol("def"); + out.put_literal_symbol("RES") + .put_number(res) + .put_symbol("def"); + out.put_literal_symbol("PL"); + if (guess_flag) + out.put_symbol("PLG"); + else + out.put_fix_number(paper_length); + out.put_symbol("def"); + out.put_literal_symbol("LS") + .put_symbol(landscape_flag ? "true" : "false") + .put_symbol("def"); + if (manual_feed_flag) { + out.begin_comment("BeginFeature:") + .comment_arg("*ManualFeed") + .comment_arg("True") + .end_comment() + .put_symbol("MANUAL") + .simple_comment("EndFeature"); + } + encode_fonts(); + while (subencodings) { + subencoding *tem = subencodings; + subencodings = subencodings->next; + encode_subfont(tem); + out.put_literal_symbol(tem->subfont) + .put_symbol(make_subencoding_name(tem->idx)) + .put_literal_symbol(tem->p->get_internal_name()) + .put_symbol("RE"); + delete tem; + } + out.simple_comment((broken_flags & NO_SETUP_SECTION) + ? "EndProlog" + : "EndSetup"); + out.end_line(); + out.copy_file(tempfp); + fclose(tempfp); +} + +void ps_printer::special(char *arg, const environment *env, char type) +{ + if (type != 'p') + return; + typedef void (ps_printer::*SPECIAL_PROCP)(char *, const environment *); + static const struct { + const char *name; + SPECIAL_PROCP proc; + } proc_table[] = { + { "exec", &ps_printer::do_exec }, + { "def", &ps_printer::do_def }, + { "mdef", &ps_printer::do_mdef }, + { "import", &ps_printer::do_import }, + { "file", &ps_printer::do_file }, + { "invis", &ps_printer::do_invis }, + { "endinvis", &ps_printer::do_endinvis }, + }; + char *p; + for (p = arg; *p == ' ' || *p == '\n'; p++) + ; + char *tag = p; + for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++) + ; + if (*p == '\0' || strncmp(tag, "ps", p - tag) != 0) { + error("X command without 'ps:' tag ignored"); + return; + } + p++; + for (; *p == ' ' || *p == '\n'; p++) + ; + char *command = p; + for (; *p != '\0' && *p != ' ' && *p != '\n'; p++) + ; + if (*command == '\0') { + error("empty X command ignored"); + return; + } + for (unsigned int i = 0; i < sizeof(proc_table)/sizeof(proc_table[0]); i++) + if (strncmp(command, proc_table[i].name, p - command) == 0) { + flush_sbuf(); + if (sbuf_color != *env->col) + set_color(env->col); + (this->*(proc_table[i].proc))(p, env); + return; + } + error("X command '%1' not recognised", command); +} + +// A conforming PostScript document must not have lines longer +// than 255 characters (excluding line termination characters). + +static int check_line_lengths(const char *p) +{ + for (;;) { + const char *end = strchr(p, '\n'); + if (end == 0) + end = strchr(p, '\0'); + if (end - p > 255) + return 0; + if (*end == '\0') + break; + p = end + 1; + } + return 1; +} + +void ps_printer::do_exec(char *arg, const environment *env) +{ + while (csspace(*arg)) + arg++; + if (*arg == '\0') { + error("missing argument to X exec command"); + return; + } + if (!check_line_lengths(arg)) + warning("lines in X exec command should" + " not be more than 255 characters long"); + out.put_fix_number(env->hpos) + .put_fix_number(env->vpos) + .put_symbol("EBEGIN") + .special(arg) + .put_symbol("EEND"); + output_hpos = output_vpos = -1; + output_style.f = 0; + output_draw_point_size = -1; + output_line_thickness = -1; + ndefined_styles = 0; + if (!ndefs) + ndefs = 1; +} + +void ps_printer::do_file(char *arg, const environment *env) +{ + while (csspace(*arg)) + arg++; + if (*arg == '\0') { + error("missing argument to X file command"); + return; + } + const char *filename = arg; + do { + ++arg; + } while (*arg != '\0' && *arg != ' ' && *arg != '\n'); + out.put_fix_number(env->hpos) + .put_fix_number(env->vpos) + .put_symbol("EBEGIN"); + rm.import_file(filename, out); + out.put_symbol("EEND"); + output_hpos = output_vpos = -1; + output_style.f = 0; + output_draw_point_size = -1; + output_line_thickness = -1; + ndefined_styles = 0; + if (!ndefs) + ndefs = 1; +} + +void ps_printer::do_def(char *arg, const environment *) +{ + while (csspace(*arg)) + arg++; + if (!check_line_lengths(arg)) + warning("lines in X def command should" + " not be more than 255 characters long"); + defs += arg; + if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n') + defs += '\n'; + ndefs++; +} + +// Like def, but the first argument says how many definitions it contains. + +void ps_printer::do_mdef(char *arg, const environment *) +{ + char *p; + int n = (int)strtol(arg, &p, 10); + if (n == 0 && p == arg) { + error("first argument to X mdef must be an integer"); + return; + } + if (n < 0) { + error("out of range argument '%1' to X mdef command", int(n)); + return; + } + arg = p; + while (csspace(*arg)) + arg++; + if (!check_line_lengths(arg)) + warning("lines in X mdef command should" + " not be more than 255 characters long"); + defs += arg; + if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n') + defs += '\n'; + ndefs += n; +} + +void ps_printer::do_import(char *arg, const environment *env) +{ + while (*arg == ' ' || *arg == '\n') + arg++; + char *p; + for (p = arg; *p != '\0' && *p != ' ' && *p != '\n'; p++) + ; + if (*p != '\0') + *p++ = '\0'; + int parms[6]; + int nparms = 0; + while (nparms < 6) { + char *end; + long n = strtol(p, &end, 10); + if (n == 0 && end == p) + break; + parms[nparms++] = int(n); + p = end; + } + if (csalpha(*p) && (p[1] == '\0' || p[1] == ' ' || p[1] == '\n')) { + error("scaling units not allowed in arguments for X import command"); + return; + } + while (*p == ' ' || *p == '\n') + p++; + if (nparms < 5) { + if (*p == '\0') + error("too few arguments for X import command"); + else + error("invalid argument '%1' for X import command", p); + return; + } + if (*p != '\0') { + error("superfluous argument '%1' for X import command", p); + return; + } + int llx = parms[0]; + int lly = parms[1]; + int urx = parms[2]; + int ury = parms[3]; + int desired_width = parms[4]; + int desired_height = parms[5]; + if (desired_width <= 0) { + error("bad width argument '%1' for X import command: must be > 0", + desired_width); + return; + } + if (nparms == 6 && desired_height <= 0) { + error("bad height argument '%1' for X import command: must be > 0", + desired_height); + return; + } + if (llx == urx) { + error("llx and urx arguments for X import command must not be equal"); + return; + } + if (lly == ury) { + error("lly and ury arguments for X import command must not be equal"); + return; + } + if (nparms == 5) { + int old_wid = urx - llx; + int old_ht = ury - lly; + if (old_wid < 0) + old_wid = -old_wid; + if (old_ht < 0) + old_ht = -old_ht; + desired_height = int(desired_width*(double(old_ht)/double(old_wid)) + .5); + } + if (env->vpos - desired_height < 0) + warning("top of imported graphic is above the top of the page"); + out.put_number(llx) + .put_number(lly) + .put_fix_number(desired_width) + .put_number(urx - llx) + .put_fix_number(-desired_height) + .put_number(ury - lly) + .put_fix_number(env->hpos) + .put_fix_number(env->vpos) + .put_symbol("PBEGIN"); + rm.import_file(arg, out); + // do this here just in case application defines PEND + out.put_symbol("end") + .put_symbol("PEND"); +} + +void ps_printer::do_invis(char *, const environment *) +{ + invis_count++; +} + +void ps_printer::do_endinvis(char *, const environment *) +{ + if (invis_count == 0) + error("unbalanced 'endinvis' command"); + else + --invis_count; +} + +printer *make_printer() +{ + return new ps_printer(user_paper_length); +} + +static void usage(FILE *stream); + +int main(int argc, char **argv) +{ + setlocale(LC_NUMERIC, "C"); + program_name = argv[0]; + string env; + static char stderr_buf[BUFSIZ]; + setbuf(stderr, stderr_buf); + int c; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((c = getopt_long(argc, argv, "b:c:F:gI:lmp:P:vw:", long_options, NULL)) + != EOF) + switch(c) { + case 'b': + // XXX check this + broken_flags = atoi(optarg); + bflag = 1; + break; + case 'c': + if (sscanf(optarg, "%d", &ncopies) != 1 || ncopies <= 0) { + error("bad number of copies '%1'", optarg); + ncopies = 1; + } + break; + case 'F': + font::command_line_font_dir(optarg); + break; + case 'g': + guess_flag = 1; + break; + case 'I': + include_search_path.command_line_dir(optarg); + break; + case 'l': + landscape_flag = 1; + break; + case 'm': + manual_feed_flag = 1; + break; + case 'p': + if (!font::scan_papersize(optarg, 0, + &user_paper_length, &user_paper_width)) + error("ignoring invalid custom paper format '%1'", optarg); + break; + case 'P': + env = "GROPS_PROLOGUE"; + env += '='; + env += optarg; + env += '\0'; + if (putenv(strsave(env.contents()))) + fatal("putenv failed"); + break; + case 'v': + printf("GNU grops (groff) version %s\n", Version_string); + exit(0); + break; + case 'w': + if (sscanf(optarg, "%d", &linewidth) != 1 || linewidth < 0) { + error("invalid line width '%1' ignored", optarg); + linewidth = -1; + } + break; + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + font::set_unknown_desc_command_handler(handle_unknown_desc_command); + SET_BINARY(fileno(stdout)); + if (optind >= argc) + do_file("-"); + else { + for (int i = optind; i < argc; i++) + do_file(argv[i]); + } + return 0; +} + +static void usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-glm] [-b brokenness-flags] [-c num-copies]" +" [-F font-directory] [-I inclusion-directory] [-p paper-format]" +" [-P prologue-file] [-w rule-thickness] [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s --help\n", + program_name, program_name, program_name); + if (stdout == stream) + fputs( +"\n" +"Translate the output of troff(1) into PostScript. See the grops(1)\n" +"manual page.\n", + stream); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/devices/grops/ps.h b/src/devices/grops/ps.h new file mode 100644 index 0000000..5cef694 --- /dev/null +++ b/src/devices/grops/ps.h @@ -0,0 +1,129 @@ +// -*- C++ -*- +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +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 <http://www.gnu.org/licenses/>. */ + +class ps_output { +public: + ps_output(FILE *, int max_line_length); + ps_output &put_string(const char *, int); + ps_output &put_number(int); + ps_output &put_fix_number(int); + ps_output &put_float(double); + ps_output &put_symbol(const char *); + ps_output &put_color(unsigned int); + ps_output &put_literal_symbol(const char *); + ps_output &set_fixed_point(int); + ps_output &simple_comment(const char *); + ps_output &begin_comment(const char *); + ps_output &comment_arg(const char *); + ps_output &end_comment(); + ps_output &set_file(FILE *); + ps_output &include_file(FILE *); + ps_output ©_file(FILE *); + ps_output &end_line(); + ps_output &put_delimiter(char); + ps_output &special(const char *); + FILE *get_file(); +private: + FILE *fp; + int col; + int max_line_length; // not including newline + int need_space; + int fixed_point; +}; + +inline FILE *ps_output::get_file() +{ + return fp; +} + +// this must stay in sync with 'resource_table' in 'psrm.cpp' +enum resource_type { + RESOURCE_FONT, + RESOURCE_FONTSET, + RESOURCE_PROCSET, + RESOURCE_FILE, + RESOURCE_ENCODING, + RESOURCE_FORM, + RESOURCE_PATTERN +}; + +struct resource; + +extern string an_empty_string; + +class resource_manager { +public: + resource_manager(); + ~resource_manager(); + void import_file(const char *filename, ps_output &); + void need_font(const char *name); + void print_header_comments(ps_output &); + void document_setup(ps_output &); + void output_prolog(ps_output &); +private: + unsigned extensions; + unsigned language_level; + resource *procset_resource; + resource *resource_list; + resource *lookup_resource(resource_type type, string &name, + string &version = an_empty_string, + unsigned revision = 0); + resource *lookup_font(const char *name); + void read_download_file(); + void supply_resource(resource *r, int rank, FILE *outfp, + int is_document = 0); + void process_file(int rank, FILE *fp, const char *filename, FILE *outfp); + resource *read_file_arg(const char **); + resource *read_procset_arg(const char **); + resource *read_font_arg(const char **); + resource *read_resource_arg(const char **); + void print_resources_comment(unsigned flag, FILE *outfp); + void print_extensions_comment(FILE *outfp); + void print_language_level_comment(FILE *outfp); + int do_begin_resource(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_include_resource(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_begin_document(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_include_document(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_begin_procset(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_include_procset(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_begin_font(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_include_font(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_begin_file(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_include_file(const char *ptr, int rank, FILE *fp, FILE *outfp); + int change_to_end_resource(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_begin_preview(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_begin_data(const char *ptr, int rank, FILE *fp, FILE *outfp); + int do_begin_binary(const char *ptr, int rank, FILE *fp, FILE *outfp); +}; + +extern unsigned broken_flags; + +// broken_flags is ored from these + +enum { + NO_SETUP_SECTION = 01, + STRIP_PERCENT_BANG = 02, + STRIP_STRUCTURE_COMMENTS = 04, + USE_PS_ADOBE_2_0 = 010, + NO_PAPERSIZE = 020 +}; + +#include "searchpath.h" + +extern search_path include_search_path; diff --git a/src/devices/grops/psfig.diff b/src/devices/grops/psfig.diff new file mode 100644 index 0000000..4e0d1f9 --- /dev/null +++ b/src/devices/grops/psfig.diff @@ -0,0 +1,106 @@ +These are patches to makes psfig work with groff. They apply to the +version of psfig in comp.sources.unix/Volume11. After applying them, +psfig should be recompiled with -DGROFF. The resulting psfig will +work only with groff, so you might want to install it under a +different name. The output of this psfig must be processed using the +macros in the file ../tmac/tmac.psfig. These will automatically add +the necessary PostScript code to the prologue output by grops. Use of +the 'global' feature in psfig will result in non-conformant PostScript +which will fail if processed by a page reversal program. Note that +psfig is unsupported by me (I'm not interested in hearing about psfig +problems.) For new documents, I recommend using the PostScript +inclusion features provided by grops. + +James Clark +jjc@jclark.com + +*** cmds.c.~1~ Thu Feb 14 16:09:45 1991 +--- cmds.c Mon Mar 4 12:49:26 1991 +*************** +*** 245,253 **** +--- 245,261 ---- + (void) sprintf(x, "%.2fp", fx); + (void) sprintf(y, "%.2fp", fy); + } else if (!*x) { ++ #ifndef GROFF + (void) sprintf(x,"(%.2fp*%s/%.2fp)", fx, y, fy); ++ #else /* GROFF */ ++ (void) sprintf(x,"(%.0fu*%s/%.0fu)", fx, y, fy); ++ #endif /* GROFF */ + } else if (!*y) { ++ #ifndef GROFF + (void) sprintf(y,"(%.2fp*%s/%.2fp)", fy, x, fx); ++ #else /* GROFF */ ++ (void) sprintf(y,"(%.0fu*%s/%.0fu)", fy, x, fx); ++ #endif /* GROFF */ + } + + /* +*** troff.c.~1~ Thu Feb 14 16:09:48 1991 +--- troff.c Mon Mar 4 12:48:46 1991 +*************** +*** 26,32 **** +--- 26,36 ---- + } + + ++ #ifndef GROFF + char incl_file_s[] = "\\X'f%s'"; ++ #else /* GROFF */ ++ char incl_file_s[] = "\\X'ps: file %s'"; ++ #endif /* GROFF */ + includeFile(filenm) + char *filenm; { + printf(incl_file_s, filenm); +*************** +*** 40,52 **** +--- 44,64 ---- + error("buffer overflow"); + } + ++ #ifndef GROFF + char endfig_s[] = "\\X'pendFig'"; ++ #else /* GROFF */ ++ char endfig_s[] = "\\X'ps: exec psfigend'"; ++ #endif /* GROFF */ + endfig() { + printf(endfig_s); + } + + char startfig_s[] = ++ #ifndef GROFF + "\\X'p\\w@\\h@%s@@'\\X'p\\w@\\h@%s@@'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'pstartFig'"; ++ #else /* GROFF */ ++ "\\X'ps: exec \\w@\\h@%s@@ \\w@\\h@%s@@ %.2f %.2f %.2f %.2f psfigstart'"; ++ #endif /* GROFF */ + + startfig(x, y, llx, lly, urx, ury) + char *x, *y; +*************** +*** 57,63 **** +--- 69,79 ---- + } + + emitDoClip() { ++ #ifndef GROFF + printf("\\X'pdoclip'"); ++ #else /* GROFF */ ++ printf("\\X'ps: exec psfigclip'"); ++ #endif /* GROFF */ + } + + flushX() +*************** +*** 116,122 **** +--- 132,142 ---- + + #define isWhite(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n') + ++ #ifndef GROFF + char literal_s[] = "\\X'p%s'"; ++ #else /* GROFF */ ++ char literal_s[] = "\\X'ps: exec %s'"; ++ #endif /* GROFF */ + emitLiteral(text) + char *text; { + static char litbuf[BUFSZ]; diff --git a/src/devices/grops/psrm.cpp b/src/devices/grops/psrm.cpp new file mode 100644 index 0000000..3c9a8b7 --- /dev/null +++ b/src/devices/grops/psrm.cpp @@ -0,0 +1,1189 @@ +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +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 <http://www.gnu.org/licenses/>. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" + +#include "ps.h" + +#ifdef NEED_DECLARATION_PUTENV +extern "C" { + int putenv(const char *); +} +#endif /* NEED_DECLARATION_PUTENV */ + +#define GROPS_PROLOGUE "prologue" + +static void print_ps_string(const string &s, FILE *outfp); + +cset white_space("\n\r \t\f"); +string an_empty_string; + +char valid_input_table[256]= { +#ifndef IS_EBCDIC_HOST + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +#else + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, +#endif +}; + +const char *extension_table[] = { + "DPS", + "CMYK", + "Composite", + "FileSystem", +}; + +const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]); + +// this must stay in sync with 'resource_type' in 'ps.h' +const char *resource_table[] = { + "font", + "fontset", + "procset", + "file", + "encoding", + "form", + "pattern", +}; + +const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]); + +static int read_uint_arg(const char **pp, unsigned *res) +{ + while (white_space(**pp)) + *pp += 1; + if (**pp == '\0') { + error("missing argument"); + return 0; + } + const char *start = *pp; + // XXX use strtoul + long n = strtol(start, (char **)pp, 10); + if (n == 0 && *pp == start) { + error("not an integer"); + return 0; + } + if (n < 0) { + error("argument must not be negative"); + return 0; + } + *res = unsigned(n); + return 1; +} + +struct resource { + resource *next; + resource_type type; + string name; + enum { NEEDED = 01, SUPPLIED = 02, FONT_NEEDED = 04, BUSY = 010 }; + unsigned flags; + string version; + unsigned revision; + char *filename; + int rank; + resource(resource_type, string &, string & = an_empty_string, unsigned = 0); + ~resource(); + void print_type_and_name(FILE *outfp); +}; + +resource::resource(resource_type t, string &n, string &v, unsigned r) +: next(0), type(t), flags(0), revision(r), filename(0), rank(-1) +{ + name.move(n); + version.move(v); + if (type == RESOURCE_FILE) { + if (name.search('\0') >= 0) + error("filename contains a character with code 0"); + filename = name.extract(); + } +} + +resource::~resource() +{ + free(filename); +} + +void resource::print_type_and_name(FILE *outfp) +{ + fputs(resource_table[type], outfp); + putc(' ', outfp); + print_ps_string(name, outfp); + if (type == RESOURCE_PROCSET) { + putc(' ', outfp); + print_ps_string(version, outfp); + fprintf(outfp, " %u", revision); + } +} + +resource_manager::resource_manager() +: extensions(0), language_level(0), resource_list(0) +{ + read_download_file(); + string procset_name("grops"); + extern const char *version_string; + extern const char *revision_string; + unsigned revision_uint; + if (!read_uint_arg(&revision_string, &revision_uint)) + revision_uint = 0; + string procset_version(version_string); + procset_resource = lookup_resource(RESOURCE_PROCSET, procset_name, + procset_version, revision_uint); + procset_resource->flags |= resource::SUPPLIED; +} + +resource_manager::~resource_manager() +{ + while (resource_list) { + resource *tem = resource_list; + resource_list = resource_list->next; + delete tem; + } +} + +resource *resource_manager::lookup_resource(resource_type type, + string &name, + string &version, + unsigned revision) +{ + resource *r; + for (r = resource_list; r; r = r->next) + if (r->type == type + && r->name == name + && r->version == version + && r->revision == revision) + return r; + r = new resource(type, name, version, revision); + r->next = resource_list; + resource_list = r; + return r; +} + +// Just a specialized version of lookup_resource(). + +resource *resource_manager::lookup_font(const char *name) +{ + resource *r; + for (r = resource_list; r; r = r->next) + if (r->type == RESOURCE_FONT + && strlen(name) == (size_t)r->name.length() + && memcmp(name, r->name.contents(), r->name.length()) == 0) + return r; + string s(name); + r = new resource(RESOURCE_FONT, s); + r->next = resource_list; + resource_list = r; + return r; +} + +void resource_manager::need_font(const char *name) +{ + lookup_font(name)->flags |= resource::FONT_NEEDED; +} + +typedef resource *Presource; // Work around g++ bug. + +void resource_manager::document_setup(ps_output &out) +{ + int nranks = 0; + resource *r; + for (r = resource_list; r; r = r->next) + if (r->rank >= nranks) + nranks = r->rank + 1; + if (nranks > 0) { + // Sort resource_list in reverse order of rank. + Presource *head = new Presource[nranks + 1]; + Presource **tail = new Presource *[nranks + 1]; + int i; + for (i = 0; i < nranks + 1; i++) { + head[i] = 0; + tail[i] = &head[i]; + } + for (r = resource_list; r; r = r->next) { + i = r->rank < 0 ? 0 : r->rank + 1; + *tail[i] = r; + tail[i] = &(*tail[i])->next; + } + resource_list = 0; + for (i = 0; i < nranks + 1; i++) + if (head[i]) { + *tail[i] = resource_list; + resource_list = head[i]; + } + delete[] head; + delete[] tail; + // check it + for (r = resource_list; r; r = r->next) + if (r->next) + assert(r->rank >= r->next->rank); + for (r = resource_list; r; r = r->next) + if (r->type == RESOURCE_FONT && r->rank >= 0) + supply_resource(r, -1, out.get_file()); + } +} + +void resource_manager::print_resources_comment(unsigned flag, + FILE *outfp) +{ + int continued = 0; + for (resource *r = resource_list; r; r = r->next) + if (r->flags & flag) { + if (continued) + fputs("%%+ ", outfp); + else { + fputs(flag == resource::NEEDED + ? "%%DocumentNeededResources: " + : "%%DocumentSuppliedResources: ", + outfp); + continued = 1; + } + r->print_type_and_name(outfp); + putc('\n', outfp); + } +} + +void resource_manager::print_header_comments(ps_output &out) +{ + for (resource *r = resource_list; r; r = r->next) + if (r->type == RESOURCE_FONT && (r->flags & resource::FONT_NEEDED)) + supply_resource(r, 0, 0); + print_resources_comment(resource::NEEDED, out.get_file()); + print_resources_comment(resource::SUPPLIED, out.get_file()); + print_language_level_comment(out.get_file()); + print_extensions_comment(out.get_file()); +} + +void resource_manager::output_prolog(ps_output &out) +{ + FILE *outfp = out.get_file(); + out.end_line(); + char *path; + if (!getenv("GROPS_PROLOGUE")) { + string e = "GROPS_PROLOGUE"; + e += '='; + e += GROPS_PROLOGUE; + e += '\0'; + if (putenv(strsave(e.contents()))) + fatal("putenv failed"); + } + char *prologue = getenv("GROPS_PROLOGUE"); + FILE *fp = font::open_file(prologue, &path); + if (!fp) + fatal("failed to open PostScript prologue '%1': %2", prologue, + strerror(errno)); + fputs("%%BeginResource: ", outfp); + procset_resource->print_type_and_name(outfp); + putc('\n', outfp); + process_file(-1, fp, path, outfp); + fclose(fp); + free(path); + fputs("%%EndResource\n", outfp); +} + +void resource_manager::import_file(const char *filename, ps_output &out) +{ + out.end_line(); + string name(filename); + resource *r = lookup_resource(RESOURCE_FILE, name); + supply_resource(r, -1, out.get_file(), 1); +} + +void resource_manager::supply_resource(resource *r, int rank, + FILE *outfp, int is_document) +{ + if (r->flags & resource::BUSY) { + r->name += '\0'; + fatal("loop detected in dependency graph for %1 '%2'", + resource_table[r->type], + r->name.contents()); + } + r->flags |= resource::BUSY; + if (rank > r->rank) + r->rank = rank; + char *path = 0 /* nullptr */; + FILE *fp = 0 /* nullptr */; + if (r->filename != 0 /* nullptr */) { + if (r->type == RESOURCE_FONT) { + fp = font::open_file(r->filename, &path); + if (!fp) { + error("failed to open PostScript resource '%1': %2", + r->filename, strerror(errno)); + delete[] r->filename; + r->filename = 0 /* nullptr */; + } + } + else { + errno = 0; + fp = include_search_path.open_file_cautious(r->filename); + if (!fp) { + error("can't open '%1': %2", r->filename, strerror(errno)); + delete[] r->filename; + r->filename = 0 /* nullptr */; + } + else + path = r->filename; + } + } + if (fp) { + if (outfp) { + if (r->type == RESOURCE_FILE && is_document) { + fputs("%%BeginDocument: ", outfp); + print_ps_string(r->name, outfp); + putc('\n', outfp); + } + else { + fputs("%%BeginResource: ", outfp); + r->print_type_and_name(outfp); + putc('\n', outfp); + } + } + process_file(rank, fp, path, outfp); + fclose(fp); + if (r->type == RESOURCE_FONT) + free(path); + if (outfp) { + if (r->type == RESOURCE_FILE && is_document) + fputs("%%EndDocument\n", outfp); + else + fputs("%%EndResource\n", outfp); + } + r->flags |= resource::SUPPLIED; + } + else { + if (outfp) { + if (r->type == RESOURCE_FILE && is_document) { + fputs("%%IncludeDocument: ", outfp); + print_ps_string(r->name, outfp); + putc('\n', outfp); + } + else { + fputs("%%IncludeResource: ", outfp); + r->print_type_and_name(outfp); + putc('\n', outfp); + } + } + r->flags |= resource::NEEDED; + } + r->flags &= ~resource::BUSY; +} + +#define PS_MAGIC "%!PS-Adobe-" + +static int ps_get_line(string &buf, FILE *fp) +{ + buf.clear(); + int c = getc(fp); + if (c == EOF) + return 0; + current_lineno++; + while (c != '\r' && c != '\n' && c != EOF) { + if (!valid_input_table[c]) + error("invalid input character code %1", int(c)); + buf += c; + c = getc(fp); + } + buf += '\n'; + buf += '\0'; + if (c == '\r') { + c = getc(fp); + if (c != EOF && c != '\n') + ungetc(c, fp); + } + return 1; +} + +static int read_text_arg(const char **pp, string &res) +{ + res.clear(); + while (white_space(**pp)) + *pp += 1; + if (**pp == '\0') { + error("missing argument"); + return 0; + } + if (**pp != '(') { + for (; **pp != '\0' && !white_space(**pp); *pp += 1) + res += **pp; + return 1; + } + *pp += 1; + res.clear(); + int level = 0; + for (;;) { + if (**pp == '\0' || **pp == '\r' || **pp == '\n') { + error("missing ')'"); + return 0; + } + if (**pp == ')') { + if (level == 0) { + *pp += 1; + break; + } + res += **pp; + level--; + } + else if (**pp == '(') { + level++; + res += **pp; + } + else if (**pp == '\\') { + *pp += 1; + switch (**pp) { + case 'n': + res += '\n'; + break; + case 'r': + res += '\n'; + break; + case 't': + res += '\t'; + break; + case 'b': + res += '\b'; + break; + case 'f': + res += '\f'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int val = **pp - '0'; + if ((*pp)[1] >= '0' && (*pp)[1] <= '7') { + *pp += 1; + val = val*8 + (**pp - '0'); + if ((*pp)[1] >= '0' && (*pp)[1] <= '7') { + *pp += 1; + val = val*8 + (**pp - '0'); + } + } + } + break; + default: + res += **pp; + break; + } + } + else + res += **pp; + *pp += 1; + } + return 1; +} + +resource *resource_manager::read_file_arg(const char **ptr) +{ + string arg; + if (!read_text_arg(ptr, arg)) + return 0; + return lookup_resource(RESOURCE_FILE, arg); +} + +resource *resource_manager::read_font_arg(const char **ptr) +{ + string arg; + if (!read_text_arg(ptr, arg)) + return 0; + return lookup_resource(RESOURCE_FONT, arg); +} + +resource *resource_manager::read_procset_arg(const char **ptr) +{ + string arg; + if (!read_text_arg(ptr, arg)) + return 0; + string version; + if (!read_text_arg(ptr, version)) + return 0; + unsigned revision; + if (!read_uint_arg(ptr, &revision)) + return 0; + return lookup_resource(RESOURCE_PROCSET, arg, version, revision); +} + +resource *resource_manager::read_resource_arg(const char **ptr) +{ + while (white_space(**ptr)) + *ptr += 1; + const char *name = *ptr; + while (**ptr != '\0' && !white_space(**ptr)) + *ptr += 1; + if (name == *ptr) { + error("missing resource type"); + return 0; + } + int ri; + for (ri = 0; ri < NRESOURCES; ri++) + if (strlen(resource_table[ri]) == size_t(*ptr - name) + && strncasecmp(resource_table[ri], name, *ptr - name) == 0) + break; + if (ri >= NRESOURCES) { + error("unknown resource type"); + return 0; + } + if (ri == RESOURCE_PROCSET) + return read_procset_arg(ptr); + string arg; + if (!read_text_arg(ptr, arg)) + return 0; + return lookup_resource(resource_type(ri), arg); +} + +static const char *matches_comment(string &buf, const char *comment) +{ + if ((size_t)buf.length() < strlen(comment) + 3) + return 0; + if (buf[0] != '%' || buf[1] != '%') + return 0; + const char *bufp = buf.contents() + 2; + for (; *comment; comment++, bufp++) + if (*bufp != *comment) + return 0; + if (comment[-1] == ':') + return bufp; + if (*bufp == '\0' || white_space(*bufp)) + return bufp; + return 0; +} + +// Return 1 if the line should be copied out. + +int resource_manager::do_begin_resource(const char *ptr, int, FILE *, + FILE *) +{ + resource *r = read_resource_arg(&ptr); + if (r) + r->flags |= resource::SUPPLIED; + return 1; +} + +int resource_manager::do_include_resource(const char *ptr, int rank, + FILE *, FILE *outfp) +{ + resource *r = read_resource_arg(&ptr); + if (r) { + if (r->type == RESOURCE_FONT) { + if (rank >= 0) + supply_resource(r, rank + 1, outfp); + else + r->flags |= resource::FONT_NEEDED; + } + else + supply_resource(r, rank, outfp); + } + return 0; +} + +int resource_manager::do_begin_document(const char *ptr, int, FILE *, + FILE *) +{ + resource *r = read_file_arg(&ptr); + if (r) + r->flags |= resource::SUPPLIED; + return 1; +} + +int resource_manager::do_include_document(const char *ptr, int rank, + FILE *, FILE *outfp) +{ + resource *r = read_file_arg(&ptr); + if (r) + supply_resource(r, rank, outfp, 1); + return 0; +} + +int resource_manager::do_begin_procset(const char *ptr, int, FILE *, + FILE *outfp) +{ + resource *r = read_procset_arg(&ptr); + if (r) { + r->flags |= resource::SUPPLIED; + if (outfp) { + fputs("%%BeginResource: ", outfp); + r->print_type_and_name(outfp); + putc('\n', outfp); + } + } + return 0; +} + +int resource_manager::do_include_procset(const char *ptr, int rank, + FILE *, FILE *outfp) +{ + resource *r = read_procset_arg(&ptr); + if (r) + supply_resource(r, rank, outfp); + return 0; +} + +int resource_manager::do_begin_file(const char *ptr, int, FILE *, + FILE *outfp) +{ + resource *r = read_file_arg(&ptr); + if (r) { + r->flags |= resource::SUPPLIED; + if (outfp) { + fputs("%%BeginResource: ", outfp); + r->print_type_and_name(outfp); + putc('\n', outfp); + } + } + return 0; +} + +int resource_manager::do_include_file(const char *ptr, int rank, + FILE *, FILE *outfp) +{ + resource *r = read_file_arg(&ptr); + if (r) + supply_resource(r, rank, outfp); + return 0; +} + +int resource_manager::do_begin_font(const char *ptr, int, FILE *, + FILE *outfp) +{ + resource *r = read_font_arg(&ptr); + if (r) { + r->flags |= resource::SUPPLIED; + if (outfp) { + fputs("%%BeginResource: ", outfp); + r->print_type_and_name(outfp); + putc('\n', outfp); + } + } + return 0; +} + +int resource_manager::do_include_font(const char *ptr, int rank, FILE *, + FILE *outfp) +{ + resource *r = read_font_arg(&ptr); + if (r) { + if (rank >= 0) + supply_resource(r, rank + 1, outfp); + else + r->flags |= resource::FONT_NEEDED; + } + return 0; +} + +int resource_manager::change_to_end_resource(const char *, int, FILE *, + FILE *outfp) +{ + if (outfp) + fputs("%%EndResource\n", outfp); + return 0; +} + +int resource_manager::do_begin_preview(const char *, int, FILE *fp, + FILE *) +{ + string buf; + do { + if (!ps_get_line(buf, fp)) { + error("end of file in preview section"); + break; + } + } while (!matches_comment(buf, "EndPreview")); + return 0; +} + +int read_one_of(const char **ptr, const char **s, int n) +{ + while (white_space(**ptr)) + *ptr += 1; + if (**ptr == '\0') + return -1; + const char *start = *ptr; + do { + ++(*ptr); + } while (**ptr != '\0' && !white_space(**ptr)); + for (int i = 0; i < n; i++) + if (strlen(s[i]) == size_t(*ptr - start) + && memcmp(s[i], start, *ptr - start) == 0) + return i; + return -1; +} + +void skip_possible_newline(FILE *fp, FILE *outfp) +{ + int c = getc(fp); + if (c == '\r') { + current_lineno++; + if (outfp) + putc(c, outfp); + int cc = getc(fp); + if (cc != '\n') { + if (cc != EOF) + ungetc(cc, fp); + } + else { + if (outfp) + putc(cc, outfp); + } + } + else if (c == '\n') { + current_lineno++; + if (outfp) + putc(c, outfp); + } + else if (c != EOF) + ungetc(c, fp); +} + +int resource_manager::do_begin_data(const char *ptr, int, FILE *fp, + FILE *outfp) +{ + while (white_space(*ptr)) + ptr++; + const char *start = ptr; + unsigned numberof; + if (!read_uint_arg(&ptr, &numberof)) + return 0; + static const char *types[] = { "Binary", "Hex", "ASCII" }; + const int Binary = 0; + int type = 0; + static const char *units[] = { "Bytes", "Lines" }; + const int Bytes = 0; + int unit = Bytes; + while (white_space(*ptr)) + ptr++; + if (*ptr != '\0') { + type = read_one_of(&ptr, types, 3); + if (type < 0) { + error("bad data type"); + return 0; + } + while (white_space(*ptr)) + ptr++; + if (*ptr != '\0') { + unit = read_one_of(&ptr, units, 2); + if (unit < 0) { + error("expected 'Bytes' or 'Lines'"); + return 0; + } + } + } + if (type != Binary) + return 1; + if (outfp) { + fputs("%%BeginData: ", outfp); + fputs(start, outfp); + } + if (numberof > 0) { + unsigned bytecount = 0; + unsigned linecount = 0; + do { + int c = getc(fp); + if (c == EOF) { + error("end of file within data section"); + return 0; + } + if (outfp) + putc(c, outfp); + bytecount++; + if (c == '\r') { + int cc = getc(fp); + if (cc != '\n') { + linecount++; + current_lineno++; + } + if (cc != EOF) + ungetc(cc, fp); + } + else if (c == '\n') { + linecount++; + current_lineno++; + } + } while ((unit == Bytes ? bytecount : linecount) < numberof); + } + skip_possible_newline(fp, outfp); + string buf; + if (!ps_get_line(buf, fp)) { + error("missing %%%%EndData line"); + return 0; + } + if (!matches_comment(buf, "EndData")) + error("bad %%%%EndData line"); + if (outfp) + fputs(buf.contents(), outfp); + return 0; +} + +int resource_manager::do_begin_binary(const char *ptr, int, FILE *fp, + FILE *outfp) +{ + if (!outfp) + return 0; + unsigned count; + if (!read_uint_arg(&ptr, &count)) + return 0; + if (outfp) + fprintf(outfp, "%%%%BeginData: %u Binary Bytes\n", count); + while (count != 0) { + int c = getc(fp); + if (c == EOF) { + error("end of file within binary section"); + return 0; + } + if (outfp) + putc(c, outfp); + --count; + if (c == '\r') { + int cc = getc(fp); + if (cc != '\n') + current_lineno++; + if (cc != EOF) + ungetc(cc, fp); + } + else if (c == '\n') + current_lineno++; + } + skip_possible_newline(fp, outfp); + string buf; + if (!ps_get_line(buf, fp)) { + error("missing %%%%EndBinary line"); + return 0; + } + if (!matches_comment(buf, "EndBinary")) { + error("bad %%%%EndBinary line"); + if (outfp) + fputs(buf.contents(), outfp); + } + else if (outfp) + fputs("%%EndData\n", outfp); + return 0; +} + +static unsigned parse_extensions(const char *ptr) +{ + unsigned flags = 0; + for (;;) { + while (white_space(*ptr)) + ptr++; + if (*ptr == '\0') + break; + const char *name = ptr; + do { + ++ptr; + } while (*ptr != '\0' && !white_space(*ptr)); + int i; + for (i = 0; i < NEXTENSIONS; i++) + if (strlen(extension_table[i]) == size_t(ptr - name) + && memcmp(extension_table[i], name, ptr - name) == 0) { + flags |= (1 << i); + break; + } + if (i >= NEXTENSIONS) { + string s(name, ptr - name); + s += '\0'; + error("unknown extension '%1'", s.contents()); + } + } + return flags; +} + +// XXX if it has not been surrounded with {Begin,End}Document need to +// strip out Page: Trailer {Begin,End}Prolog {Begin,End}Setup sections. + +// XXX Perhaps the decision whether to use BeginDocument or +// BeginResource: file should be postponed till we have seen +// the first line of the file. + +void resource_manager::process_file(int rank, FILE *fp, + const char *filename, FILE *outfp) +{ + // If none of these comments appear in the header section, and we are + // just analyzing the file (i.e., outfp is 0), then we can return + // immediately. + static const char *header_comment_table[] = { + "DocumentNeededResources:", + "DocumentSuppliedResources:", + "DocumentNeededFonts:", + "DocumentSuppliedFonts:", + "DocumentNeededProcSets:", + "DocumentSuppliedProcSets:", + "DocumentNeededFiles:", + "DocumentSuppliedFiles:", + }; + + const int NHEADER_COMMENTS = sizeof(header_comment_table) + / sizeof(header_comment_table[0]); + struct comment_info { + const char *name; + int (resource_manager::*proc)(const char *, int, FILE *, FILE *); + }; + + static const comment_info comment_table[] = { + { "BeginResource:", &resource_manager::do_begin_resource }, + { "IncludeResource:", &resource_manager::do_include_resource }, + { "BeginDocument:", &resource_manager::do_begin_document }, + { "IncludeDocument:", &resource_manager::do_include_document }, + { "BeginProcSet:", &resource_manager::do_begin_procset }, + { "IncludeProcSet:", &resource_manager::do_include_procset }, + { "BeginFont:", &resource_manager::do_begin_font }, + { "IncludeFont:", &resource_manager::do_include_font }, + { "BeginFile:", &resource_manager::do_begin_file }, + { "IncludeFile:", &resource_manager::do_include_file }, + { "EndProcSet", &resource_manager::change_to_end_resource }, + { "EndFont", &resource_manager::change_to_end_resource }, + { "EndFile", &resource_manager::change_to_end_resource }, + { "BeginPreview:", &resource_manager::do_begin_preview }, + { "BeginData:", &resource_manager::do_begin_data }, + { "BeginBinary:", &resource_manager::do_begin_binary }, + }; + + const int NCOMMENTS = sizeof(comment_table)/sizeof(comment_table[0]); + string buf; + int saved_lineno = current_lineno; + const char *saved_filename = current_filename; + current_filename = filename; + current_lineno = 0; + if (!ps_get_line(buf, fp)) { + current_filename = saved_filename; + current_lineno = saved_lineno; + return; + } + if ((size_t)buf.length() < sizeof(PS_MAGIC) + || memcmp(buf.contents(), PS_MAGIC, sizeof(PS_MAGIC) - 1) != 0) { + if (outfp) { + do { + if (!(broken_flags & STRIP_PERCENT_BANG) + || buf[0] != '%' || buf[1] != '!') + fputs(buf.contents(), outfp); + } while (ps_get_line(buf, fp)); + } + } + else { + if (!(broken_flags & STRIP_PERCENT_BANG) && outfp) + fputs(buf.contents(), outfp); + int in_header = 1; + int interesting = 0; + int had_extensions_comment = 0; + int had_language_level_comment = 0; + for (;;) { + if (!ps_get_line(buf, fp)) + break; + int copy_this_line = 1; + if (buf[0] == '%') { + if (buf[1] == '%') { + const char *ptr; + int i; + for (i = 0; i < NCOMMENTS; i++) + if ((ptr = matches_comment(buf, comment_table[i].name))) { + copy_this_line + = (this->*(comment_table[i].proc))(ptr, rank, fp, outfp); + break; + } + if (i >= NCOMMENTS && in_header) { + if ((ptr = matches_comment(buf, "EndComments"))) + in_header = 0; + else if (!had_extensions_comment + && (ptr = matches_comment(buf, "Extensions:"))) { + extensions |= parse_extensions(ptr); + // XXX handle possibility that next line is %%+ + had_extensions_comment = 1; + } + else if (!had_language_level_comment + && (ptr = matches_comment(buf, "LanguageLevel:"))) { + unsigned ll; + if (read_uint_arg(&ptr, &ll) && ll > language_level) + language_level = ll; + had_language_level_comment = 1; + } + else { + for (i = 0; i < NHEADER_COMMENTS; i++) + if (matches_comment(buf, header_comment_table[i])) { + interesting = 1; + break; + } + } + } + if ((broken_flags & STRIP_STRUCTURE_COMMENTS) + && (matches_comment(buf, "EndProlog") + || matches_comment(buf, "Page:") + || matches_comment(buf, "Trailer"))) + copy_this_line = 0; + } + else if (buf[1] == '!') { + if (broken_flags & STRIP_PERCENT_BANG) + copy_this_line = 0; + } + } + else + in_header = 0; + if (!outfp && !in_header && !interesting) + break; + if (copy_this_line && outfp) + fputs(buf.contents(), outfp); + } + } + current_filename = saved_filename; + current_lineno = saved_lineno; +} + +void resource_manager::read_download_file() +{ + char *path = 0 /* nullptr */; + FILE *fp = font::open_file("download", &path); + if (0 /* nullptr */ == fp) + fatal("failed to open 'download' file: %1", strerror(errno)); + char buf[512]; + int lineno = 0; + while (fgets(buf, sizeof(buf), fp)) { + lineno++; + char *p = strtok(buf, " \t\r\n"); + if (p == 0 /* nullptr */ || *p == '#') + continue; + char *q = strtok(0 /* nullptr */, " \t\r\n"); + if (!q) + fatal_with_file_and_line(path, lineno, "file name missing for" + " font '%1'", p); + lookup_font(p)->filename = strsave(q); + } + free(path); + fclose(fp); +} + +// XXX Can we share some code with ps_output::put_string()? + +static void print_ps_string(const string &s, FILE *outfp) +{ + int len = s.length(); + const char *str = s.contents(); + int funny = 0; + if (str[0] == '(') + funny = 1; + else { + for (int i = 0; i < len; i++) + if (str[i] <= 040 || str[i] > 0176) { + funny = 1; + break; + } + } + if (!funny) { + put_string(s, outfp); + return; + } + int level = 0; + int i; + for (i = 0; i < len; i++) + if (str[i] == '(') + level++; + else if (str[i] == ')' && --level < 0) + break; + putc('(', outfp); + for (i = 0; i < len; i++) + switch (str[i]) { + case '(': + case ')': + if (level != 0) + putc('\\', outfp); + putc(str[i], outfp); + break; + case '\\': + fputs("\\\\", outfp); + break; + case '\n': + fputs("\\n", outfp); + break; + case '\r': + fputs("\\r", outfp); + break; + case '\t': + fputs("\\t", outfp); + break; + case '\b': + fputs("\\b", outfp); + break; + case '\f': + fputs("\\f", outfp); + break; + default: + if (str[i] < 040 || str[i] > 0176) + fprintf(outfp, "\\%03o", str[i] & 0377); + else + putc(str[i], outfp); + break; + } + putc(')', outfp); +} + +void resource_manager::print_extensions_comment(FILE *outfp) +{ + if (extensions) { + fputs("%%Extensions:", outfp); + for (int i = 0; i < NEXTENSIONS; i++) + if (extensions & (1 << i)) { + putc(' ', outfp); + fputs(extension_table[i], outfp); + } + putc('\n', outfp); + } +} + +void resource_manager::print_language_level_comment(FILE *outfp) +{ + if (language_level) + fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/devices/grotty/TODO b/src/devices/grotty/TODO new file mode 100644 index 0000000..3f23dc3 --- /dev/null +++ b/src/devices/grotty/TODO @@ -0,0 +1,3 @@ +Document font and device description file usage of grotty. + +With -h avoid using a tab when a single space will do. diff --git a/src/devices/grotty/grotty.1.man b/src/devices/grotty/grotty.1.man new file mode 100644 index 0000000..3dcafae --- /dev/null +++ b/src/devices/grotty/grotty.1.man @@ -0,0 +1,810 @@ +.TH grotty @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +grotty \- +.I groff +output driver for typewriter-like (terminal) devices +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright (C) 1989-2021 Free Software Foundation, Inc. +.\" +.\" Permission is granted to make and distribute verbatim copies of this +.\" manual provided the copyright notice and this permission notice are +.\" preserved on all copies. +.\" +.\" Permission is granted to copy and distribute modified versions of +.\" this manual under the conditions for verbatim copying, provided that +.\" the entire resulting derived work is distributed under the terms of +.\" a permission notice identical to this one. +.\" +.\" Permission is granted to copy and distribute translations of this +.\" manual into another language, under the above conditions for +.\" modified versions, except that this permission notice may be +.\" included in translations approved by the Free Software Foundation +.\" instead of in the original English. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_grotty_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY grotty +.RB [ \-dfho ] +.RB [ \-i | \-r ] +.RB [ \-F\~\c +.IR dir ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY "grotty \-c" +.RB [ \-bBdfhouU ] +.RB [ \-F\~\c +.IR dir ] +.RI [ file\~ .\|.\|.] +.YS +. +. +.SY grotty +.B \-\-help +.YS +. +. +.SY grotty +.B \-v +. +.SY grotty +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +The GNU +.I roff +TTY +(\[lq]Teletype\[rq]) +output driver translates the output of +.MR @g@troff @MAN1EXT@ +into a form suitable for typewriter-like devices, +including terminal emulators. +. +Normally, +.I grotty +is invoked by +.MR groff @MAN1EXT@ +when the latter is given one of the +.RB \[lq] \-T\~ascii \[rq], +.RB \[lq] \-T\~latin1 \[rq], +.BR \-Tlatin1 , +or +.RB \[lq] \-T\~utf8 \[rq] +options on systems using ISO character encoding standards, +or with +.RB \[lq] \-T\~cp1047 \[rq] +or +.RB \[lq] \-T\~utf8 \[rq] +on EBCDIC-based hosts. +. +(In this installation, +.B @DEVICE@ +is the default output device.) +. +Use +.IR groff 's +.B \-P +option to pass any options shown above to +.IR grotty . +. +If no +.I file +arguments are given, +or if +.I file +is \[lq]\-\[rq], +.I grotty +reads the standard input stream. +. +Output is written to the standard output stream. +. +. +.P +By default, +.I grotty +emits SGR escape sequences +(from ISO\~6429, +popularly called \[lq]ANSI escapes\[rq]) +to change text attributes +(bold, +italic, +underline, +reverse video +.\" ECMA-48, 2nd edition (1979) calls it "negative image". +[\[lq]negative image\[rq]] +and colors). +. +Devices supporting the appropriate sequences can view +.I roff +documents using eight different background and foreground colors. +. +Following ISO\~6429, +the following colors are defined in +.IR tty.tmac : +black, +white, +red, +green, +blue, +yellow, +magenta, +and cyan. +. +Unrecognized colors are mapped to the default color, +which is dependent on the settings of the terminal. +. +OSC\~8 hyperlinks are produced for these devices. +. +. +.P +In keeping with long-standing practice and the rarity of terminals +(and emulators) +that support oblique or italic fonts, +italicized text is represented with underlining by default\[em]but see +the +.B \-i +option below. +. +. +.\" ==================================================================== +.SS "SGR and OSC support in pagers" +.\" ==================================================================== +. +When paging +.IR grotty 's +output with +.MR less 1 , +the latter program must be instructed to pass SGR and OSC sequences +through to the device; +its +.B \-R +option is one way to achieve this +.RI ( less +version 566 or later is required for OSC\~8 support). +. +Consequently, +programs like +.MR man 1 +that page +.I roff +documents with +.I less +must call it with an appropriate option. +. +. +.\" ==================================================================== +.SS "Legacy output format" +.\" ==================================================================== +. +The +.B \-c +option tells +.I grotty +to use an output format compatible with paper terminals, +like the Teletype machines for which +.I roff +and +.I nroff +were first developed but which are no longer in wide use. +. +SGR escape sequences are not emitted; +bold, +italic, +and underlining character attributes are thus not manipulated. +. +Instead, +.I grotty +overstrikes, +representing a bold character +.I c +with the sequence +.RI \[lq] c\~\c +BACKSPACE\~\c +.IR c \[rq], +an italic character +.I c +with the sequence +.RB \[lq] _\~\c +BACKSPACE\~\c +.IR c \[rq], +and bold italics with +.RB \[lq] _\~\c +BACKSPACE\~\c +.I c +BACKSPACE\~\c +.IR c \[rq]. +. +This rendering is inherently ambiguous when the character +.I c +is itself the underscore. +. +. +.P +The legacy output format can be rendered on a video terminal +(or emulator) +by piping +.IR grotty 's +output through +.MR ul 1 , +.\" from bsdmainutils 11.1.2+b1 (on Debian Buster) +which may render bold italics as reverse video. +. +.\" 'more' from util-linux 2.33.1 (on Debian Buster) neither renders +.\" double-struck characters as bold nor supports -b, but does render +.\" SGR sequences (including color) with no flags required. +Some implementations of +.MR more 1 +are also able to display these sequences; +you may wish to experiment with that command's +.B \-b +option. +. +.\" Version 487 of... +.I less +renders legacy bold and italics without requiring options. +. +In contrast to the terminal output drivers of some other +.I roff +implementations, +.I grotty +never outputs reverse line feeds. +. +There is therefore no need to filter its output through +.MR col 1 . +. +. +.\" ==================================================================== +.SS "Device control commands" +.\" ==================================================================== +. +.I grotty +understands one device control function produced by the +.I roff +.B \[rs]X +escape sequence in a document. +. +. +.TP +.BR "\[rs]X\[aq]tty: link " [\c +.IR uri \~[ key\c +.BI = value\c +] \|.\|.\|.\|]\c +.B \[aq] +. +Embed a hyperlink using the OSC 8 terminal escape sequence. +. +Specifying +.I uri +starts hyperlinked text, +and omitting it ends the hyperlink. +. +When +.I uri +is present, +any number of additional key/value pairs can be specified; +their interpretation is the responsibility of the pager or terminal. +. +Spaces or tabs cannot appear literally in +.IR uri , +.IR key , +or +.IR value ; +they must be represented in an alternate form. +. +. +.\" ==================================================================== +.SS "Device description files" +.\" ==================================================================== +. +If the +.I DESC +file for the character encoding contains the +.RB \[lq] unicode \[rq] +directive, +.I grotty +emits Unicode characters in UTF-8 encoding. +. +Otherwise, +it emits characters in a single-byte encoding depending on the data in +the font description files. +. +See +.MR groff_font @MAN5EXT@ . +. +. +.P +A font description file may contain a directive +.RB \[lq] internalname\~\c +.IR n \[rq] +where +.I n +is a decimal integer. +. +If the 01 bit in +.I n +is set, +then the font is treated as an italic font; +if the 02 bit is set, +then it is treated as a bold font. +. +.\" The following seems to say nothing that is not true of font +.\" description files in general; if so, it belongs in groff_font(5). +.\"The code field in the font description field gives the code which is +.\"used to output the character. +.\". +.\"This code can also be used in the +.\".I groff +.\".B \[rs]N +.\"escape sequence in a document. +. +. +.\" ==================================================================== +.SS Typefaces +.\" ==================================================================== +. +.I grotty +supports the standard four styles: +.B R +(roman), +.B I +.RI ( italic ), +.B B +.RB ( bold ), +and +.B BI +(\f[BI]bold-italic\f[]). +. +Because the output driver operates in +.I nroff +mode, +attempts to set or change the font family or type size are ignored. +. +. +. +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-\-help +displays a usage message, +while +.B \-v +and +.B \-\-version +show version information; +all exit afterward. +. +. +.TP +.B \-b +Suppress the use of overstriking for bold characters in legacy output +format. +. +. +.TP +.B \-B +Use only overstriking for bold-italic characters in legacy output +format. +. +. +.TP +.B \-c +Use +.IR grotty 's +legacy output format +(see subsection \[lq]Legacy output format\[rq] above). +. +SGR and OSC escape sequences are not emitted. +. +. +.TP +.B \-d +Ignore all +.B \[rs]D +drawing escape sequences in the input. +. +By default, +.I grotty +renders +.BR \[rs]D\[aq]l \|.\|.\|.\& \[aq] +escape sequences that have at least one zero argument +(and so are either horizontal or vertical) +using Unicode box drawing characters +(for the +.B utf8 +device) +or the +.BR \- , +.BR | , +and +.B + +characters +(for all other devices). +. +.I grotty +handles +.BR \[rs]D\[aq]p \|.\|.\|.\& \[aq] +escape sequences that consist entirely of horizontal and vertical +lines similarly. +. +. +.TP +.B \-f +Emit a form feed at the end of each page having no output on its last +line. +. +. +.TP +.BI \-F\~ dir +Prepend directory +.RI dir /dev name +to the search path for font and device description files; +.I name +describes the output device's character encoding, +one of +.BR ascii , +.BR latin1 , +.BR utf8 , +or +.BR cp1047 . +. +. +.TP +.B \-h +Use literal horizontal tab characters in the output. +. +Tabs are assumed to be set every 8 columns. +. +. +.TP +.B \-i +Render oblique-styled fonts +.RB ( I +and +.BR BI ) +with the SGR attribute for italic text +rather than underlined text. +. +Many terminals don't support this attribute; +however, +.MR xterm 1 , +since patch\~#314 (2014-12-28), +does. +. +Ignored if +.B \-c +is also specified. +. +. +.TP +.B \-o +Suppress overstriking +(other than for bold and/or underlined characters when the legacy output +format is in use). +. +. +.TP +.B \-r +Render oblique-styled fonts +.RB ( I +and +.BR BI ) +with the SGR attribute for reverse video text +rather than underlined text. +. +Ignored if +.B \-c +or +.B \-i +is also specified. +. +. +.TP +.B \-u +Suppress the use of underlining for italic characters in legacy output +format. +. +. +.TP +.B \-U +Use only underlining for bold-italic characters in legacy output format. +. +. +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +A list of directories in which to seek the selected output device's +directory of device and font description files. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.TP +.I GROFF_NO_SGR +If set, +.IR grotty 's +legacy output format is used just as if the +.B \-c +option were specified; +see subsection \[lq]Legacy output format\[rq] above. +. +. +.br +.ne 3v \" Keep section heading and paragraph tag together. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @FONTDIR@/\:\%devascii/\:DESC +describes the +.B ascii +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devascii/ F +describes the font known +.RI as\~ F +on device +.BR ascii . +. +. +.TP +.I @FONTDIR@/\:\%devcp1047/\:DESC +describes the +.B cp1047 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devcp1047/ F +describes the font known +.RI as\~ F +on device +.BR cp1047 . +. +. +.TP +.I @FONTDIR@/\:\%devlatin1/\:DESC +describes the +.B latin1 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devlatin1/ F +describes the font known +.RI as\~ F +on device +.BR latin1 . +. +. +.TP +.I @FONTDIR@/\:\%devutf8/\:DESC +describes the +.B utf8 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devutf8/ F +describes the font known +.RI as\~ F +on device +.BR utf8 . +. +. +.TP +.I @MACRODIR@/\:tty\:.tmac +defines macros for use with the +.BR ascii , +.BR cp1047 , +.BR latin1 , +and +.B utf8 +output devices. +. +It is automatically loaded by +.I troffrc +when any of those output devices is selected. +. +. +.TP +.I @MACRODIR@/\:tty\-char\:.tmac +defines fallback characters for use with +.I grotty. +. +See +.MR nroff @MAN1EXT@ . +. +. +.\" XXX: The following no longer seems to be true; an inspection of the +.\" font/*/dev*.am files suggests no evidence of it, at any rate. +.\".P +.\"Note that on EBCDIC hosts, +.\"only files for the +.\".B cp1047 +.\"device are installed. +. +. +.\" ==================================================================== +.SH Limitations +.\" ==================================================================== +. +.I grotty +is intended only for simple documents. +. +. +.IP \[bu] 2n +There is no support for fractional horizontal or vertical motions. +. +. +.IP \[bu] +.I roff +.B \[rs]D +escape sequences producing anything other than horizontal and vertical +lines are not supported. +. +. +.IP \[bu] +Characters above the first line +(that is, +with a vertical drawing position of\~0) +cannot be rendered. +. +. +.IP \[bu] +Color handling differs from other output drivers. +. +The +.I groff +requests and escape sequences that set the stroke and fill colors +instead set the foreground and background character cell colors, +respectively. +. +. +.\" ==================================================================== +.SH Examples +.\" ==================================================================== +. +The following +.I groff +document exercises several features for which output device support +varies: +(1)\~bold style; +(2)\~italic (underline) style; +(3)\~bold-italic style; +(4)\~character composition by overstriking (\[lq]co\[:o]perate\[rq]); +(5)\~foreground color; +(6)\~background color; +and +(7)\~horizontal and vertical line-drawing. +. +. +.P +.RS +.EX +You might see \ef[B]bold\ef[] and \ef[I]italic\ef[]. +Some people see \ef[BI]both\ef[]. +If the output device does (not) co\ez\e[ad]operate, +you might see \em[red]red\em[]. +Black on cyan can have a \eM[cyan]\em[black]prominent\em[]\eM[] +\eD\[aq]l 1i 0\[aq]\eD\[aq]l 0 2i\[aq]\eD\[aq]l 1i 0\[aq] look. +\&.\e" If in nroff mode, end page now. +\&.if n .pl \en[nl]u +.EE +.RE +. +. +.P +Given the foregoing input, +compare and contrast the output of the following. +. +. +.P +.RS +.EX +$ \c +.B groff \-T ascii \c +.I file +$ \c +.B groff \-T utf8 \-P \-i \c +.I file +$ \c +.B groff \-T utf8 \-P \-c \c +.I file \c +.B | ul +.EE +.RE +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +\[lq]Control Functions for Coded Character Sets\[rq] +(ECMA-48) +5th\~edition, +Ecma International, +June\~1991. +. +A gratis version of ISO\~6429, +this document includes a normative description of SGR escape sequences. +. +Available at +.UR http://\:www\:.ecma\-international\:.org/\:publications/\:files/\:\ +ECMA\-ST/\:Ecma\-048\:.pdf +.UE . +. +. +.P +.UR https://\:gist\:.github\:.com/\:egmontkob/\:\ +eb114294efbcd5ad\:b1944c9f3cb5feda +\[lq]Hyperlinks in Terminal Emulators\[rq] +.UE , +Egmont Koblinger. +. +. +.P +.MR groff @MAN1EXT@ , +.MR @g@troff @MAN1EXT@ , +.MR groff_out @MAN5EXT@ , +.MR groff_font @MAN5EXT@ , +.MR groff_char @MAN7EXT@ , +.MR ul 1 , +.MR more 1 , +.MR less 1 , +.MR man 1 +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_grotty_1_man_C] +.do rr *groff_grotty_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/grotty/grotty.am b/src/devices/grotty/grotty.am new file mode 100644 index 0000000..14921c5 --- /dev/null +++ b/src/devices/grotty/grotty.am @@ -0,0 +1,39 @@ +# Copyright (C) 2014-2021 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +bin_PROGRAMS += grotty +grotty_SOURCES = src/devices/grotty/tty.cpp +grotty_LDADD = $(LIBM) \ + libdriver.a \ + libgroff.a \ + lib/libgnu.a +man1_MANS += src/devices/grotty/grotty.1 +EXTRA_DIST += \ + src/devices/grotty/grotty.1.man \ + src/devices/grotty/TODO + +grotty_TESTS = \ + src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh \ + src/devices/grotty/tests/osc8_works.sh +TESTS += $(grotty_TESTS) +EXTRA_DIST += $(grotty_TESTS) + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh b/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh new file mode 100755 index 0000000..92552d2 --- /dev/null +++ b/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh @@ -0,0 +1,205 @@ +#!/bin/sh +# +# Copyright (C) 2022 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. +# + +grotty="${abs_top_builddir:-.}/grotty" + +fail= + +wail () { + printf "FAILED " >&2 + fail=YES +} + +# Ensure that characters are mapped to glyphs normatively. + +input='x T @DEVICE@ +x res 240 24 40 +x init +p1 +x font 1 R +f1 +s10 +V40 +H0 +md +DFd +t!#$%&()*+,./0123456789:;<=>?@ +n40 0 +V80 +H0 +tABCDEFGHIJKLMNOPQRSTUVWXYZ[]_ +n40 0 +V120 +H0 +tabcdefghijklmnopqrstuvwxyz{|} +n40 0 +V160 +H0 +tneutral +wh24 +tdouble +wh24 +tquote: +wh24 +Cdq +h24 +n40 0 +V200 +H0 +tclosing +wh24 +tsingle +wh24 +tquote: +wh24 +t'"'"' +n40 0 +V240 +H0 +thyphen: +wh24 +t- +n40 0 +V280 +H0 +tbackslash: +wh24 +Crs +h24 +n40 0 +V320 +H0 +tmodifier +wh24 +tcircumflex: +wh24 +t^ +n40 0 +V360 +H0 +topening +wh24 +tsingle +wh24 +tquote: +wh24 +t` +n40 0 +V400 +H0 +tmodifier +wh24 +ttilde: +wh24 +t~ +n40 0 +x trailer +V2640 +x stop' + +# TODO: Test cp1047 when we have access to a host environment using it. + +for D in ascii latin1 utf8 +do + if [ "$D" = "utf8" ] + then + # We can't test UTF-8 if the environment doesn't support it. + if [ "$(locale charmap)" != UTF-8 ] + then + # If we've already seen a failure case, report it. + if [ -n "$fail" ] + then + exit 1 # fail + else + exit 77 # skip + fi + fi + fi + + printf 'checking "%s" output device...' $D >&2 + output=$(echo "$input" | sed s/@DEVICE@/$D/ \ + | "$grotty" -F font -F build/font) + printf 'group1 ' >&2 + echo "$output" | grep -Fqx '!#$%&()*+,./0123456789:;<=>?@' || wail + printf 'group2 ' >&2 + echo "$output" | grep -Fqx 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_' || wail + printf 'group3 ' >&2 + echo "$output" | grep -Fqx 'abcdefghijklmnopqrstuvwxyz{|}' || wail + printf '" ' >&2 + echo "$output" | grep -Fqx 'neutral double quote: "' || wail + printf '\\ ' >&2 + echo "$output" | grep -Fqx 'backslash: \' || wail + case $D in + (utf8) +# Expected: +#0000000 ! # $ % & ( ) * + , . / 0 1 2 3 +#0000020 4 5 6 7 8 9 : ; < = > ? @ \n A B +#0000040 C D E F G H I J K L M N O P Q R +#0000060 S T U V W X Y Z [ ] _ \n a b c d +#0000100 e f g h i j k l m n o p q r s t +#0000120 u v w x y z { | } \n n e u t r a +#0000140 l d o u b l e q u o t e : +#0000160 " \n c l o s i n g s i n g l e +#0000200 q u o t e : 342 200 231 \n h y p h +#0000220 e n : 342 200 220 \n b a c k s l a s +#0000240 h : \ \n m o d i f i e r c i +#0000260 r c u m f l e x : 313 206 \n o p e +#0000300 n i n g s i n g l e q u o t +#0000320 e : 342 200 230 \n m o d i f i e r +#0000340 t i l d e : 313 234 \n +#0000352 + output_od=$(echo "$output" | LC_ALL=C od -t c) + printf "' " >&2 + printf '%s\n' "$output_od" \ + | grep -Eq '0000200 +q +u +o +t +e +: +342 +200 +231' \ + || wail + printf '` ' >&2 + printf '%s\n' "$output_od" \ + | grep -Eq '0000320 +e +: +342 +200 +230' || wail + printf "%s " '-' >&2 + printf '%s\n' "$output_od" \ + | grep -Eq '0000220 +e +n +: +342 +200 +220' || wail + printf '^ ' >&2 + printf '%s\n' "$output_od" \ + | grep -Eq '0000260 +r +c +u +m +f +l +e +x +: +313 +206' \ + || wail + printf "~ " >&2 + printf '%s\n' "$output_od" \ + | grep -Eq '0000340 +t +i +l +d +e +: +313 +234' || wail + ;; + (*) + printf '` ' >&2 + echo "$output" | grep -Fqx 'opening single quote: `' || wail + printf "' " >&2 + echo "$output" | grep -Fqx "closing single quote: '" || wail + printf "%s " '-' >&2 + echo "$output" | grep -Fqx "hyphen: -" || wail + printf '^ ' >&2 + echo "$output" | grep -Fqx "modifier circumflex: ^" || wail + printf "~ " >&2 + echo "$output" | grep -Fqx "modifier tilde: ~" || wail + ;; + esac + echo >&2 +done + +test -z "$fail" + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/devices/grotty/tests/osc8_works.sh b/src/devices/grotty/tests/osc8_works.sh new file mode 100755 index 0000000..db6480d --- /dev/null +++ b/src/devices/grotty/tests/osc8_works.sh @@ -0,0 +1,119 @@ +#!/bin/sh +# +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. +# + +set -e + +grotty="${abs_top_builddir:-.}/grotty" + +input="x T utf8 +x res 240 24 40 +x init +p1 +x font 1 R +f1 +s10 +V40 +H0 +md +DFd +tA +n40 0 +x X tty: link +x X tty: link h +x X tty: link http://example.com/1 +x X tty: link +x X tty: link http://example.com/2 +tB +x X tty: link +x X tty: link mailto:g.branden.robinson@gmail.com +tBranden +x X tty: link +x trailer +V2640 +x stop" + +# We expect diagnostics from the first few "x X tty: link" lines. The +# first should complain about a link ending without having been started. +# The second is bogus ("h") but it's not grotty's job to validate the +# structure of a URI. The third should draw complaint because we didn't +# end the (bogus) URI that we started with the second. + +# The remaining input is well-formed. The URI ending in "1" is +# effectively hidden because no character cells are drawn while it is +# active. +echo "expect two diagnostic messages regarding ill-formed links" >&2 +output=$(echo "$input" | "$grotty" -F font -F build/font | od -t c) + +# Expected: +#0000000 A 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h 033 \ +#0000020 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t t p +#0000040 : / / e x a m p l e . c o m / 1 +#0000060 033 \ 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t +#0000100 t p : / / e x a m p l e . c o m +#0000120 / 2 033 \ B 033 ] 8 ; ; 033 \ 033 ] 8 ; +#0000140 ; m a i l t o : g . b r a n d e +#0000160 n . r o b i n s o n @ g m a i l +#0000200 . c o m 033 \ B r a n d e n 033 ] 8 +#0000220 ; ; 033 \ \n \n \n \n \n \n \n \n \n \n \n \n +#0000240 \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n +#* +#0000320 \n \n \n \n \n \n +#0000326 + +echo "testing for URI that corresponds to no character cells" >&2 +echo "$output" | grep -Eq 'A 033 +] +8 +; +; +033 +\\' + +echo "testing http URI (1)" >&2 +echo "$output" | grep -Eq '0000020 +.*033 +] +8 +; +; +h + t +t +p' + +echo "testing http URI (2)" >&2 +echo "$output" | grep -Eq '0000040 +: +/ +/ +e +x +a +m +p +l +e +\. +c' + +echo "testing http URI (3)" >&2 +echo "$output" | grep -Eq '0000040.* +o +m +/ +1' + +echo "testing http URI (4)" >&2 +echo "$output" | grep -Eq '0000060 +033 +\\' + +echo "testing mailto URI (1)" >&2 +echo "$output" | grep -Eq '0000120 +.* +033 +] +8 +;$' + +echo "testing mailto URI (2)" >&2 +echo "$output" | grep -Eq '0000140 +; +m +a +i +l +t +o +: +g +\. +b' + +echo "testing mailto URI (3)" >&2 +echo "$output" | grep -Eq '0000140.* +r +a +n +d +e$' + +echo "testing mailto URI (4)" >&2 +echo "$output" | grep -Eq '0000160 +n +\. +r +o +b +i +n +s +o +n +@' + +echo "testing mailto URI (5)" >&2 +echo "$output" | grep -Eq '0000160.* +g +m +a +i +l$' + +echo "testing mailto URI (6)" >&2 +echo "$output" | grep -Eq '0000200 +\. +c +o +m +033 +\\ +B +r +a +n +d' + +echo "testing mailto URI (7)" >&2 +echo "$output" | grep -Eq '0000200.* +e +n +033 +] +8$' + +echo "testing mailto URI (8)" >&2 +echo "$output" | grep -Eq '0000220 +; +; +033 +\\' + +# vim:set ai et sw=4 ts=4 tw=72: diff --git a/src/devices/grotty/tty.cpp b/src/devices/grotty/tty.cpp new file mode 100644 index 0000000..45926f6 --- /dev/null +++ b/src/devices/grotty/tty.cpp @@ -0,0 +1,1043 @@ +/* Copyright (C) 1989-2021 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + OSC 8 support by G. Branden Robinson + +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 <http://www.gnu.org/licenses/>. */ + +#include "driver.h" +#include "device.h" +#include "ptable.h" + +typedef signed char schar; + +declare_ptable(schar) +implement_ptable(schar) + +extern "C" const char *Version_string; + +#define putstring(s) fputs(s, stdout) + +#ifndef SHRT_MIN +#define SHRT_MIN (-32768) +#endif + +#ifndef SHRT_MAX +#define SHRT_MAX 32767 +#endif + +#define TAB_WIDTH 8 + +// A character of the output device fits in a 32-bit word. +typedef unsigned int output_character; + +static bool want_horizontal_tabs = false; +static bool want_form_feeds = false; +static bool want_emboldening_by_overstriking = true; +static bool do_bold; +static bool want_italics_by_underlining = true; +static bool do_underline; +static bool want_glyph_composition_by_overstriking = true; +static bool allow_drawing_commands = true; +static bool want_sgr_italics = false; +static bool do_sgr_italics; +static bool want_reverse_video_for_italics = false; +static bool do_reverse_video; +static bool use_overstriking_drawing_scheme = false; + +static void update_options(); +static void usage(FILE *stream); + +static int hline_char = '-'; +static int vline_char = '|'; + +enum { + UNDERLINE_MODE = 0x01, + BOLD_MODE = 0x02, + VDRAW_MODE = 0x04, + HDRAW_MODE = 0x08, + CU_MODE = 0x10, + COLOR_CHANGE = 0x20, + START_LINE = 0x40, + END_LINE = 0x80 +}; + +// Mode to use for bold-underlining. +static unsigned char bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE; +static unsigned char bold_underline_mode; + +#ifndef IS_EBCDIC_HOST +#define CSI "\033[" +#define OSC8 "\033]8" +#define ST "\033\\" +#else +#define CSI "\047[" +#define OSC8 "\047]8" +#define ST "\047\\" +#endif + +// SGR handling (ISO 6429) +#define SGR_BOLD CSI "1m" +#define SGR_NO_BOLD CSI "22m" +#define SGR_ITALIC CSI "3m" +#define SGR_NO_ITALIC CSI "23m" +#define SGR_UNDERLINE CSI "4m" +#define SGR_NO_UNDERLINE CSI "24m" +#define SGR_REVERSE CSI "7m" +#define SGR_NO_REVERSE CSI "27m" +// many terminals can't handle 'CSI 39 m' and 'CSI 49 m' to reset +// the foreground and background color, respectively; we thus use +// 'CSI 0 m' exclusively +#define SGR_DEFAULT CSI "0m" + +#define DEFAULT_COLOR_IDX -1 + +class tty_font : public font { + tty_font(const char *); + unsigned char mode; +public: + ~tty_font(); + unsigned char get_mode() { return mode; } +#if 0 + void handle_x_command(int argc, const char **argv); +#endif + static tty_font *load_tty_font(const char *); +}; + +tty_font *tty_font::load_tty_font(const char *s) +{ + tty_font *f = new tty_font(s); + if (!f->load()) { + delete f; + return 0; + } + const char *num = f->get_internal_name(); + long n; + if (num != 0 && (n = strtol(num, 0, 0)) != 0) + f->mode = (unsigned char)(n & (BOLD_MODE|UNDERLINE_MODE)); + if (!do_underline) + f->mode &= ~UNDERLINE_MODE; + if (!do_bold) + f->mode &= ~BOLD_MODE; + if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE)) + f->mode = (unsigned char)((f->mode & ~(BOLD_MODE|UNDERLINE_MODE)) + | bold_underline_mode); + return f; +} + +tty_font::tty_font(const char *nm) +: font(nm), mode(0) +{ +} + +tty_font::~tty_font() +{ +} + +#if 0 +void tty_font::handle_x_command(int argc, const char **argv) +{ + if (argc >= 1 && strcmp(argv[0], "bold") == 0) + mode |= BOLD_MODE; + else if (argc >= 1 && strcmp(argv[0], "underline") == 0) + mode |= UNDERLINE_MODE; +} +#endif + +class tty_glyph { +public: + tty_glyph *next; + int w; + int hpos; + unsigned int code; + unsigned char mode; + schar back_color_idx; + schar fore_color_idx; + inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); } + inline int order() { + return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); } +}; + + +class tty_printer : public printer { + tty_glyph **lines; + int nlines; + int cached_v; + int cached_vpos; + schar curr_fore_idx; + schar curr_back_idx; + bool is_underlining; + bool is_boldfacing; + bool is_continuously_underlining; + PTABLE(schar) tty_colors; + void make_underline(int); + void make_bold(output_character, int); + schar color_to_idx(color *); + void add_char(output_character, int, int, int, color *, color *, + unsigned char); + void simple_add_char(const output_character, const environment *); + char *make_rgb_string(unsigned int, unsigned int, unsigned int); + bool tty_color(unsigned int, unsigned int, unsigned int, schar *, + schar = DEFAULT_COLOR_IDX); + void line(int, int, int, int, color *, color *); + void draw_line(int *, int, const environment *); + void draw_polygon(int *, int, const environment *); + void special_link(const char *, const environment *); +public: + tty_printer(); + ~tty_printer(); + void set_char(glyph *, font *, const environment *, int, const char *); + void draw(int, int *, int, const environment *); + void special(char *, const environment *, char); + void change_color(const environment * const); + void change_fill_color(const environment * const); + void put_char(output_character); + void put_color(schar, int); + void begin_page(int) { } + void end_page(int); + font *make_font(const char *); +}; + +char *tty_printer::make_rgb_string(unsigned int r, + unsigned int g, + unsigned int b) +{ + char *s = new char[8]; + s[0] = char(r >> 8); + s[1] = char(r & 0xff); + s[2] = char(g >> 8); + s[3] = char(g & 0xff); + s[4] = char(b >> 8); + s[5] = char(b & 0xff); + s[6] = char(0x80); + s[7] = 0; + // avoid null-bytes in string + for (int i = 0; i < 6; i++) + if (!s[i]) { + s[i] = 1; + s[6] |= 1 << i; + } + return s; +} + +bool tty_printer::tty_color(unsigned int r, + unsigned int g, + unsigned int b, schar *idx, schar value) +{ + bool is_known_color = true; + char *s = make_rgb_string(r, g, b); + schar *i = tty_colors.lookup(s); + if (!i) { + is_known_color = false; + i = new schar[1]; + *i = value; + tty_colors.define(s, i); + } + *idx = *i; + delete[] s; + return is_known_color; +} + +tty_printer::tty_printer() : cached_v(0) +{ + if (font::is_unicode) { + hline_char = 0x2500; + vline_char = 0x2502; + } + schar dummy; + // black, white + (void)tty_color(0, 0, 0, &dummy, 0); + (void)tty_color(color::MAX_COLOR_VAL, + color::MAX_COLOR_VAL, + color::MAX_COLOR_VAL, &dummy, 7); + // red, green, blue + (void)tty_color(color::MAX_COLOR_VAL, 0, 0, &dummy, 1); + (void)tty_color(0, color::MAX_COLOR_VAL, 0, &dummy, 2); + (void)tty_color(0, 0, color::MAX_COLOR_VAL, &dummy, 4); + // yellow, magenta, cyan + (void)tty_color(color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, 0, &dummy, 3); + (void)tty_color(color::MAX_COLOR_VAL, 0, color::MAX_COLOR_VAL, &dummy, 5); + (void)tty_color(0, color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, &dummy, 6); + nlines = 66; + lines = new tty_glyph *[nlines]; + for (int i = 0; i < nlines; i++) + lines[i] = 0; + is_continuously_underlining = false; +} + +tty_printer::~tty_printer() +{ + delete[] lines; +} + +void tty_printer::make_underline(int w) +{ + if (use_overstriking_drawing_scheme) { + if (!w) + warning("can't underline zero-width character"); + else { + putchar('_'); + putchar('\b'); + } + } + else { + if (!is_underlining) { + if (do_sgr_italics) + putstring(SGR_ITALIC); + else if (do_reverse_video) + putstring(SGR_REVERSE); + else + putstring(SGR_UNDERLINE); + } + is_underlining = true; + } +} + +void tty_printer::make_bold(output_character c, int w) +{ + if (use_overstriking_drawing_scheme) { + if (!w) + warning("can't print zero-width character in bold"); + else { + put_char(c); + putchar('\b'); + } + } + else { + if (!is_boldfacing) + putstring(SGR_BOLD); + is_boldfacing = true; + } +} + +schar tty_printer::color_to_idx(color *col) +{ + if (col->is_default()) + return DEFAULT_COLOR_IDX; + unsigned int r, g, b; + col->get_rgb(&r, &g, &b); + schar idx; + if (!tty_color(r, g, b, &idx)) { + char *s = col->print_color(); + error("unrecognized color '%1' mapped to default", s); + delete[] s; + } + return idx; +} + +void tty_printer::set_char(glyph *g, font *f, const environment *env, + int w, const char *) +{ + if (w % font::hor != 0) + fatal("glyph width is not a multiple of horizontal motion quantum"); + add_char(f->get_code(g), w, + env->hpos, env->vpos, + env->col, env->fill, + ((tty_font *)f)->get_mode()); +} + +void tty_printer::add_char(output_character c, int w, + int h, int v, + color *fore, color *back, + unsigned char mode) +{ +#if 0 + // This is too expensive. + if (h % font::hor != 0) + fatal("horizontal position not a multiple of horizontal motion quantum"); +#endif + int hpos = h / font::hor; + if (hpos < SHRT_MIN || hpos > SHRT_MAX) { + error("character with ridiculous horizontal position discarded"); + return; + } + int vpos; + if (v == cached_v && cached_v != 0) + vpos = cached_vpos; + else { + if (v % font::vert != 0) + fatal("vertical position not a multiple of vertical motion" + " quantum"); + vpos = v / font::vert; + if (vpos > nlines) { + tty_glyph **old_lines = lines; + lines = new tty_glyph *[vpos + 1]; + memcpy(lines, old_lines, nlines * sizeof(tty_glyph *)); + for (int i = nlines; i <= vpos; i++) + lines[i] = 0; + delete[] old_lines; + nlines = vpos + 1; + } + // Note that the first output line corresponds to groff + // position font::vert. + if (vpos <= 0) { + error("output above first line discarded"); + return; + } + cached_v = v; + cached_vpos = vpos; + } + tty_glyph *g = new tty_glyph; + g->w = w; + g->hpos = hpos; + g->code = c; + g->fore_color_idx = color_to_idx(fore); + g->back_color_idx = color_to_idx(back); + g->mode = mode; + + // The list will be reversed later. After reversal, it must be in + // increasing order of hpos, with COLOR_CHANGE and CU specials before + // HDRAW characters before VDRAW characters before normal characters + // at each hpos, and otherwise in order of occurrence. + + tty_glyph **pp; + for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next) + if ((*pp)->hpos < hpos + || ((*pp)->hpos == hpos && (*pp)->order() >= g->order())) + break; + g->next = *pp; + *pp = g; +} + +void tty_printer::simple_add_char(const output_character c, + const environment *env) +{ + add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0); +} + +void tty_printer::special(char *arg, const environment *env, char type) +{ + if (type == 'u') { + add_char(*arg - '0', 0, env->hpos, env->vpos, env->col, env->fill, + CU_MODE); + return; + } + if (type != 'p') + return; + char *p; + for (p = arg; *p == ' ' || *p == '\n'; p++) + ; + char *tag = p; + for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++) + ; + if (*p == '\0' || strncmp(tag, "tty", p - tag) != 0) { + error("X command without 'tty:' tag ignored"); + return; + } + p++; + for (; *p == ' ' || *p == '\n'; p++) + ; + char *command = p; + for (; *p != '\0' && *p != ' ' && *p != '\n'; p++) + ; + if (*command == '\0') { + error("empty X command ignored"); + return; + } + if (strncmp(command, "link", p - command) == 0) + special_link(p, env); + else + warning("unrecognized X command '%1' ignored", command); +} + +// Produce an OSC 8 hyperlink. Given ditroff input of the form: +// x X tty: link [URI[ KEY=VALUE] ...] +// produce "OSC 8 [;KEY=VALUE];[URI] ST". KEY/VALUE pairs can be +// repeated arbitrarily and are separated by colons. Omission of the +// URI ends the hyperlink that was begun by specifying it. See +// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>. +void tty_printer::special_link(const char *arg, const environment *env) +{ + static bool is_link_active = false; + if (use_overstriking_drawing_scheme) + return; + for (const char *s = OSC8; *s != '\0'; s++) + simple_add_char(*s, env); + simple_add_char(';', env); + char c = *arg; + if ('\0' == c || '\n' == c) { + simple_add_char(';', env); + if (!is_link_active) + warning("ending hyperlink when none was started"); + is_link_active = false; + } + else { + // Our caller ensures that we see white space after 'link'. + assert(c == ' ' || c == '\t'); + if (is_link_active) { + warning("new hyperlink started without ending previous one;" + " recovering"); + simple_add_char(';', env); + for (const char *s = ST OSC8; *s != '\0'; s++) + simple_add_char(*s, env); + simple_add_char(';', env); + } + is_link_active = true; + do + c = *arg++; + while (c == ' ' || c == '\t'); + arg--; + // The first argument is the URI. + const char *uri = arg; + do + c = *arg++; + while (c != '\0' && c != ' ' && c != '\t'); + arg--; + ptrdiff_t uri_len = arg - uri; + // Any remaining arguments are "key=value" pairs. + const char *pair = 0; + bool done = false; + do { + if (pair != 0) + simple_add_char(':', env); + pair = arg; + bool in_pair = true; + do { + c = *arg++; + if ('\0' == c) + done = true; + else if (' ' == c || '\t' == c) + in_pair = false; + else + simple_add_char(c, env); + } while (!done && in_pair); + } while (!done); + simple_add_char(';', env); + for (size_t i = uri_len; i > 0; i--) + simple_add_char(*uri++, env); + } + for (const char *s = ST; *s != '\0'; s++) + simple_add_char(*s, env); +} + +void tty_printer::change_color(const environment * const env) +{ + add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE); +} + +void tty_printer::change_fill_color(const environment * const env) +{ + add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE); +} + +void tty_printer::draw(int code, int *p, int np, const environment *env) +{ + if (!allow_drawing_commands) + return; + if (code == 'l') + draw_line(p, np, env); + else if (code == 'p') + draw_polygon(p, np, env); + else + warning("ignoring unsupported drawing command '%1'", char(code)); +} + +void tty_printer::draw_polygon(int *p, int np, const environment *env) +{ + if (np & 1) { + error("even number of arguments required for polygon"); + return; + } + if (np == 0) { + error("no arguments for polygon"); + return; + } + // We only draw polygons which consist entirely of horizontal and + // vertical lines. + int hpos = 0; + int vpos = 0; + for (int i = 0; i < np; i += 2) { + if (!(p[i] == 0 || p[i + 1] == 0)) + return; + hpos += p[i]; + vpos += p[i + 1]; + } + if (!(hpos == 0 || vpos == 0)) + return; + int start_hpos = env->hpos; + int start_vpos = env->vpos; + hpos = start_hpos; + vpos = start_vpos; + for (int i = 0; i < np; i += 2) { + line(hpos, vpos, p[i], p[i + 1], env->col, env->fill); + hpos += p[i]; + vpos += p[i + 1]; + } + line(hpos, vpos, start_hpos - hpos, start_vpos - vpos, + env->col, env->fill); +} + +void tty_printer::draw_line(int *p, int np, const environment *env) +{ + if (np != 2) { + error("2 arguments required for line"); + return; + } + line(env->hpos, env->vpos, p[0], p[1], env->col, env->fill); +} + +void tty_printer::line(int hpos, int vpos, int dx, int dy, + color *col, color *fill) +{ + // XXX: zero-length lines get drawn as '+' crossings in nroff, even + // when there is no crossing, but they nevertheless occur frequently + // in input. Does tbl produce them? +#if 0 + if (0 == dx) + fatal("cannot draw zero-length horizontal line"); + if (0 == dy) + fatal("cannot draw zero-length vertical line"); +#endif + if ((dx != 0) && (dy != 0)) + warning("cannot draw diagonal line"); + if (dx % font::hor != 0) + fatal("length of horizontal line %1 is not a multiple of horizontal" + " motion quantum %2", dx, font::hor); + if (dy % font::vert != 0) + fatal("length of vertical line %1 is not a multiple of vertical" + " motion quantum %2", dy, font::vert); + if (dx == 0) { + // vertical line + int v = vpos; + int len = dy; + if (len < 0) { + v += len; + len = -len; + } + if (len == 0) + add_char(vline_char, font::hor, hpos, v, col, fill, + VDRAW_MODE|START_LINE|END_LINE); + else { + add_char(vline_char, font::hor, hpos, v, col, fill, + VDRAW_MODE|START_LINE); + len -= font::vert; + v += font::vert; + while (len > 0) { + add_char(vline_char, font::hor, hpos, v, col, fill, + VDRAW_MODE|START_LINE|END_LINE); + len -= font::vert; + v += font::vert; + } + add_char(vline_char, font::hor, hpos, v, col, fill, + VDRAW_MODE|END_LINE); + } + } + if (dy == 0) { + // horizontal line + int h = hpos; + int len = dx; + if (len < 0) { + h += len; + len = -len; + } + if (len == 0) + add_char(hline_char, font::hor, h, vpos, col, fill, + HDRAW_MODE|START_LINE|END_LINE); + else { + add_char(hline_char, font::hor, h, vpos, col, fill, + HDRAW_MODE|START_LINE); + len -= font::hor; + h += font::hor; + while (len > 0) { + add_char(hline_char, font::hor, h, vpos, col, fill, + HDRAW_MODE|START_LINE|END_LINE); + len -= font::hor; + h += font::hor; + } + add_char(hline_char, font::hor, h, vpos, col, fill, + HDRAW_MODE|END_LINE); + } + } +} + +void tty_printer::put_char(output_character wc) +{ + if (font::is_unicode && wc >= 0x80) { + char buf[6 + 1]; + int count; + char *p = buf; + if (wc < 0x800) + count = 1, *p = (unsigned char)((wc >> 6) | 0xc0); + else if (wc < 0x10000) + count = 2, *p = (unsigned char)((wc >> 12) | 0xe0); + else if (wc < 0x200000) + count = 3, *p = (unsigned char)((wc >> 18) | 0xf0); + else if (wc < 0x4000000) + count = 4, *p = (unsigned char)((wc >> 24) | 0xf8); + else if (wc <= 0x7fffffff) + count = 5, *p = (unsigned char)((wc >> 30) | 0xfC); + else + return; + do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80); + while (count > 0); + *++p = '\0'; + putstring(buf); + } + else + putchar(wc); +} + +void tty_printer::put_color(schar color_index, int back) +{ + if (color_index == DEFAULT_COLOR_IDX) { + putstring(SGR_DEFAULT); + // set bold and underline again + if (is_boldfacing) + putstring(SGR_BOLD); + if (is_underlining) { + if (do_sgr_italics) + putstring(SGR_ITALIC); + else if (do_reverse_video) + putstring(SGR_REVERSE); + else + putstring(SGR_UNDERLINE); + } + // set other color again + back = !back; + color_index = back ? curr_back_idx : curr_fore_idx; + } + if (color_index != DEFAULT_COLOR_IDX) { + putstring(CSI); + if (back) + putchar('4'); + else + putchar('3'); + putchar(color_index + '0'); + putchar('m'); + } +} + +// The possible Unicode combinations for crossing characters. +// +// ' ' = 0, ' -' = 4, '- ' = 8, '--' = 12, +// +// ' ' = 0, ' ' = 1, '|' = 2, '|' = 3 +// | | + +static output_character crossings[4*4] = { + 0x0000, 0x2577, 0x2575, 0x2502, + 0x2576, 0x250C, 0x2514, 0x251C, + 0x2574, 0x2510, 0x2518, 0x2524, + 0x2500, 0x252C, 0x2534, 0x253C +}; + +void tty_printer::end_page(int page_length) +{ + if (page_length % font::vert != 0) + error("vertical position at end of page not multiple of vertical" + " motion quantum"); + int lines_per_page = page_length / font::vert; + int last_line; + for (last_line = nlines; last_line > 0; last_line--) + if (lines[last_line - 1]) + break; +#if 0 + if (last_line > lines_per_page) { + error("characters past last line discarded"); + do { + --last_line; + while (lines[last_line]) { + tty_glyph *tem = lines[last_line]; + lines[last_line] = tem->next; + delete tem; + } + } while (last_line > lines_per_page); + } +#endif + for (int i = 0; i < last_line; i++) { + tty_glyph *p = lines[i]; + lines[i] = 0; + tty_glyph *g = 0; + while (p) { + tty_glyph *tem = p->next; + p->next = g; + g = p; + p = tem; + } + int hpos = 0; + tty_glyph *nextp; + curr_fore_idx = DEFAULT_COLOR_IDX; + curr_back_idx = DEFAULT_COLOR_IDX; + is_underlining = false; + is_boldfacing = false; + for (p = g; p; delete p, p = nextp) { + nextp = p->next; + if (p->mode & CU_MODE) { + is_continuously_underlining = (p->code != 0); + continue; + } + if (nextp && p->hpos == nextp->hpos) { + if (p->draw_mode() == HDRAW_MODE && + nextp->draw_mode() == VDRAW_MODE) { + if (font::is_unicode) + nextp->code = + crossings[((p->mode & (START_LINE|END_LINE)) >> 4) + + ((nextp->mode & (START_LINE|END_LINE)) >> 6)]; + else + nextp->code = '+'; + continue; + } + if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode()) { + nextp->code = p->code; + continue; + } + if (!want_glyph_composition_by_overstriking) + continue; + } + if (hpos > p->hpos) { + do { + putchar('\b'); + hpos--; + } while (hpos > p->hpos); + } + else { + if (want_horizontal_tabs) { + for (;;) { + int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH; + if (next_tab_pos > p->hpos) + break; + if (is_continuously_underlining) + make_underline(p->w); + else if (!use_overstriking_drawing_scheme + && is_underlining) { + if (do_sgr_italics) + putstring(SGR_NO_ITALIC); + else if (do_reverse_video) + putstring(SGR_NO_REVERSE); + else + putstring(SGR_NO_UNDERLINE); + is_underlining = false; + } + putchar('\t'); + hpos = next_tab_pos; + } + } + for (; hpos < p->hpos; hpos++) { + if (is_continuously_underlining) + make_underline(p->w); + else if (!use_overstriking_drawing_scheme && is_underlining) { + if (do_sgr_italics) + putstring(SGR_NO_ITALIC); + else if (do_reverse_video) + putstring(SGR_NO_REVERSE); + else + putstring(SGR_NO_UNDERLINE); + is_underlining = false; + } + putchar(' '); + } + } + assert(hpos == p->hpos); + if (p->mode & COLOR_CHANGE) { + if (!use_overstriking_drawing_scheme) { + if (p->fore_color_idx != curr_fore_idx) { + put_color(p->fore_color_idx, 0); + curr_fore_idx = p->fore_color_idx; + } + if (p->back_color_idx != curr_back_idx) { + put_color(p->back_color_idx, 1); + curr_back_idx = p->back_color_idx; + } + } + continue; + } + if (p->mode & UNDERLINE_MODE) + make_underline(p->w); + else if (!use_overstriking_drawing_scheme && is_underlining) { + if (do_sgr_italics) + putstring(SGR_NO_ITALIC); + else if (do_reverse_video) + putstring(SGR_NO_REVERSE); + else + putstring(SGR_NO_UNDERLINE); + is_underlining = false; + } + if (p->mode & BOLD_MODE) + make_bold(p->code, p->w); + else if (!use_overstriking_drawing_scheme && is_boldfacing) { + putstring(SGR_NO_BOLD); + is_boldfacing = false; + } + if (!use_overstriking_drawing_scheme) { + if (p->fore_color_idx != curr_fore_idx) { + put_color(p->fore_color_idx, 0); + curr_fore_idx = p->fore_color_idx; + } + if (p->back_color_idx != curr_back_idx) { + put_color(p->back_color_idx, 1); + curr_back_idx = p->back_color_idx; + } + } + put_char(p->code); + hpos += p->w / font::hor; + } + if (!use_overstriking_drawing_scheme + && (is_boldfacing || is_underlining + || curr_fore_idx != DEFAULT_COLOR_IDX + || curr_back_idx != DEFAULT_COLOR_IDX)) + putstring(SGR_DEFAULT); + putchar('\n'); + } + if (want_form_feeds) { + if (last_line < lines_per_page) + putchar('\f'); + } + else { + for (; last_line < lines_per_page; last_line++) + putchar('\n'); + } +} + +font *tty_printer::make_font(const char *nm) +{ + return tty_font::load_tty_font(nm); +} + +printer *make_printer() +{ + return new tty_printer(); +} + +static void update_options() +{ + if (use_overstriking_drawing_scheme) { + do_sgr_italics = false; + do_reverse_video = false; + bold_underline_mode = bold_underline_mode_option; + do_bold = want_emboldening_by_overstriking; + do_underline = want_italics_by_underlining; + } + else { + do_sgr_italics = want_sgr_italics; + do_reverse_video = want_reverse_video_for_italics; + bold_underline_mode = BOLD_MODE|UNDERLINE_MODE; + do_bold = true; + do_underline = true; + } +} + +int main(int argc, char **argv) +{ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + if (getenv("GROFF_NO_SGR")) + use_overstriking_drawing_scheme = true; + setbuf(stderr, stderr_buf); + setlocale(LC_CTYPE, ""); + int c; + static const struct option long_options[] = { + { "help", no_argument, 0, CHAR_MAX + 1 }, + { "version", no_argument, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + while ((c = getopt_long(argc, argv, "bBcdfF:hiI:oruUv", long_options, NULL)) + != EOF) + switch(c) { + case 'v': + printf("GNU grotty (groff) version %s\n", Version_string); + exit(EXIT_SUCCESS); + break; + case 'i': + // Use italic font instead of underlining. + want_sgr_italics = true; + break; + case 'I': + // ignore include search path + break; + case 'b': + // Do not embolden by overstriking. + want_emboldening_by_overstriking = false; + break; + case 'c': + // Use old scheme for emboldening and underline. + use_overstriking_drawing_scheme = true; + break; + case 'u': + // Do not underline. + want_italics_by_underlining = false; + break; + case 'o': + // Do not overstrike (other than emboldening and underlining). + want_glyph_composition_by_overstriking = false; + break; + case 'r': + // Use reverse mode instead of underlining. + want_reverse_video_for_italics = true; + break; + case 'B': + // Do bold-underlining as bold. + bold_underline_mode_option = BOLD_MODE; + break; + case 'U': + // Do bold-underlining as underlining. + bold_underline_mode_option = UNDERLINE_MODE; + break; + case 'h': + // Use horizontal tabs. + want_horizontal_tabs = true; + break; + case 'f': + want_form_feeds = true; + break; + case 'F': + font::command_line_font_dir(optarg); + break; + case 'd': + // Ignore \D commands. + allow_drawing_commands = false; + break; + case CHAR_MAX + 1: // --help + usage(stdout); + break; + case '?': + usage(stderr); + exit(EXIT_FAILURE); + break; + default: + assert(0 == "unhandled getopt_long return value"); + } + update_options(); + if (optind >= argc) + do_file("-"); + else { + for (int i = optind; i < argc; i++) + do_file(argv[i]); + } + return 0; +} + +static void usage(FILE *stream) +{ + fprintf(stream, +"usage: %s [-bBcdfhioruU] [-F font-directory] [file ...]\n" +"usage: %s {-v | --version}\n" +"usage: %s --help\n", + program_name, program_name, program_name); + if (stdout == stream) { + fputs( +"\n" +"Translate the output of troff(1) into a form suitable for\n" +"typewriterâ€like devices, including terminal emulators. See the\n" +"grotty(1) manual page.\n", + stream); + exit(EXIT_SUCCESS); + } +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: diff --git a/src/devices/xditview/ChangeLog b/src/devices/xditview/ChangeLog new file mode 100644 index 0000000..a33297b --- /dev/null +++ b/src/devices/xditview/ChangeLog @@ -0,0 +1,556 @@ +2004-05-29 Werner LEMBERG <wl@gnu.org> + + gxditview and xtotroff have been integrated into the normal groff + directory structure; future changes are logged in the main + ChangeLog file. + +2004-05-13 Werner LEMBERG <wl@gnu.org> + +Version 1.19.1 released +======================= + +2004-04-17 Werner LEMBERG <wl@gnu.org> + + * device.c (scale_round): Round correctly for negative values + (this is the same function as in src/libs/libgroff/font.c). + Found by Paul Eggert. + +2003-11-10 Werner LEMBERG <wl@gnu.org> + + * Imakefile.in: s/@top_srcdir@/@abs_top_srcdir@/, + s/@groff_top_builddir@/@abs_top_builddir@/. + +Version 1.19 released +===================== + +2003-03-03 Werner LEMBERG <wl@gnu.org> + + * Imakefile.in (extraclean): Added gxditview._man. + +2003-01-28 Werner LEMBERG <wl@gnu.org> + + * Imakefile.in (SEP): New variable; set to @PATH_SEPARATOR@. + (GROFF_FONTPATH): Use it. + +2003-01-07 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (Adobe_Symbol_map): Add `sqrt'. + +2003-01-06 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (Adobe_Symbol_map): Add `integral'. + +2002-12-29 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (ISO_8859_1_map): Remove `ap' and `eq'. + +2002-12-20 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (Adobe_Symbol_map): Don't include `or'. + * draw.c (AdjustCharDeltas): Apply correction only if nadj > 1. + (DoCharacter): Call FlushCharCache if font size and font number + differ. + Reset `dw->dvi.cache.adjustable' properly. + +2002-12-09 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (ISO_8859_1_map): Use `tno' symbol instead of `no'. + +2002-12-01 Werner LEMBERG <wl@gnu.org> + + * Imakefile.in: Use `InstallAppDefaultsLong' instead of + `InstallAppDefaults' to make it work if build directory isn't + $srcdir. + +2002-11-24 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (Adobe_Symbol_map): Add glyph `braceex'. + +2002-11-14 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (ISO_8859_1_map): Don't include `or'. + +Version 1.18.1 released +======================= + +2002-09-16 Werner LEMBERG <wl@gnu.org> + + * Imakefile.in (GROFF_LOCALFONTDIR): New variable. + (GROFF_FONTPATH): Use it. + Remove /usr/local/lib/font. + +Version 1.18.0 released +======================= + +2002-06-22 Werner LEMBERG <wl@gnu.org> + + * gxditview.c (main): Handle `-help' and `--help' correctly. + +2002-06-17 Colin Watson <cjwatson@debian.org> + + * Imakefile.in: s/@top_builddir@/@groff_top_builddir@/. + +2002-04-06 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (ISO_8859_1_map, Adobe_Symbol_map): Remove all + characters > 0x80. + * parse.c (ParseInput): Ignore `m' command. + (ParseDrawFunction): Don't move for unknown drawing functions. + Don't move for `f' drawing function. + +2002-03-25 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (ISO_8859_1_map): Use `t+-', `tmu', and `tdi' symbols + instead of `+-', `mu', and `di', respectively. + +2002-02-23 Werner LEMBERG <wl@gnu.org> + + * DviChar.c (ISO_8859_1_map): Add `mc' symbol. + +2001-09-22 Werner LEMBERG <wl@gnu.org> + + * Imakefile.in: Redefine `ProgramTargetHelper' as + `ProgramTargetHelperNoMan' and add a call to `InstallManPageLong' + to make the `install.man' target work if the build directory isn't + $srcdir. + +Version 1.17.2 released +======================= + +Version 1.17.1 released +======================= + +2001-04-21 Albert Chin-A-Young <china@thewrittenword.com> + + * Imakefile.in: Add support for recent HP architectures. + +Version 1.17 released +===================== + +2001-01-04 Rob Daasch <daasch@ece.pdx.edu> + + * parse.c (ParseInput): Added 'F' to command switch to swallow + filename strings as ignored comments. + +2000-12-02 Werner LEMBERG <wl@gnu.org> + + * device.c (find_file): Remove home directory in search path. + +2000-11-14 Werner LEMBERG <wl@gnu.org> + + * device.c (open_device_file): Remove `path' parameter. + (find_file): Construct font path similar to groff: First the contents + of GROFF_FONT_PATH, then the home directory, and finally the default + font path. + * Imakefile.in: Fix GROFF_DATAPROGRAMDIR and GROFF_FONTPATH. + +2000-10-23 Werner LEMBERG <wl@gnu.org> + + Change installation structure for data files from .../groff/... to + .../groff/<version><revision>/... to be conform with other GNU + programs. + + * Imakefile.in: Implement it. + +Version 1.16.1 released +======================= + +Version 1.16 released +===================== + +2000-05-18 Werner LEMBERG <wl@gnu.org> + + * DviChar.c: Adding `cq' as an alias for "'" in latin-1 map. + +2000-05-03 Werner LEMBERG <wl@gnu.org> + + * DviChar.c: Adding `dq' as an alias for `"' in latin-1 map. + +2000-04-28 Werner LEMBERG <wl@gnu.org> + + * DviChar.c: Replacing `md' glyph name with `pc' in latin-1 map to + make it distinct from the `md' glyph in the symbol font. + +2000-03-03 Werner LEMBERG <wl@gnu.org> + + * Imakefile replaced with Imakefile.in which will be configured by + the main configure script of groff. This will set the correct font + path, and it will make it possible to build xditview in a directory + different from $srcdir. + +2000-03-01 Colin Phipps <crp22@cam.ac.uk> + + * Dvi.c (OpenFile): Use tmpdir() for security reasons. + * xtotroff.c (MapFont): Avoid race while opening file. + +2000-02-06 Werner LEMBERG <wl@gnu.org> + + * Imakefile: Adapted to new directory structure. + + * README: Updated. + +Version 1.15 released +===================== + +1999-12-21 Werner LEMBERG <wl@gnu.org> + + * README: Fixed ftp GNU address. + +1999-12-13 Werner LEMBERG <wl@gnu.org> + + * device.c: Use extern declarations of strtok(), strchr(), and + getenv() only if not defined as macros. + +1999-11-18 Larry Jones <larry.jones@sdrc.com> + + * xditview.c: Add fallback_resources to allow running without + access to the app-defaults file. + + * Imakefile: Added rule to create app-defaults to a C header file. + + * GXditview-ad.h: New file containing fallback default resources. + + * ad2c: New file to do the app-defaults -> C header file + conversion. + +1999-10-27 Larry Jones <larry.jones@sdrc.com> + + * font.c (DisposeFontSizes): If there's a problem loading a font, + xditview will fall-back and use the default font, but it hasn't + checked before unloading fonts which could result in unloading the + default font (possibly multiple times) and then X errors. + +1999-09-13 Werner LEMBERG <wl@gnu.org> + + * Imakefile (extraclean): Added Makefile. + + * xditview.c (main, MakePrompt): Fixing compilation warnings. + + * TODO: Imakefile should be replaced with a configure script. + +1999-09-13 Werner LEMBERG <wl@gnu.org> + + * Makefile: Removed. + +1999-09-12 Werner LEMBERG <wl@gnu.org> + + * Imakefile (GROFF_FONTPATH): Another addition. + + * device.c (FONTPATH): Update to match current groff version. + +1999-09-11 Larry Jones <larry.jones@sdrc.com> + + * Imakefile (GROFF_LIBDIR, GROFF_FONTPATH): Update to match + current groff version. + + * Dvi.c (Realize, Destroy), DviP.h, draw.c (setFillGC), gray*.bm: + Allow 8 levels of gray rather than just 1. + + * draw.c (DrawFilledCircle, DrawFilledEllipse, DrawFilledPolygon): + Draw outlines to prevent gaps between abutting figures. + +1999-05-27 Werner LEMBERG <wl@gnu.org> + + * xtotroff.c (usage): Fixed typo. + +Mon Sep 11 10:40:33 1995 James Clark <jjc@jclark.com> + + * device.c (INT_MIN, INT_MAX): Don't define if already defined. + +Mon Aug 8 11:14:11 1994 James Clark (jjc@jclark.com) + + * DviChar.c (Adobe_Symbol_map): Use \(nb for notsubset. + +Tue Apr 19 04:41:16 1994 James Clark (jjc@jclark.com) + + * Dvi.c (resources): Change default for background and foreground + to "XtDefaultBackground" and "XtDefaultForeground". + +Sat Feb 12 10:38:47 1994 James Clark (jjc@jclark.com) + + * DviChar.c (Adobe_Symbol_map): Rename radicalex to rn. + +Thu May 27 20:30:12 1993 James Clark (jjc@jclark.com) + + * device.c (isascii): Define if necessary. + (canonicalize_name): Cast argument to isdigit() to unsigned char. + +Thu Apr 29 18:36:57 1993 James Clark (jjc at jclark.com) + + * xditview.c: Include <X11/Xos.h>. + (NewFile): Don't declare rindex(). Use strrchr() rather than + rindex(). + +Tue Mar 30 15:12:09 1993 James Clark (jjc at jclark) + + * draw.c (charExists): Check that fi->per_char is not NULL. + +Sat Dec 12 17:42:40 1992 James Clark (jjc at jclark) + + * Dvi.c (SetGeometry): Cast XtMakeGeometryRequest arguments. + + * draw.c (DrawPolygon, DrawFilledPolygon): Cast Xtfree argument. + + * font.c (DisposeFontSizes): Add declaration. + + * draw.c (FakeCharacter): Add declaration. + +Wed Oct 28 13:24:00 1992 James Clark (jjc at jclark) + + * Imakefile (install.dev): Deleted. + (fonts): New target. + +Mon Oct 12 10:50:44 1992 James Clark (jjc at jclark) + + * Imakefile (install.dev): Say when we're installing devX*-12. + + * Imakefile (install.dev): Depends on DESC and FontMap. + +Thu Oct 1 20:03:45 1992 James Clark (jjc at jclark) + + * xditview.c (Syntax): Mention -filename option. + +Sat Aug 15 12:56:39 1992 James Clark (jjc at jclark) + + * GXditview.ad: Bind space and return to NextPage. Bind backspace + and delete to previous page. + + * DviChar.c (Adobe_Symbol_map): Add `an'. + + * DviChar.c (Adobe_Symbol_map): Add arrowvertex, arrowverttp, and + arrowvertbt. + +Mon Aug 10 11:54:27 1992 James Clark (jjc at jclark) + + * FontMap: Add m/p fields to the fonts names. + +Sat Aug 8 12:00:28 1992 James Clark (jjc at jclark) + + * DESC: Leave font positions 5-9 blank. + +Tue Jul 28 11:37:05 1992 James Clark (jjc at jclark) + + * Imakefile: Don't use gendef. Pass definition of FONTPATH using + DEFINES. + (path.h): Deleted. + (device.c): Don't include path.h. Provide default definition of + FONTPATH. + +Mon Jul 6 14:06:53 1992 James Clark (jjc at jclark) + + * Imakefile: Don't install tmac.X and tmac.Xps. + * tmac.X, tmac.Xps: Moved to ../macros. + + * Imakefile: Don't install eqnchar. + * eqnchar: Deleted. + +Sun Jun 14 12:55:02 1992 James Clark (jjc@jclark) + + * tmac.Xps: Handle OE, oe, lq, rq. + * draw.c (FakeCharacter): Don't handle these. + + * draw.c (FakeCharacter): Don't handle f/. + +Mon Jun 8 11:46:37 1992 James Clark (jjc@jclark) + + * tmac.X: Translate char160 to space. + +Sun Jun 7 14:39:53 1992 James Clark (jjc@jclark) + + * tmac.X: Do `mso tmac.psic' before restoring compatibility mode. + + * tmac.X: Add \(OE, \(oe, \(ah, \(ao, \(ho. + + * tmac.Xps: Make it work in compatibility mode. + Redo existing character definitions with .Xps-char. + Add more character definitions. + (Xps-char): New macro. + +Sat Jun 6 21:46:03 1992 James Clark (jjc@jclark) + + * DviChar.c (Adobe_Symbol_map): Add +h, +f, +p, Fn, lz. + * tmac.X: Add \(bq, \(Bq, \(aq. + * tmac.Xps: Handle \(aq, \(bq, \(Bq, \(Fn. + +Wed Jun 3 11:11:15 1992 James Clark (jjc@jclark) + + * DviChar.c (Adobe_Symbol_map): Add wp. + +Tue Apr 21 09:21:59 1992 James Clark (jjc at jclark) + + * GXditview.ad: Bind n, p, q keys to NextPage, PreviousPage and + Quit actions. + + * xditview.c (RerasterizeAction): New function. + (xditview_actions): Add RerasterizeAction. + * GXditview.ad: Bind r key to Rerasterize action. + +Fri Apr 17 08:25:36 1992 James Clark (jjc at jclark) + + * xditview.c: Add -filename option. + (main): Copy any -filename argument into current_file_name. + +Mon Mar 16 10:21:58 1992 James Clark (jjc at jclark) + + * tmac.X: Load tmac.pspic. + +Sun Mar 8 11:27:19 1992 James Clark (jjc at jclark) + + * Lex.c (GetLine, GetWord, GetNumber): Rewrite. + +Sat Oct 12 22:58:52 1991 James Clark (jjc at jclark) + + * Dvi.c (SetDevice): If the size change request is refused but a + larger geometry is offered, request that. + +Wed Oct 9 12:27:48 1991 James Clark (jjc at jclark) + + * font.c (InstallFontSizes): Ignore FontNameAverageWidth component. + + * Dvi.c (default_font_map): Add `adobe' to font names to avoid + ambiguity. + + * FontMap: New file. + * FontMap.X100, FontMap.X75: Deleted. + * xtotroff.c (main, usage): Add -s and -r options. + (MapFont): Change the font pattern to have the selected resolution and + size. + * Imakefile (install.dev): Use FontMap and supply appropriate -s + and -r options. + + * xtotroff.c (MapFont): Check for ambiguity by comparing canonicalized + font names. + + * DviP.h (DviFontList): Add initialized and scalable members. + (font.c): Add support for scalable fonts based on R5 xditview. + + * DviChar.c: Use xmalloc rather than malloc. + * xditview.c (xmalloc): New function. + * xtotroff.c (xmalloc): New function. + * other files: Use XtMalloc and XtFree instead of malloc and free. + +Thu Aug 29 20:15:31 1991 James Clark (jjc at jclark) + + * draw.c (setGC): Do multiplication in floating point to avoid + overflow. + +Tue Aug 13 12:04:41 1991 James Clark (jjc at jclark) + + * draw.c (FakeCharacter): Remove casts in definition of pack2. + +Tue Jul 30 11:42:39 1991 James Clark (jjc at jclark) + + * tmac.Xps: New file. + * Imakefile (install): Install tmac.Xps. + +Tue Jul 2 09:31:37 1991 James Clark (jjc at jclark) + + * xtotroff.c (main): Pass argv[0] to usage(). + +Sun Jun 30 12:34:06 1991 James Clark (jjc at jclark) + + * xtotroff.c (MapFont): Handle the case where XLoadQueryFont + returns NULL. + +Sat Jun 29 12:32:52 1991 James Clark (jjc at jclark) + + * Imakefile: Use ../gendef to generate path.h. + +Sun Jun 16 13:26:34 1991 James Clark (jjc at jclark) + + * Imakefile (depend.o): Change to device.o. + +Sun Jun 2 12:17:56 1991 James Clark (jjc at jclark) + + * Imakefile: Remove spaces from the beginning of variable + assignment lines. + +Sun May 26 14:14:01 1991 James Clark (jjc at jclark) + + * xditview.c (Syntax): Update. + + * Dvi.c (DviSaveToFile, SaveToFile): New functions. + (FindPage): Check that we're not readingTmp before checking for + end of file of normal input file. + (ClassPartInitialize): New function. + * Dvi.h: Add declaration of DviSaveToFile. + * DviP.h: Add save method to DviClassPart. Declare + InheritSaveToFile. + * xditview.c (DoPrint, Print, PrintAction): New functions. + * xditview.c: Add print menu entry. + * xditview.c: Provide printCommand application resource. + * lex.c: Don't output EOF to temporary file. + + * Dvi.c (QueryGeometry): Check request->request_mode. + + * Dvi.c (SetDevice): New function. + (SetDeviceResolution): Deleted. + + * Dvi.c: Add resolution resource. + * DviP.h: Add definitions of XtNResolution and XtCResolution. + * xditview.c: Add -resolution argument. + * GXditview.ad: Add default for GXditview.height. + * Dvi.c (Initialize, SetDevice): Use default_resolution. + + * Dvi.c: Make MY_HEIGHT and MY_WIDTH use the paperlength and + paperwidth commands in the DESC file. + + * Dvi.c: Add SS font to default font map. + + * draw.c: Rewritten so as not to assume device and display + resolution is the same. + * DviP.h: Include device.h. Add device_font member to DviFontList. + Add adjustable array to DviCharCache. Add text_x_width, + text_device_width, word_flag, device_font, device_font_number, + device, native, device_resolution, display_resolution, + paperlength, paperwidth, scale_factor, sizescale members. + * Dvi.c (Initialize): Initialize new variable used by draw.c. + (Destroy): Call device_destroy. + * font.c (MaxFontPosition): New function. + (LookupFontSizeBySize): Handle sizescale. + (InstallFont): Load the device font. + (ForgetFonts): New function. + (QueryDeviceFont): New function. + * parse.c (ParseInput): Handle t and u commands. Split off + character output into draw.c. + (ParseDeviceControl): Ignore res command. Use the device argument + to the T command. + + * font.c (MapXNameToDviName): Ifdefed out. + + * path.h: New file. + * device.c, device.h: New files. + + * DviChar.c: Add entries for lB, rB, oq, lC, rC, md. + + * INSTALL: New file. + + * libxdvi: Merged into main directory. + * xtotroff.c, xditview.c: Change includes accordingly. + + * devX75, devX100: Merged into main directory. + * xditview.man: Renamed to gxditview.man. + + * Xditview.ad: Renamed to GXditview.ad. + * xditview.c (main): Use class of GXditview rather than xditview. + + * Imakefile: New file. + * Makefile: Deleted. + + * xtotroff.c (MapFont): Unlink output file before opening it. + + * Started separate ChangeLog. + +________________________________________________________________________ + +Copyright 1991-2020 Free Software Foundation, Inc. + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. + +Local Variables: +version-control: never +mode: change-log +coding: latin-1 +End: diff --git a/src/devices/xditview/DESC.in b/src/devices/xditview/DESC.in new file mode 100644 index 0000000..172170c --- /dev/null +++ b/src/devices/xditview/DESC.in @@ -0,0 +1,9 @@ +styles R I B BI +fonts 6 0 0 0 0 0 S +sizes 8 10 12 14 18 24 0 +res 75 +X11 +hor 1 +vert 1 +unitwidth 10 +postpro gxditview diff --git a/src/devices/xditview/Dvi.c b/src/devices/xditview/Dvi.c new file mode 100644 index 0000000..3520676 --- /dev/null +++ b/src/devices/xditview/Dvi.c @@ -0,0 +1,603 @@ +#include <config.h> + +#ifndef SABER +#ifndef lint +static char Xrcsid[] = "$XConsortium: Dvi.c,v 1.9 89/12/10 16:12:25 rws Exp $"; +#endif /* lint */ +#endif /* SABER */ + +/* + * Dvi.c - Dvi display widget + * + */ + +#define XtStrlen(s) ((s) ? strlen(s) : 0) + + /* The following are defined for the reader's convenience. Any + Xt..Field macro in this code just refers to some field in + one of the substructures of the WidgetRec. */ + +#include <X11/IntrinsicP.h> +#include <X11/StringDefs.h> +#include <X11/Xmu/Converters.h> +#include <stdio.h> +#include <ctype.h> + +#include "DviP.h" +#include "font.h" +#include "page.h" +#include "parse.h" + +/**************************************************************** + * + * Full class record constant + * + ****************************************************************/ + +/* Private Data */ + +static char default_font_map_1[] = "\ +TR -adobe-times-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\ +TI -adobe-times-medium-i-normal--*-100-*-*-*-*-iso8859-1\n\ +TB -adobe-times-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\ +TBI -adobe-times-bold-i-normal--*-100-*-*-*-*-iso8859-1\n\ +CR -adobe-courier-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\ +CI -adobe-courier-medium-o-normal--*-100-*-*-*-*-iso8859-1\n\ +CB -adobe-courier-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\ +CBI -adobe-courier-bold-o-normal--*-100-*-*-*-*-iso8859-1\n\ +"; +static char default_font_map_2[] = "\ +HR -adobe-helvetica-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\ +HI -adobe-helvetica-medium-o-normal--*-100-*-*-*-*-iso8859-1\n\ +HB -adobe-helvetica-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\ +HBI -adobe-helvetica-bold-o-normal--*-100-*-*-*-*-iso8859-1\n\ +"; +static char default_font_map_3[] = "\ +NR -adobe-new century schoolbook-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\ +NI -adobe-new century schoolbook-medium-i-normal--*-100-*-*-*-*-iso8859-1\n\ +NB -adobe-new century schoolbook-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\ +NBI -adobe-new century schoolbook-bold-i-normal--*-100-*-*-*-*-iso8859-1\n\ +S -adobe-symbol-medium-r-normal--*-100-*-*-*-*-adobe-fontspecific\n\ +SS -adobe-symbol-medium-r-normal--*-100-*-*-*-*-adobe-fontspecific\n\ +"; + +#define offset(field) XtOffset(DviWidget, field) + +#define MY_WIDTH(dw) ((int)(dw->dvi.paperwidth * dw->dvi.scale_factor + .5)) +#define MY_HEIGHT(dw) ((int)(dw->dvi.paperlength * dw->dvi.scale_factor + .5)) + +static XtResource resources[] = { + {(String)XtNfontMap, (String)XtCFontMap, (String)XtRString, + sizeof (char *), offset(dvi.font_map_string), + (String)XtRString, NULL /* set in code */}, + {(String)XtNforeground, (String)XtCForeground, (String)XtRPixel, + sizeof (unsigned long), offset(dvi.foreground), + (String)XtRString, (XtPointer)"XtDefaultForeground"}, + {(String)XtNbackground, (String)XtCBackground, (String)XtRPixel, + sizeof (unsigned long), offset(dvi.background), + (String)XtRString, (XtPointer)"XtDefaultBackground"}, + {(String)XtNpageNumber, (String)XtCPageNumber, (String)XtRInt, + sizeof (int), offset(dvi.requested_page), + (String)XtRString, (XtPointer)"1"}, + {(String)XtNlastPageNumber, (String)XtCLastPageNumber, (String)XtRInt, + sizeof (int), offset (dvi.last_page), + (String)XtRString, (XtPointer)"0"}, + {(String)XtNfile, (String)XtCFile, (String)XtRFile, + sizeof (FILE *), offset (dvi.file), + (String)XtRFile, (XtPointer)0}, + {(String)XtNseek, (String)XtCSeek, (String)XtRBoolean, + sizeof (Boolean), offset(dvi.seek), + (String)XtRString, (XtPointer)"false"}, + {(String)XtNfont, (String)XtCFont, (String)XtRFontStruct, + sizeof (XFontStruct *), offset(dvi.default_font), + (String)XtRString, (XtPointer)"xtdefaultfont"}, + {(String)XtNbackingStore, (String)XtCBackingStore, (String)XtRBackingStore, + sizeof (int), offset(dvi.backing_store), + (String)XtRString, (XtPointer)"default"}, + {(String)XtNnoPolyText, (String)XtCNoPolyText, (String)XtRBoolean, + sizeof (Boolean), offset(dvi.noPolyText), + (String)XtRString, (XtPointer)"false"}, + {(String)XtNresolution, (String)XtCResolution, (String)XtRInt, + sizeof(int), offset(dvi.default_resolution), + (String)XtRString, (XtPointer)"75"}, +}; + +#undef offset + +static void ClassInitialize (void); +static void ClassPartInitialize(WidgetClass); +static void Initialize(Widget, Widget, ArgList, Cardinal *); +static void Realize (Widget, XtValueMask *, XSetWindowAttributes *); +static void Destroy (Widget); +static void Redisplay (Widget, XEvent *, Region); +static Boolean SetValues (Widget, Widget, Widget, + ArgList, Cardinal *); +static Boolean SetValuesHook (Widget, ArgList, Cardinal *); +static XtGeometryResult QueryGeometry (Widget, XtWidgetGeometry *, + XtWidgetGeometry *); +static void ShowDvi (DviWidget); +static void CloseFile (DviWidget); +static void OpenFile (DviWidget); +static void FindPage (DviWidget); + +static void SaveToFile (Widget, FILE *); + +DviClassRec dviClassRec = { +{ + &widgetClassRec, /* superclass */ + (String)"Dvi", /* class_name */ + sizeof(DviRec), /* size */ + ClassInitialize, /* class_initialize */ + ClassPartInitialize, /* class_part_initialize */ + FALSE, /* class_inited */ + Initialize, /* initialize */ + NULL, /* initialize_hook */ + Realize, /* realize */ + NULL, /* actions */ + 0, /* num_actions */ + resources, /* resources */ + XtNumber(resources), /* resource_count */ + NULLQUARK, /* xrm_class */ + FALSE, /* compress_motion */ + TRUE, /* compress_exposure */ + TRUE, /* compress_enterleave */ + FALSE, /* visible_interest */ + Destroy, /* destroy */ + NULL, /* resize */ + Redisplay, /* expose */ + SetValues, /* set_values */ + SetValuesHook, /* set_values_hook */ + NULL, /* set_values_almost */ + NULL, /* get_values_hook */ + NULL, /* accept_focus */ + XtVersion, /* version */ + NULL, /* callback_private */ + 0, /* tm_table */ + QueryGeometry, /* query_geometry */ + NULL, /* display_accelerator */ + NULL /* extension */ +},{ + SaveToFile, /* save */ +}, +}; + +WidgetClass dviWidgetClass = (WidgetClass) &dviClassRec; + +static void ClassInitialize (void) +{ + int len1 = strlen(default_font_map_1); + int len2 = strlen(default_font_map_2); + int len3 = strlen(default_font_map_3); + char *dfm = XtMalloc(len1 + len2 + len3 + 1); + char *ptr = dfm; + strcpy(ptr, default_font_map_1); ptr += len1; + strcpy(ptr, default_font_map_2); ptr += len2; + strcpy(ptr, default_font_map_3); + resources[0].default_addr = dfm; + + XtAddConverter( XtRString, XtRBackingStore, XmuCvtStringToBackingStore, + NULL, 0 ); +} + +/**************************************************************** + * + * Private Procedures + * + ****************************************************************/ + +/* ARGSUSED */ +static void Initialize(Widget request, Widget new_wd, + ArgList args, Cardinal *num_args) +{ + DviWidget dw = (DviWidget) new_wd; + + dw->dvi.current_page = 0; + dw->dvi.font_map = 0; + dw->dvi.cache.index = 0; + dw->dvi.text_x_width = 0; + dw->dvi.text_device_width = 0; + dw->dvi.word_flag = 0; + dw->dvi.file = 0; + dw->dvi.tmpFile = 0; + dw->dvi.state = 0; + dw->dvi.readingTmp = 0; + dw->dvi.cache.char_index = 0; + dw->dvi.cache.font_size = -1; + dw->dvi.cache.font_number = -1; + dw->dvi.cache.adjustable[0] = 0; + dw->dvi.file_map = 0; + dw->dvi.fonts = 0; + dw->dvi.seek = False; + dw->dvi.device_resolution = dw->dvi.default_resolution; + dw->dvi.display_resolution = dw->dvi.default_resolution; + dw->dvi.paperlength = dw->dvi.default_resolution*11; + dw->dvi.paperwidth = (dw->dvi.default_resolution*8 + + dw->dvi.default_resolution/2); + dw->dvi.scale_factor = 1.0; + dw->dvi.sizescale = 1; + dw->dvi.line_thickness = -1; + dw->dvi.line_width = 1; + dw->dvi.fill = DVI_FILL_MAX; + dw->dvi.device_font = 0; + dw->dvi.device_font_number = -1; + dw->dvi.device = 0; + dw->dvi.native = 0; + + request = request; /* unused; suppress compiler warning */ + args = args; + num_args = num_args; +} + +#include "gray1.bm" +#include "gray2.bm" +#include "gray3.bm" +#include "gray4.bm" +#include "gray5.bm" +#include "gray6.bm" +#include "gray7.bm" +#include "gray8.bm" + +static void +Realize (Widget w, XtValueMask *valueMask, XSetWindowAttributes *attrs) +{ + DviWidget dw = (DviWidget) w; + XGCValues values; + + if (dw->dvi.backing_store != Always + WhenMapped + NotUseful) { + attrs->backing_store = dw->dvi.backing_store; + *valueMask |= CWBackingStore; + } + XtCreateWindow (w, (unsigned)InputOutput, (Visual *) CopyFromParent, + *valueMask, attrs); + values.foreground = dw->dvi.foreground; + values.cap_style = CapRound; + values.join_style = JoinRound; + values.line_width = dw->dvi.line_width; + dw->dvi.normal_GC = XCreateGC (XtDisplay (w), XtWindow (w), + GCForeground|GCCapStyle|GCJoinStyle + |GCLineWidth, + &values); + dw->dvi.gray[0] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray1_bits, + gray1_width, gray1_height); + dw->dvi.gray[1] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray2_bits, + gray2_width, gray2_height); + dw->dvi.gray[2] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray3_bits, + gray3_width, gray3_height); + dw->dvi.gray[3] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray4_bits, + gray4_width, gray4_height); + dw->dvi.gray[4] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray5_bits, + gray5_width, gray5_height); + dw->dvi.gray[5] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray6_bits, + gray6_width, gray6_height); + dw->dvi.gray[6] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray7_bits, + gray7_width, gray7_height); + dw->dvi.gray[7] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w), + gray8_bits, + gray8_width, gray8_height); + values.background = dw->dvi.background; + values.stipple = dw->dvi.gray[5]; + dw->dvi.fill_GC = XCreateGC (XtDisplay (w), XtWindow (w), + GCForeground|GCBackground|GCStipple, + &values); + + dw->dvi.fill_type = 9; + + if (dw->dvi.file) + OpenFile (dw); + ParseFontMap (dw); +} + +static void +Destroy(Widget w) +{ + DviWidget dw = (DviWidget) w; + + XFreeGC (XtDisplay (w), dw->dvi.normal_GC); + XFreeGC (XtDisplay (w), dw->dvi.fill_GC); + XFreePixmap (XtDisplay (w), dw->dvi.gray[0]); + XFreePixmap (XtDisplay (w), dw->dvi.gray[1]); + XFreePixmap (XtDisplay (w), dw->dvi.gray[2]); + XFreePixmap (XtDisplay (w), dw->dvi.gray[3]); + XFreePixmap (XtDisplay (w), dw->dvi.gray[4]); + XFreePixmap (XtDisplay (w), dw->dvi.gray[5]); + XFreePixmap (XtDisplay (w), dw->dvi.gray[6]); + XFreePixmap (XtDisplay (w), dw->dvi.gray[7]); + DestroyFontMap (dw->dvi.font_map); + DestroyFileMap (dw->dvi.file_map); + device_destroy (dw->dvi.device); +} + +/* + * Repaint the widget window + */ + +/* ARGSUSED */ +static void +Redisplay(Widget w, XEvent *event, Region region) +{ + DviWidget dw = (DviWidget) w; + XRectangle extents; + + XClipBox (region, &extents); + dw->dvi.extents.x1 = extents.x; + dw->dvi.extents.y1 = extents.y; + dw->dvi.extents.x2 = extents.x + extents.width; + dw->dvi.extents.y2 = extents.y + extents.height; + ShowDvi (dw); + + event = event; /* unused; suppress compiler warning */ +} + +/* + * Set specified arguments into widget + */ +/* ARGSUSED */ +static Boolean +SetValues (Widget wcurrent, Widget wrequest, Widget wnew, + ArgList args, Cardinal *num_args) +{ + Boolean redisplay = FALSE; + char *new_map; + int cur, req; + DviWidget current = (DviWidget)wcurrent; + DviWidget request = (DviWidget)wrequest; + DviWidget new_wd = (DviWidget)wnew; + + if (current->dvi.font_map_string != request->dvi.font_map_string) { + new_map = XtMalloc (strlen (request->dvi.font_map_string) + 1); + if (new_map) { + redisplay = TRUE; + strcpy (new_map, request->dvi.font_map_string); + new_wd->dvi.font_map_string = new_map; + if (current->dvi.font_map_string) + XtFree (current->dvi.font_map_string); + current->dvi.font_map_string = 0; + ParseFontMap (new_wd); + } + } + + req = request->dvi.requested_page; + cur = current->dvi.requested_page; + if (cur != req) { + if (!request->dvi.file) + req = 0; + else { + if (req < 1) + req = 1; + if (current->dvi.last_page != 0 && + req > current->dvi.last_page) + req = current->dvi.last_page; + } + if (cur != req) + redisplay = TRUE; + new_wd->dvi.requested_page = req; + if (current->dvi.last_page == 0 && req > cur) + FindPage (new_wd); + } + + args = args; /* unused; suppress compiler warning */ + num_args = num_args; + + return redisplay; +} + +/* + * use the set_values_hook entry to check when + * the file is set + */ + +static Boolean +SetValuesHook (Widget wdw, ArgList args, Cardinal *num_argsp) +{ + Cardinal i; + DviWidget dw = (DviWidget)wdw; + + for (i = 0; i < *num_argsp; i++) { + if (!strcmp (args[i].name, XtNfile)) { + CloseFile (dw); + OpenFile (dw); + return TRUE; + } + } + return FALSE; +} + +static void CloseFile (DviWidget dw) +{ + if (dw->dvi.tmpFile) + fclose (dw->dvi.tmpFile); + ForgetPagePositions (dw); +} + +static void OpenFile (DviWidget dw) +{ + dw->dvi.tmpFile = 0; + if (!dw->dvi.seek) + dw->dvi.tmpFile = tmpfile(); + dw->dvi.requested_page = 1; + dw->dvi.last_page = 0; +} + +static XtGeometryResult +QueryGeometry (Widget w, XtWidgetGeometry *request, + XtWidgetGeometry *geometry_return) +{ + XtGeometryResult ret; + DviWidget dw = (DviWidget) w; + + ret = XtGeometryYes; + if (((request->request_mode & CWWidth) + && request->width < MY_WIDTH(dw)) + || ((request->request_mode & CWHeight) + && request->height < MY_HEIGHT(dw))) + ret = XtGeometryAlmost; + geometry_return->width = MY_WIDTH(dw); + geometry_return->height = MY_HEIGHT(dw); + geometry_return->request_mode = CWWidth|CWHeight; + return ret; +} + +void +SetDevice (DviWidget dw, const char *name) +{ + XtWidgetGeometry request, reply; + XtGeometryResult ret; + + ForgetFonts (dw); + dw->dvi.device = device_load (name); + if (!dw->dvi.device) + return; + dw->dvi.sizescale = dw->dvi.device->sizescale; + dw->dvi.device_resolution = dw->dvi.device->res; + dw->dvi.native = dw->dvi.device->X11; + dw->dvi.paperlength = dw->dvi.device->paperlength; + dw->dvi.paperwidth = dw->dvi.device->paperwidth; + if (dw->dvi.native) { + dw->dvi.display_resolution = dw->dvi.device_resolution; + dw->dvi.scale_factor = 1.0; + } + else { + dw->dvi.display_resolution = dw->dvi.default_resolution; + dw->dvi.scale_factor = ((double)dw->dvi.display_resolution + / dw->dvi.device_resolution); + } + request.request_mode = CWWidth|CWHeight; + request.width = MY_WIDTH(dw); + request.height = MY_HEIGHT(dw); + ret = XtMakeGeometryRequest ((Widget)dw, &request, &reply); + if (ret == XtGeometryAlmost + && reply.height >= request.height + && reply.width >= request.width) { + request.width = reply.width; + request.height = reply.height; + XtMakeGeometryRequest ((Widget)dw, &request, &reply); + } +} + +static void +ShowDvi (DviWidget dw) +{ + if (!dw->dvi.file) { + static char Error[] = "No file selected"; + + XSetFont (XtDisplay(dw), dw->dvi.normal_GC, + dw->dvi.default_font->fid); + XDrawString (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + 20, 20, Error, strlen (Error)); + return; + } + + FindPage (dw); + + dw->dvi.display_enable = 1; + ParseInput (dw); + if (dw->dvi.last_page && dw->dvi.requested_page > dw->dvi.last_page) + dw->dvi.requested_page = dw->dvi.last_page; +} + +static void +FindPage (DviWidget dw) +{ + int i; + long file_position; + + if (dw->dvi.requested_page < 1) + dw->dvi.requested_page = 1; + + if (dw->dvi.last_page != 0 && dw->dvi.requested_page > dw->dvi.last_page) + dw->dvi.requested_page = dw->dvi.last_page; + + file_position = SearchPagePosition (dw, dw->dvi.requested_page); + if (file_position != -1) { + FileSeek(dw, file_position); + dw->dvi.current_page = dw->dvi.requested_page; + } else { + for (i=dw->dvi.requested_page; i > 0; i--) { + file_position = SearchPagePosition (dw, i); + if (file_position != -1) + break; + } + if (file_position == -1) + file_position = 0; + FileSeek (dw, file_position); + + dw->dvi.current_page = i; + + dw->dvi.display_enable = 0; + while (dw->dvi.current_page != dw->dvi.requested_page) { + dw->dvi.current_page = ParseInput (dw); + /* + * at EOF, seek back to the beginning of this page. + */ + if (!dw->dvi.readingTmp && feof (dw->dvi.file)) { + file_position = SearchPagePosition (dw, + dw->dvi.current_page); + if (file_position != -1) + FileSeek (dw, file_position); + dw->dvi.requested_page = dw->dvi.current_page; + break; + } + } + } +} + +void DviSaveToFile(Widget w, FILE *fp) +{ + XtCheckSubclass(w, dviWidgetClass, NULL); + (*((DviWidgetClass) XtClass(w))->command_class.save)(w, fp); +} + +static +void SaveToFile(Widget w, FILE *fp) +{ + DviWidget dw = (DviWidget)w; + long pos; + int c; + + if (dw->dvi.tmpFile) { + pos = ftell(dw->dvi.tmpFile); + if (dw->dvi.ungot) { + pos--; + dw->dvi.ungot = 0; + /* The ungot character is in the tmpFile, so we don't + want to read it from file. */ + (void)getc(dw->dvi.file); + } + } + else + pos = ftell(dw->dvi.file); + FileSeek(dw, 0L); + while (DviGetC(dw, &c) != EOF) + if (putc(c, fp) == EOF) { + /* XXX print error message */ + break; + } + FileSeek(dw, pos); +} + +static +void ClassPartInitialize(WidgetClass widget_class) +{ + DviWidgetClass wc = (DviWidgetClass)widget_class; + DviWidgetClass super = (DviWidgetClass) wc->core_class.superclass; + if (wc->command_class.save == InheritSaveToFile) + wc->command_class.save = super->command_class.save; +} + +/* +Local Variables: +c-indent-level: 8 +c-continued-statement-offset: 8 +c-brace-offset: -8 +c-argdecl-indent: 8 +c-label-offset: -8 +c-tab-always-indent: nil +End: +*/ diff --git a/src/devices/xditview/Dvi.h b/src/devices/xditview/Dvi.h new file mode 100644 index 0000000..bf97374 --- /dev/null +++ b/src/devices/xditview/Dvi.h @@ -0,0 +1,46 @@ +/* +* $XConsortium: Dvi.h,v 1.4 89/07/21 14:22:06 jim Exp $ +*/ + +#ifndef _XtDvi_h +#define _XtDvi_h + +/*********************************************************************** + * + * Dvi Widget + * + ***********************************************************************/ + +/* Parameters: + + Name Class RepType Default Value + ---- ----- ------- ------------- + background Background pixel White + foreground Foreground Pixel Black + fontMap FontMap char * ... + pageNumber PageNumber int 1 +*/ + +#define XtNfontMap (String)"fontMap" +#define XtNpageNumber (String)"pageNumber" +#define XtNlastPageNumber (String)"lastPageNumber" +#define XtNnoPolyText (String)"noPolyText" +#define XtNseek (String)"seek" +#define XtNresolution (String)"resolution" + +#define XtCFontMap (String)"FontMap" +#define XtCPageNumber (String)"PageNumber" +#define XtCLastPageNumber (String)"LastPageNumber" +#define XtCNoPolyText (String)"NoPolyText" +#define XtCSeek (String)"Seek" +#define XtCResolution (String)"Resolution" + +typedef struct _DviRec *DviWidget; /* completely defined in DviP.h */ +typedef struct _DviClassRec *DviWidgetClass; /* completely defined in DviP.h */ + +extern WidgetClass dviWidgetClass; + +void DviSaveToFile(Widget, FILE *); + +#endif /* _XtDvi_h */ +/* DON'T ADD STUFF AFTER THIS #endif */ diff --git a/src/devices/xditview/DviP.h b/src/devices/xditview/DviP.h new file mode 100644 index 0000000..8b71c69 --- /dev/null +++ b/src/devices/xditview/DviP.h @@ -0,0 +1,235 @@ +/* + * $XConsortium: DviP.h,v 1.5 89/07/22 19:44:08 keith Exp $ + */ + +/* + * DviP.h - Private definitions for Dvi widget + */ + +#ifndef _XtDviP_h +#define _XtDviP_h + +#include "Dvi.h" +#include "DviChar.h" +#include "device.h" + +/*********************************************************************** + * + * Dvi Widget Private Data + * + ***********************************************************************/ + +/************************************ + * + * Class structure + * + ***********************************/ + +/* Type for save method. */ + +typedef void (*DviSaveProc)(Widget, FILE *); + +/* + * New fields for the Dvi widget class record + */ + + +typedef struct _DviClass { + DviSaveProc save; +} DviClassPart; + +/* + * Full class record declaration + */ + +typedef struct _DviClassRec { + CoreClassPart core_class; + DviClassPart command_class; +} DviClassRec; + +extern DviClassRec dviClassRec; + +/*************************************** + * + * Instance (widget) structure + * + **************************************/ + +/* + * a list of fonts we've used for this widget + */ + +typedef struct _dviFontSizeList { + struct _dviFontSizeList *next; + int size; + char *x_name; + XFontStruct *font; + int doesnt_exist; +} DviFontSizeList; + +typedef struct _dviFontList { + struct _dviFontList *next; + char *dvi_name; + char *x_name; + int dvi_number; + Boolean initialized; + Boolean scalable; + DviFontSizeList *sizes; + DviCharNameMap *char_map; + DeviceFont *device_font; +} DviFontList; + +typedef struct _dviFontMap { + struct _dviFontMap *next; + char *dvi_name; + char *x_name; +} DviFontMap; + +#define DVI_TEXT_CACHE_SIZE 256 +#define DVI_CHAR_CACHE_SIZE 1024 + +typedef struct _dviCharCache { + XTextItem cache[DVI_TEXT_CACHE_SIZE]; + char adjustable[DVI_TEXT_CACHE_SIZE]; + char char_cache[DVI_CHAR_CACHE_SIZE]; + int index; + int max; + int char_index; + int font_size; + int font_number; + XFontStruct *font; + int start_x, start_y; + int x, y; +} DviCharCache; + +typedef struct _dviState { + struct _dviState *next; + int font_size; + int font_number; + int x; + int y; +} DviState; + +typedef struct _dviFileMap { + struct _dviFileMap *next; + long position; + int page_number; +} DviFileMap; + +/* + * New fields for the Dvi widget record + */ + +typedef struct { + /* + * resource specifiable items + */ + char *font_map_string; + unsigned long foreground; + unsigned long background; + int requested_page; + int last_page; + XFontStruct *default_font; + FILE *file; + Boolean noPolyText; + Boolean seek; /* file is 'seekable' */ + int default_resolution; + /* + * private state + */ + FILE *tmpFile; /* used when reading stdin */ + char readingTmp; /* reading now from tmp */ + char ungot; /* have ungetc'd a char */ + GC normal_GC; + GC fill_GC; + DviFileMap *file_map; + DviFontList *fonts; + DviFontMap *font_map; + int current_page; + int font_size; + int font_number; + DeviceFont *device_font; + int device_font_number; + Device *device; + int native; + int device_resolution; + int display_resolution; + int paperlength; + int paperwidth; + double scale_factor; /* display res / device res */ + int sizescale; + int line_thickness; + int line_width; + +#define DVI_FILL_MAX 1000 + + int fill; +#define DVI_FILL_WHITE 0 +#define DVI_FILL_GRAY 1 +#define DVI_FILL_BLACK 2 + int fill_type; + Pixmap gray[8]; + int backing_store; + XFontStruct *font; + int display_enable; + struct ExposedExtents { + int x1, y1, x2, y2; + } extents; + DviState *state; + DviCharCache cache; + int text_x_width; + int text_device_width; + int word_flag; +} DviPart; + +int DviGetAndPut(DviWidget, int *); +#define DviGetIn(dw,cp)\ + (dw->dvi.tmpFile ? (\ + DviGetAndPut (dw, cp) \ + ) :\ + (*cp = getc (dw->dvi.file))\ +) + +#define DviGetC(dw, cp)\ + (dw->dvi.readingTmp ? (\ + ((*cp = getc (dw->dvi.tmpFile)) == EOF) ? (\ + fseek (dw->dvi.tmpFile, 0l, 2),\ + (dw->dvi.readingTmp = 0),\ + DviGetIn (dw,cp)\ + ) : (\ + *cp\ + )\ + ) : (\ + DviGetIn(dw,cp)\ + )\ +) + +#define DviUngetC(dw, c)\ + (dw->dvi.readingTmp ? (\ + ungetc (c, dw->dvi.tmpFile)\ + ) : ( \ + (dw->dvi.ungot = 1),\ + ungetc (c, dw->dvi.file))) + +/* + * Full widget declaration + */ + +typedef struct _DviRec { + CorePart core; + DviPart dvi; +} DviRec; + +#define InheritSaveToFile ((DviSaveProc)_XtInherit) + +XFontStruct *QueryFont (DviWidget, int, int); + +DviCharNameMap *QueryFontMap (DviWidget, int); + +DeviceFont *QueryDeviceFont (DviWidget, int); + +char *GetWord(DviWidget, char *, int); +char *GetLine(DviWidget, char *, int); + +void SetDevice (DviWidget dw, const char *name); +#endif /* _XtDviP_h */ diff --git a/src/devices/xditview/FontMap-X11 b/src/devices/xditview/FontMap-X11 new file mode 100644 index 0000000..90911f0 --- /dev/null +++ b/src/devices/xditview/FontMap-X11 @@ -0,0 +1,17 @@ +TR -adobe-times-medium-r-normal--*-*-*-*-p-*-iso8859-1 +TI -adobe-times-medium-i-normal--*-*-*-*-p-*-iso8859-1 +TB -adobe-times-bold-r-normal--*-*-*-*-p-*-iso8859-1 +TBI -adobe-times-bold-i-normal--*-*-*-*-p-*-iso8859-1 +CR -adobe-courier-medium-r-normal--*-*-*-*-m-*-iso8859-1 +CI -adobe-courier-medium-o-normal--*-*-*-*-m-*-iso8859-1 +CB -adobe-courier-bold-r-normal--*-*-*-*-m-*-iso8859-1 +CBI -adobe-courier-bold-o-normal--*-*-*-*-m-*-iso8859-1 +HR -adobe-helvetica-medium-r-normal--*-*-*-*-p-*-iso8859-1 +HI -adobe-helvetica-medium-o-normal--*-*-*-*-p-*-iso8859-1 +HB -adobe-helvetica-bold-r-normal--*-*-*-*-p-*-iso8859-1 +HBI -adobe-helvetica-bold-o-normal--*-*-*-*-p-*-iso8859-1 +NR -adobe-new century schoolbook-medium-r-normal--*-*-*-*-p-*-iso8859-1 +NI -adobe-new century schoolbook-medium-i-normal--*-*-*-*-p-*-iso8859-1 +NB -adobe-new century schoolbook-bold-r-normal--*-*-*-*-p-*-iso8859-1 +NBI -adobe-new century schoolbook-bold-i-normal--*-*-*-*-p-*-iso8859-1 +S -adobe-symbol-medium-r-normal--*-*-*-*-p-*-adobe-fontspecific diff --git a/src/devices/xditview/GXditview-color.ad b/src/devices/xditview/GXditview-color.ad new file mode 100644 index 0000000..61abd8f --- /dev/null +++ b/src/devices/xditview/GXditview-color.ad @@ -0,0 +1,15 @@ + +#include "GXditview" + +GXditview.paned.viewport.Scrollbar.background: Thistle +GXditview.paned.viewport.Scrollbar.foreground: Orchid +GXditview.paned.viewport.Scrollbar.thumb: None +GXditview.paned.viewport.dvi.background: LemonChiffon +GXditview.paned.viewport.background: Thistle +GXditview.paned.label.background: PeachPuff +GXditview.menu.background: Gold +GXditview.promptShell.promptDialog*background: Gold +GXditview.promptShell.promptDialog.accept.background: DarkOliveGreen1 +GXditview.promptShell.promptDialog.cancel.background: RosyBrown1 +GXditview.promptShell.promptDialog.value.background: Khaki + diff --git a/src/devices/xditview/GXditview.ad b/src/devices/xditview/GXditview.ad new file mode 100644 index 0000000..549f0d7 --- /dev/null +++ b/src/devices/xditview/GXditview.ad @@ -0,0 +1,71 @@ + +GXditview*shapeStyle: rectangle + +GXditview.paned.allowResize: true +GXditview.paned.label.skipAdjust: true +GXditview.paned.viewport.skipAdjust: false +GXditview.paned.viewport.showGrip: false +GXditview.paned.viewport.allowVert: true +GXditview.paned.viewport.allowHoriz: true +GXditview.paned.viewport.forceBars: true +! viewport size = papersize * resol + scrollbarthickness + 1 +! letter size paper +!GXditview.paned.viewport.width: 652 +!GXditview.paned.viewport.height: 840 +! a4 size paper +! 865 is wide enough for a page of X100-12. +GXditview.paned.viewport.width: 865 +GXditview.paned.viewport.height: 892 +GXditview.paned.viewport.Scrollbar.thickness: 14 + +GXditview.geometry: 865x892 + +GXditview.paned.translations: #augment \ + <Key>Next: NextPage()\n\ + <Key>n: NextPage()\n\ + <Key>space: NextPage()\n\ + <Key>Return: NextPage()\n\ + <Key>Prior: PreviousPage()\n\ + <Key>p: PreviousPage()\n\ + <Key>b: PreviousPage()\n\ + <Key>BackSpace: PreviousPage()\n\ + <Key>Delete: PreviousPage()\n\ + <Key>g: SelectPage()\n\ + <Key>o: OpenFile()\n\ + <Key>r: Rerasterize()\n\ + <Key>q: Quit() +GXditview.paned.viewport.clip.translations: #augment \ + <Btn1Down>: XawPositionSimpleMenu(menu) MenuPopup(menu) +GXditview.paned.label.translations: #augment \ + <Btn1Down>: XawPositionSimpleMenu(menu) MenuPopup(menu) +GXditview.paned.viewport.vertical.accelerators: #override \ + <Key>k: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\ + <Key>j: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()\n\ + <Key>Up: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\ + <Key>Down: StartScroll(Forward) NotifyScroll(FullLength) EndScroll() +GXditview.paned.viewport.horizontal.accelerators: #override \ + <Key>h: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\ + <Key>l: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()\n\ + <Key>Left: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\ + <Key>Right: StartScroll(Forward) NotifyScroll(FullLength) EndScroll() + +GXditview.menu.nextPage.label: Next Page +GXditview.menu.previousPage.label: Previous Page +GXditview.menu.selectPage.label: Goto Page +GXditview.menu.print.label: Print +GXditview.menu.openFile.label: Open +GXditview.menu.quit.label: Quit + +GXditview.promptShell.allowShellResize: true +GXditview.promptShell.promptDialog.value.translations: #override \ + <Key>Return: Accept() \n\ + <Key>Escape: Cancel() + +GXditview.promptShell.promptDialog.accept.label: Accept +GXditview.promptShell.promptDialog.accept.translations: #override \ + <BtnUp>: Accept() unset() + +GXditview.promptShell.promptDialog.cancel.label: Cancel +GXditview.promptShell.promptDialog.cancel.translations: #override \ + <BtnUp>: Cancel() unset() + diff --git a/src/devices/xditview/Menu.h b/src/devices/xditview/Menu.h new file mode 100644 index 0000000..c306b27 --- /dev/null +++ b/src/devices/xditview/Menu.h @@ -0,0 +1,46 @@ +/* + * $XConsortium: Menu.h,v 1.2 89/07/21 14:22:10 jim Exp $ + */ + +#ifndef _XtMenu_h +#define _XtMenu_h + +/*********************************************************************** + * + * Menu Widget + * + ***********************************************************************/ + +/* Parameters: + + Name Class RepType Default Value + ---- ----- ------- ------------- + background Background pixel White + border BorderColor pixel Black + borderWidth BorderWidth int 1 + height Height int 120 + mappedWhenManaged MappedWhenManaged Boolean True + reverseVideo ReverseVideo Boolean False + width Width int 120 + x Position int 0 + y Position int 0 + +*/ + +#define XtNmenuEntries "menuEntries" +#define XtNhorizontalPadding "horizontalPadding" +#define XtNverticalPadding "verticalPadding" +#define XtNselection "Selection" + +#define XtCMenuEntries "MenuEntries" +#define XtCPadding "Padding" +#define XtCSelection "Selection" + +typedef struct _MenuRec *MenuWidget; /* completely defined in MenuPrivate.h */ +typedef struct _MenuClassRec *MenuWidgetClass; /* completely defined in MenuPrivate.h */ + +extern WidgetClass menuWidgetClass; + +extern Widget XawMenuCreate (); +#endif /* _XtMenu_h */ +/* DON'T ADD STUFF AFTER THIS #endif */ diff --git a/src/devices/xditview/README b/src/devices/xditview/README new file mode 100644 index 0000000..9de7869 --- /dev/null +++ b/src/devices/xditview/README @@ -0,0 +1,13 @@ +This is gxditview, an X11 previewer for groff based on MIT's xditview. +This version can be used with the output of gtroff -Tps as well as +with -TX75 and -TX100. You will need X11R5 or newer to install it (it +might work on X11R4, but I haven't tested it.) + +Previously, gxditview was installed in the usual place for X binaries +(e.g., /usr/bin/X11); you have to remove it manually. + +xditview is copyrighted by MIT under the usual X terms (see +gxditview.man); the changes to that which come along with the groff package +are in the public domain. + +Please report bugs at http://savannah.gnu.org/bugs/?group=groff. diff --git a/src/devices/xditview/TODO b/src/devices/xditview/TODO new file mode 100644 index 0000000..0b5b140 --- /dev/null +++ b/src/devices/xditview/TODO @@ -0,0 +1,21 @@ +Open the main window with the correct width and height, depending on +both the selected device and the paper dimensions. + +Add a command-line option to specify the paper dimensions (similar to +other groff devices). + +Better error handling. + +Resource and command-line option to specify font path. + +Resource to specify name of environment variable from which to get the +font path. + +Have character substitutions (currently done in draw.c:FakeCharacter) +specified in a resource (similar format to FontMap). + +The initial width of the dialog box should expand to accommodate the +default value. + +Option in Print dialog to specify that only the current page should be +printed. diff --git a/src/devices/xditview/ad2c b/src/devices/xditview/ad2c new file mode 100644 index 0000000..2ec45ed --- /dev/null +++ b/src/devices/xditview/ad2c @@ -0,0 +1,64 @@ +#! /bin/sh +# +# ad2c : Convert app-defaults file to C strings decls. +# +# George Ferguson, ferguson@cs.rcohester.edu, 12 Nov 1990. +# 19 Mar 1991: gf +# Made it self-contained. +# 6 Jan 1992: mycroft@gnu.ai.mit.edu (Charles Hannum) +# Removed use of "-n" and ":read" label since Gnu and +# IBM sed print pattern space on "n" command. Still works +# with Sun sed, of course. +# 7 Jan 1992: matthew@sunpix.East.Sun.COM (Matthew Stier) +# Escape quotes after escaping backslashes. +# 8 Jul 1992: Version 1.6 +# Manpage fixes. +# 19 Apr 1993: Version 1.7 +# Remove comments that were inside the sed command since +# some versions of sed don't like them. The comments are +# now given here in the header. +# 31 May 2004: Werner Lemberg <wl@gnu.org> +# Force casts to 'String'. +# +# Comments on the script by line: +# /^!/d Remove comments +# /^$/d Remove blanks +# s/\\/\\\\/g Escape backslashes... +# s/\\$//g ...except the line continuation ones +# s/"/\\"/g Escape quotes +# s/^/"/ Add leading quote and cast +# : test Establish label for later branch +# /\\$/b slash Branch to label "slash" if line ends in backslash +# s/$/",/ Otherwise add closing quote and comma... +# p ...output the line... +# d ...and clear the pattern space so it's not printed again +# : slash Branch comes here if line ends in backslash +# n Read next line, append to pattern space +# [...] The "d" and "s" commands that follow just delete +# comments and blank lines and escape control sequences +# b test Branch up to see if the line ends in backslash or not +# + +sed ' +/^!/d +/^$/d +s/\\/\\\\/g +s/\\$//g +s/"/\\"/g +s/^/(String)"/ +: test +/\\$/b slash +s/$/",/ +p +d +: slash +n +/^!/d +/^$/d +s/"/\\"/g +s/\\\\/\\/g +s/\\n/\\\\n/g +s/\\t/\\\\t/g +s/\\f/\\\\f/g +s/\\b/\\\\b/g +b test' "$@" diff --git a/src/devices/xditview/device.c b/src/devices/xditview/device.c new file mode 100644 index 0000000..1eaafbe --- /dev/null +++ b/src/devices/xditview/device.c @@ -0,0 +1,565 @@ +/* device.c */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <X11/Xos.h> +#include <X11/Intrinsic.h> + +#include "device.h" +#include "defs.h" + +#ifndef isascii +#define isascii(c) (1) +#endif + +/* Name of environment variable containing path to be used for +searching for device and font description files. */ +#define FONTPATH_ENV_VAR "GROFF_FONT_PATH" + +#define WS " \t\r\n" + +#ifndef INT_MIN +/* Minimum and maximum values a 'signed int' can hold. */ +#define INT_MIN (-INT_MAX-1) +#define INT_MAX 2147483647 +#endif + +#define CHAR_TABLE_SIZE 307 + +struct _DeviceFont { + char *name; + int special; + DeviceFont *next; + Device *dev; + struct charinfo *char_table[CHAR_TABLE_SIZE]; + struct charinfo *code_table[256]; +}; + +struct charinfo { + int width; + int code; + struct charinfo *next; + struct charinfo *code_next; + char name[1]; +}; + +static char *current_filename = 0; +static int current_lineno = -1; + +static void error(const char *s); +static FILE *open_device_file(const char *, const char *, char **); +static DeviceFont *load_font(Device *, const char *); +static Device *new_device(const char *); +static DeviceFont *new_font(const char *, Device *); +static void delete_font(DeviceFont *); +static unsigned hash_name(const char *); +static struct charinfo *add_char(DeviceFont *, const char *, int, int); +static int read_charset_section(DeviceFont *, FILE *); +static char *canonicalize_name(const char *); +static int scale_round(int, int, int); + +static +Device *new_device(const char *name) +{ + Device *dev; + + dev = XtNew(Device); + dev->sizescale = 1; + dev->res = 0; + dev->unitwidth = 0; + dev->fonts = 0; + dev->X11 = 0; + dev->paperlength = 0; + dev->paperwidth = 0; + dev->name = XtNewString(name); + return dev; +} + +void device_destroy(Device *dev) +{ + DeviceFont *f; + + if (!dev) + return; + f = dev->fonts; + while (f) { + DeviceFont *tem = f; + f = f->next; + delete_font(tem); + } + + XtFree(dev->name); + XtFree((char *)dev); +} + +Device *device_load(const char *name) +{ + Device *dev; + FILE *fp; + int err = 0; + char buf[256]; + + fp = open_device_file(name, "DESC", ¤t_filename); + if (!fp) + return 0; + dev = new_device(name); + current_lineno = 0; + while (fgets(buf, sizeof(buf), fp)) { + char *p; + current_lineno++; + p = strtok(buf, WS); + if (p) { + int *np = 0; + char *q; + + if (strcmp(p, "charset") == 0) + break; + if (strcmp(p, "X11") == 0) + dev->X11 = 1; + else if (strcmp(p, "sizescale") == 0) + np = &dev->sizescale; + else if (strcmp(p, "res") == 0) + np = &dev->res; + else if (strcmp(p, "unitwidth") == 0) + np = &dev->unitwidth; + else if (strcmp(p, "paperwidth") == 0) + np = &dev->paperwidth; + else if (strcmp(p, "paperlength") == 0) + np = &dev->paperlength; + + if (np) { + q = strtok((char *)0, WS); + if (!q || sscanf(q, "%d", np) != 1 || *np <= 0) { + error("bad argument"); + err = 1; + break; + } + } + } + } + fclose(fp); + current_lineno = -1; + if (!err) { + if (dev->res == 0) { + error("missing res line"); + err = 1; + } + else if (dev->unitwidth == 0) { + error("missing unitwidth line"); + err = 1; + } + } + if (dev->paperlength == 0) + dev->paperlength = dev->res*11; + if (dev->paperwidth == 0) + dev->paperwidth = dev->res*8 + dev->res/2; + if (err) { + device_destroy(dev); + dev = 0; + } + XtFree(current_filename); + current_filename = 0; + return dev; +} + + +DeviceFont *device_find_font(Device *dev, const char *name) +{ + DeviceFont *f; + + if (!dev) + return 0; + for (f = dev->fonts; f; f = f->next) + if (strcmp(f->name, name) == 0) + return f; + return load_font(dev, name); +} + +static +DeviceFont *load_font(Device *dev, const char *name) +{ + FILE *fp; + char buf[256]; + DeviceFont *f; + int special = 0; + + fp = open_device_file(dev->name, name, ¤t_filename); + if (!fp) + return 0; + current_lineno = 0; + for (;;) { + char *p; + + if (!fgets(buf, sizeof(buf), fp)) { + error("no charset line"); + return 0; + } + current_lineno++; + p = strtok(buf, WS); + /* charset must be on a line by itself */ + if (p && strcmp(p, "charset") == 0 && strtok((char *)0, WS) == 0) + break; + if (p && strcmp(p, "special") == 0) + special = 1; + } + f = new_font(name, dev); + f->special = special; + if (!read_charset_section(f, fp)) { + delete_font(f); + f = 0; + } + else { + f->next = dev->fonts; + dev->fonts = f; + } + fclose(fp); + XtFree(current_filename); + current_filename = 0; + return f; +} + +static +DeviceFont *new_font(const char *name, Device *dev) +{ + int i; + DeviceFont *f; + + f = XtNew(DeviceFont); + f->name = XtNewString(name); + f->dev = dev; + f->special = 0; + f->next = 0; + for (i = 0; i < CHAR_TABLE_SIZE; i++) + f->char_table[i] = 0; + for (i = 0; i < 256; i++) + f->code_table[i] = 0; + return f; +} + +static +void delete_font(DeviceFont *f) +{ + int i; + + if (!f) + return; + XtFree(f->name); + for (i = 0; i < CHAR_TABLE_SIZE; i++) { + struct charinfo *ptr = f->char_table[i]; + while (ptr) { + struct charinfo *tem = ptr; + ptr = ptr->next; + XtFree((char *)tem); + } + } + XtFree((char *)f); +} + + +static +unsigned hash_name(const char *name) +{ + unsigned n = 0; + /* XXX do better than this */ + while (*name) + n = (n << 1) ^ *name++; + + return n; +} + +static +int scale_round(int n, int x, int y) +{ + int y2; + + if (x == 0) + return 0; + y2 = y/2; + if (n >= 0) { + if (n <= (INT_MAX - y2)/x) + return (n*x + y2)/y; + return (int)(n*(double)x/(double)y + .5); + } + else { + if (-(unsigned)n <= (-(unsigned)INT_MIN - y2)/x) + return (n*x - y2)/y; + return (int)(n*(double)x/(double)y + .5); + } +} + +static +char *canonicalize_name(const char *s) +{ + static char ch[2]; + if (s[0] == 'c' && s[1] == 'h' && s[2] == 'a' && s[3] == 'r') { + const char *p; + int n; + + for (p = s + 4; *p; p++) + if (!isascii(*p) || !isdigit((unsigned char)*p)) + return (char *)s; + n = atoi(s + 4); + if (n >= 0 && n <= 0xff) { + ch[0] = (char)n; + return ch; + } + } + return (char *)s; +} + +/* Return 1 if the character is present in the font; widthp gets the +width if non-null. */ + +int device_char_width(DeviceFont *f, int ps, const char *name, int *widthp) +{ + struct charinfo *p; + + name = canonicalize_name(name); + for (p = f->char_table[hash_name(name) % CHAR_TABLE_SIZE];; p = p->next) { + if (!p) + return 0; + if (strcmp(p->name, name) == 0) + break; + } + *widthp = scale_round(p->width, ps, f->dev->unitwidth); + return 1; +} + +int device_code_width(DeviceFont *f, int ps, int code, int *widthp) +{ + struct charinfo *p; + + for (p = f->code_table[code & 0xff];; p = p->code_next) { + if (!p) + return 0; + if (p->code == code) + break; + } + *widthp = scale_round(p->width, ps, f->dev->unitwidth); + return 1; +} + +char *device_name_for_code(DeviceFont *f, int code) +{ + static struct charinfo *state = 0; + if (f) + state = f->code_table[code & 0xff]; + for (; state; state = state->code_next) + if (state->code == code && state->name[0] != '\0') { + char *name = state->name; + state = state->code_next; + return name; + } + return 0; +} + +int device_font_special(DeviceFont *f) +{ + return f->special; +} + +static +struct charinfo *add_char(DeviceFont *f, const char *name, int width, int code) +{ + struct charinfo **pp; + struct charinfo *ci; + + name = canonicalize_name(name); + if (strcmp(name, "---") == 0) + name = ""; + + ci = (struct charinfo *)XtMalloc(XtOffsetOf(struct charinfo, name[0]) + + strlen(name) + 1); + + strcpy(ci->name, name); + ci->width = width; + ci->code = code; + + if (*name != '\0') { + pp = &f->char_table[hash_name(name) % CHAR_TABLE_SIZE]; + ci->next = *pp; + *pp = ci; + } + pp = &f->code_table[code & 0xff]; + ci->code_next = *pp; + *pp = ci; + return ci; +} + +/* Return non-zero for success. */ + +static +int read_charset_section(DeviceFont *f, FILE *fp) +{ + struct charinfo *last_charinfo = 0; + char buf[256]; + + while (fgets(buf, sizeof(buf), fp)) { + char *name; + int width; + int code; + char *p; + + current_lineno++; + name = strtok(buf, WS); + if (!name) + continue; /* ignore blank lines */ + p = strtok((char *)0, WS); + if (!p) /* end of charset section */ + break; + if (strcmp(p, "\"") == 0) { + if (!last_charinfo) { + error("first line of charset section cannot use '\"'"); + return 0; + } + else + (void)add_char(f, name, + last_charinfo->width, last_charinfo->code); + } + else { + char *q; + if (sscanf(p, "%d", &width) != 1) { + error("bad width field"); + return 0; + } + p = strtok((char *)0, WS); + if (!p) { + error("missing type field"); + return 0; + } + p = strtok((char *)0, WS); + if (!p) { + error("missing code field"); + return 0; + } + code = (int)strtol(p, &q, 0); + if (q == p) { + error("bad code field"); + return 0; + } + last_charinfo = add_char(f, name, width, code); + } + } + return 1; +} + +static +FILE *find_file(const char *file, char **result) +{ + char *buf = NULL; + int bufsiz = 0; + int flen; + FILE *fp; + char *path; + char *env; + + env = getenv(FONTPATH_ENV_VAR); + path = XtMalloc(((env && *env) ? strlen(env) + 1 : 0) + + strlen(FONTPATH) + 1); + *path = '\0'; + if (env && *env) { + strcat(path, env); + strcat(path, ":"); + } + strcat(path, FONTPATH); + + *result = NULL; + + if (file == NULL) + return NULL; + if (*file == '\0') + return NULL; + + if (*file == '/') { + fp = fopen(file, "r"); + if (fp) + *result = XtNewString(file); + return fp; + } + + flen = strlen(file); + + while (*path) { + int len; + char *start, *end; + + start = path; + end = strchr(path, ':'); + if (end) + path = end + 1; + else + path = end = strchr(path, '\0'); + if (start >= end) + continue; + if (end[-1] == '/') + --end; + len = (end - start) + 1 + flen + 1; + if (len > bufsiz) { + if (buf) + buf = XtRealloc(buf, len); + else + buf = XtMalloc(len); + bufsiz = len; + } + memcpy(buf, start, end - start); + buf[end - start] = '/'; + strcpy(buf + (end - start) + 1, file); + fp = fopen(buf, "r"); + if (fp) { + *result = buf; + return fp; + } + } + XtFree(buf); + return NULL; +} + +static +FILE *open_device_file(const char *device_name, const char *file_name, + char **result) +{ + char *buf; + FILE *fp; + + buf = XtMalloc(3 + strlen(device_name) + 1 + strlen(file_name) + 1); + sprintf(buf, "dev%s/%s", device_name, file_name); + fp = find_file(buf, result); + if (!fp) { + fprintf(stderr, "can't find device file '%s'\n", file_name); + fflush(stderr); + } + XtFree(buf); + return fp; +} + +static +void error(const char *s) +{ + if (current_filename) { + fprintf(stderr, "%s:", current_filename); + if (current_lineno > 0) + fprintf(stderr, "%d:", current_lineno); + putc(' ', stderr); + } + fputs(s, stderr); + putc('\n', stderr); + fflush(stderr); +} + +/* +Local Variables: +c-indent-level: 4 +c-continued-statement-offset: 4 +c-brace-offset: -4 +c-argdecl-indent: 4 +c-label-offset: -4 +c-tab-always-indent: nil +End: +*/ diff --git a/src/devices/xditview/device.h b/src/devices/xditview/device.h new file mode 100644 index 0000000..6f2944b --- /dev/null +++ b/src/devices/xditview/device.h @@ -0,0 +1,21 @@ + +typedef struct _DeviceFont DeviceFont; + +typedef struct _Device { + char *name; + int sizescale; + int res; + int unitwidth; + int paperlength; + int paperwidth; + int X11; + DeviceFont *fonts; +} Device; + +void device_destroy(Device *); +Device *device_load(const char *); +DeviceFont *device_find_font(Device *, const char *); +int device_char_width(DeviceFont *, int, const char *, int *); +char *device_name_for_code(DeviceFont *, int); +int device_code_width(DeviceFont *, int, int, int *); +int device_font_special(DeviceFont *); diff --git a/src/devices/xditview/draw.c b/src/devices/xditview/draw.c new file mode 100644 index 0000000..288d98a --- /dev/null +++ b/src/devices/xditview/draw.c @@ -0,0 +1,709 @@ +/* + * draw.c + * + * accept dvi function calls and translate to X + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <X11/Xos.h> +#include <X11/IntrinsicP.h> +#include <X11/StringDefs.h> +#include <stdio.h> +#include <ctype.h> +#include <math.h> + +/* math.h on a Sequent doesn't define M_PI, apparently */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#include "DviP.h" +#include "draw.h" +#include "font.h" + +#define DeviceToX(dw, n) ((int)((n) * (dw)->dvi.scale_factor + .5)) +#define XPos(dw) (DeviceToX((dw), (dw)->dvi.state->x - \ + (dw)->dvi.text_device_width) + (dw)->dvi.text_x_width) +#define YPos(dw) (DeviceToX((dw), (dw)->dvi.state->y)) + +/* forward reference */ +static int FakeCharacter(DviWidget, char *, int); + +/* shadowed by a macro definition in parse.c, and unused elsewhere */ +#if 0 +static void +HorizontalMove(DviWidget dw, int delta) +{ + dw->dvi.state->x += delta; +} +#endif + +void +HorizontalGoto(DviWidget dw, int NewPosition) +{ + dw->dvi.state->x = NewPosition; +} + +void +VerticalMove(DviWidget dw, int delta) +{ + dw->dvi.state->y += delta; +} + +void +VerticalGoto(DviWidget dw, int NewPosition) +{ + dw->dvi.state->y = NewPosition; +} + +static void +AdjustCacheDeltas (DviWidget dw) +{ + int extra; + int nadj; + int i; + + nadj = 0; + extra = DeviceToX(dw, dw->dvi.text_device_width) + - dw->dvi.text_x_width; + if (extra == 0) + return; + for (i = 0; i <= dw->dvi.cache.index; i++) + if (dw->dvi.cache.adjustable[i]) + ++nadj; + dw->dvi.text_x_width += extra; + if (nadj <= 1) + return; + for (i = 0; i <= dw->dvi.cache.index; i++) + if (dw->dvi.cache.adjustable[i]) { + int x; + int *deltap; + + x = extra/nadj; + deltap = &dw->dvi.cache.cache[i].delta; +#define MIN_DELTA 2 + if (*deltap > 0 && x + *deltap < MIN_DELTA) { + x = MIN_DELTA - *deltap; + if (x <= 0) + *deltap = MIN_DELTA; + else + x = 0; + } + else + *deltap += x; + extra -= x; + --nadj; + dw->dvi.cache.adjustable[i] = 0; + } +} + +void +FlushCharCache (DviWidget dw) +{ + if (dw->dvi.cache.char_index != 0) { + AdjustCacheDeltas (dw); + XDrawText (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + dw->dvi.cache.start_x, dw->dvi.cache.start_y, + dw->dvi.cache.cache, dw->dvi.cache.index + 1); + } + dw->dvi.cache.index = 0; + dw->dvi.cache.max = DVI_TEXT_CACHE_SIZE; +#if 0 + if (dw->dvi.noPolyText) + dw->dvi.cache.max = 1; +#endif + dw->dvi.cache.char_index = 0; + dw->dvi.cache.cache[0].nchars = 0; + dw->dvi.cache.start_x = dw->dvi.cache.x = XPos (dw); + dw->dvi.cache.start_y = dw->dvi.cache.y = YPos (dw); +} + +void +Newline (DviWidget dw) +{ + FlushCharCache (dw); + dw->dvi.text_x_width = dw->dvi.text_device_width = 0; + dw->dvi.word_flag = 0; +} + +void +Word (DviWidget dw) +{ + dw->dvi.word_flag = 1; +} + +#define charWidth(fi,c) (\ + (fi)->per_char ?\ + (fi)->per_char[(c) - (fi)->min_char_or_byte2].width\ + :\ + (fi)->max_bounds.width\ +) + + +static +int charExists (XFontStruct *fi, int c) +{ + XCharStruct *p; + + /* 'c' is always >= 0 */ + if (fi->per_char == NULL + || (unsigned int)c < fi->min_char_or_byte2 + || (unsigned int)c > fi->max_char_or_byte2) + return 0; + p = fi->per_char + (c - fi->min_char_or_byte2); + return (p->lbearing != 0 || p->rbearing != 0 || p->width != 0 + || p->ascent != 0 || p->descent != 0 || p->attributes != 0); +} + +/* 'wid' is in device units */ +static void +DoCharacter (DviWidget dw, int c, int wid) +{ + register XFontStruct *font; + register XTextItem *text; + int x, y; + + x = XPos(dw); + y = YPos(dw); + + /* + * quick and dirty extents calculation: + */ + if (!(y + 24 >= dw->dvi.extents.y1 + && y - 24 <= dw->dvi.extents.y2 +#if 0 + && x + 24 >= dw->dvi.extents.x1 + && x - 24 <= dw->dvi.extents.x2 +#endif + )) + return; + + if (y != dw->dvi.cache.y + || dw->dvi.cache.char_index >= DVI_CHAR_CACHE_SIZE) { + FlushCharCache (dw); + x = dw->dvi.cache.x; + dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0; + } + /* + * load a new font, if the current block is not empty, + * step to the next. + */ + if (dw->dvi.cache.font_size != dw->dvi.state->font_size || + dw->dvi.cache.font_number != dw->dvi.state->font_number) + { + FlushCharCache (dw); + x = dw->dvi.cache.x; + dw->dvi.cache.font_size = dw->dvi.state->font_size; + dw->dvi.cache.font_number = dw->dvi.state->font_number; + dw->dvi.cache.font = QueryFont (dw, + dw->dvi.cache.font_number, + dw->dvi.cache.font_size); + if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) { + ++dw->dvi.cache.index; + if (dw->dvi.cache.index >= dw->dvi.cache.max) + FlushCharCache (dw); + dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0; + dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0; + } + } + if (x != dw->dvi.cache.x || dw->dvi.word_flag) { + if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) { + ++dw->dvi.cache.index; + if (dw->dvi.cache.index >= dw->dvi.cache.max) + FlushCharCache (dw); + dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0; + dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0; + } + dw->dvi.cache.adjustable[dw->dvi.cache.index] + = dw->dvi.word_flag; + dw->dvi.word_flag = 0; + } + font = dw->dvi.cache.font; + text = &dw->dvi.cache.cache[dw->dvi.cache.index]; + if (text->nchars == 0) { + text->chars = &dw->dvi.cache.char_cache[dw->dvi.cache.char_index]; + text->delta = x - dw->dvi.cache.x; + if (font != dw->dvi.font) { + text->font = font->fid; + dw->dvi.font = font; + } else + text->font = None; + dw->dvi.cache.x += text->delta; + } + if (charExists(font, c)) { + int w; + dw->dvi.cache.char_cache[dw->dvi.cache.char_index++] = (char) c; + ++text->nchars; + w = charWidth(font, c); + dw->dvi.cache.x += w; + if (wid != 0) { + dw->dvi.text_x_width += w; + dw->dvi.text_device_width += wid; + } + } +} + +static +int FindCharWidth (DviWidget dw, char *buf, int *widp) +{ + int maxpos; + int i; + + if (dw->dvi.device_font == 0 + || dw->dvi.state->font_number != dw->dvi.device_font_number) { + dw->dvi.device_font_number = dw->dvi.state->font_number; + dw->dvi.device_font + = QueryDeviceFont (dw, dw->dvi.device_font_number); + } + if (dw->dvi.device_font + && device_char_width (dw->dvi.device_font, + dw->dvi.state->font_size, buf, widp)) + return 1; + + maxpos = MaxFontPosition (dw); + for (i = 1; i <= maxpos; i++) { + DeviceFont *f = QueryDeviceFont (dw, i); + if (f && device_font_special (f) + && device_char_width (f, dw->dvi.state->font_size, + buf, widp)) { + dw->dvi.state->font_number = i; + return 1; + } + } + return 0; +} + +/* Return the width of the character in device units. */ + +int +PutCharacter (DviWidget dw, char *buf) +{ + int prevFont; + int c = -1; + int wid = 0; + DviCharNameMap *map; + + if (!dw->dvi.display_enable) + return 0; /* The width doesn't matter in this case. */ + prevFont = dw->dvi.state->font_number; + if (!FindCharWidth (dw, buf, &wid)) + return 0; + map = QueryFontMap (dw, dw->dvi.state->font_number); + if (map) + c = DviCharIndex (map, buf); + if (c >= 0) + DoCharacter (dw, c, wid); + else + (void) FakeCharacter (dw, buf, wid); + dw->dvi.state->font_number = prevFont; + return wid; +} + +/* Return 1 if we can fake it; 0 otherwise. */ + +static +int FakeCharacter (DviWidget dw, char *buf, int wid) +{ + int oldx, oldw; + char ch[2]; + const char *chars = 0; + + if (buf[0] == '\0' || buf[1] == '\0' || buf[2] != '\0') + return 0; +#define pack2(c1, c2) (((c1) << 8) | (c2)) + + switch (pack2(buf[0], buf[1])) { + case pack2('f', 'i'): + chars = "fi"; + break; + case pack2('f', 'l'): + chars = "fl"; + break; + case pack2('f', 'f'): + chars = "ff"; + break; + case pack2('F', 'i'): + chars = "ffi"; + break; + case pack2('F', 'l'): + chars = "ffl"; + break; + } + if (!chars) + return 0; + oldx = dw->dvi.state->x; + oldw = dw->dvi.text_device_width; + ch[1] = '\0'; + for (; *chars; chars++) { + ch[0] = *chars; + dw->dvi.state->x += PutCharacter (dw, ch); + } + dw->dvi.state->x = oldx; + dw->dvi.text_device_width = oldw + wid; + return 1; +} + +void +PutNumberedCharacter (DviWidget dw, int c) +{ + char *name; + int wid; + DviCharNameMap *map; + + if (!dw->dvi.display_enable) + return; + + if (dw->dvi.device_font == 0 + || dw->dvi.state->font_number != dw->dvi.device_font_number) { + dw->dvi.device_font_number = dw->dvi.state->font_number; + dw->dvi.device_font + = QueryDeviceFont (dw, dw->dvi.device_font_number); + } + + if (dw->dvi.device_font == 0 + || !device_code_width (dw->dvi.device_font, + dw->dvi.state->font_size, c, &wid)) + return; + if (dw->dvi.native) { + DoCharacter (dw, c, wid); + return; + } + map = QueryFontMap (dw, dw->dvi.state->font_number); + if (!map) + return; + for (name = device_name_for_code (dw->dvi.device_font, c); + name; + name = device_name_for_code ((DeviceFont *)0, c)) { + int code = DviCharIndex (map, name); + if (code >= 0) { + DoCharacter (dw, code, wid); + break; + } + if (FakeCharacter (dw, name, wid)) + break; + } +} + +/* unused */ +#if 0 +static void +ClearPage (DviWidget dw) +{ + XClearWindow (XtDisplay (dw), XtWindow (dw)); +} +#endif + +static void +setGC (DviWidget dw) +{ + int desired_line_width; + + if (dw->dvi.line_thickness < 0) + desired_line_width = (int)(((double)dw->dvi.device_resolution + * dw->dvi.state->font_size) + / (10.0*72.0*dw->dvi.sizescale)); + else + desired_line_width = dw->dvi.line_thickness; + + if (desired_line_width != dw->dvi.line_width) { + XGCValues values; + values.line_width = DeviceToX(dw, desired_line_width); + if (values.line_width == 0) + values.line_width = 1; + XChangeGC(XtDisplay (dw), dw->dvi.normal_GC, + GCLineWidth, &values); + dw->dvi.line_width = desired_line_width; + } +} + +static void +setFillGC (DviWidget dw) +{ + int fill_type; + unsigned long mask = GCFillStyle | GCForeground; + + fill_type = (dw->dvi.fill * 10) / (DVI_FILL_MAX + 1); + if (dw->dvi.fill_type != fill_type) { + XGCValues values; + if (fill_type <= 0) { + values.foreground = dw->dvi.background; + values.fill_style = FillSolid; + } else if (fill_type >= 9) { + values.foreground = dw->dvi.foreground; + values.fill_style = FillSolid; + } else { + values.foreground = dw->dvi.foreground; + values.fill_style = FillOpaqueStippled; + values.stipple = dw->dvi.gray[fill_type - 1]; + mask |= GCStipple; + } + XChangeGC(XtDisplay (dw), dw->dvi.fill_GC, mask, &values); + dw->dvi.fill_type = fill_type; + } +} + +void +DrawLine (DviWidget dw, int x, int y) +{ + int xp, yp; + + AdjustCacheDeltas (dw); + setGC (dw); + xp = XPos (dw); + yp = YPos (dw); + XDrawLine (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + xp, yp, + xp + DeviceToX (dw, x), yp + DeviceToX (dw, y)); +} + +void +DrawCircle (DviWidget dw, int diam) +{ + int d; + + AdjustCacheDeltas (dw); + setGC (dw); + d = DeviceToX (dw, diam); + XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + XPos (dw), YPos (dw) - d/2, + d, d, 0, 64*360); +} + +void +DrawFilledCircle (DviWidget dw, int diam) +{ + int d; + + AdjustCacheDeltas (dw); + setFillGC (dw); + d = DeviceToX (dw, diam); + XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC, + XPos (dw), YPos (dw) - d/2, + d, d, 0, 64*360); + XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC, + XPos (dw), YPos (dw) - d/2, + d, d, 0, 64*360); +} + +void +DrawEllipse (DviWidget dw, int a, int b) +{ + AdjustCacheDeltas (dw); + setGC (dw); + XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + XPos (dw), YPos (dw) - DeviceToX (dw, b/2), + DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360); +} + +void +DrawFilledEllipse (DviWidget dw, int a, int b) +{ + AdjustCacheDeltas (dw); + setFillGC (dw); + XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC, + XPos (dw), YPos (dw) - DeviceToX (dw, b/2), + DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360); + XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC, + XPos (dw), YPos (dw) - DeviceToX (dw, b/2), + DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360); +} + +void +DrawArc (DviWidget dw, int x_0, int y_0, int x_1, int y_1) +{ + int angle1, angle2; + int rad = (int)((sqrt ((double)x_0*x_0 + (double)y_0*y_0) + + sqrt ((double)x_1*x_1 + (double)y_1*y_1) + + 1.0)/2.0); + if ((x_0 == 0 && y_0 == 0) || (x_1 == 0 && y_1 == 0)) + return; + angle1 = (int)(atan2 ((double)y_0, (double)-x_0)*180.0*64.0/M_PI); + angle2 = (int)(atan2 ((double)-y_1, (double)x_1)*180.0*64.0/M_PI); + + angle2 -= angle1; + if (angle2 < 0) + angle2 += 64*360; + + AdjustCacheDeltas (dw); + setGC (dw); + + rad = DeviceToX (dw, rad); + XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + XPos (dw) + DeviceToX (dw, x_0) - rad, + YPos (dw) + DeviceToX (dw, y_0) - rad, + rad*2, rad*2, angle1, angle2); +} + +void +DrawPolygon (DviWidget dw, int *v, int n) +{ + XPoint *p; + int i; + int dx, dy; + + n /= 2; + + AdjustCacheDeltas (dw); + setGC (dw); + p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint)); + p[0].x = XPos (dw); + p[0].y = YPos (dw); + dx = 0; + dy = 0; + for (i = 0; i < n; i++) { + dx += v[2*i]; + p[i + 1].x = DeviceToX (dw, dx) + p[0].x; + dy += v[2*i + 1]; + p[i + 1].y = DeviceToX (dw, dy) + p[0].y; + } + p[n+1].x = p[0].x; + p[n+1].y = p[0].y; + XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + p, n + 2, CoordModeOrigin); + XtFree((char *)p); +} + +void +DrawFilledPolygon (DviWidget dw, int *v, int n) +{ + XPoint *p; + int i; + int dx, dy; + + n /= 2; + if (n < 2) + return; + + AdjustCacheDeltas (dw); + setFillGC (dw); + p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint)); + p[0].x = p[n+1].x = XPos (dw); + p[0].y = p[n+1].y = YPos (dw); + dx = 0; + dy = 0; + for (i = 0; i < n; i++) { + dx += v[2*i]; + p[i + 1].x = DeviceToX (dw, dx) + p[0].x; + dy += v[2*i + 1]; + p[i + 1].y = DeviceToX (dw, dy) + p[0].y; + } + XFillPolygon (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC, + p, n + 1, Complex, CoordModeOrigin); + XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC, + p, n + 2, CoordModeOrigin); + XtFree((char *)p); +} + +#define POINTS_MAX 10000 + +static void +appendPoint(XPoint *points, int *pointi, int x, int y) +{ + if (*pointi < POINTS_MAX) { + points[*pointi].x = x; + points[*pointi].y = y; + *pointi += 1; + } +} + +#define FLATNESS 1 + +static void +flattenCurve(XPoint *points, int *pointi, + int x_2, int y_2, int x_3, int y_3, int x_4, int y_4) +{ + int x_1, y_1, dx, dy, n1, n2, n; + + x_1 = points[*pointi - 1].x; + y_1 = points[*pointi - 1].y; + + dx = x_4 - x_1; + dy = y_4 - y_1; + + n1 = dy*(x_2 - x_1) - dx*(y_2 - y_1); + n2 = dy*(x_3 - x_1) - dx*(y_3 - y_1); + if (n1 < 0) + n1 = -n1; + if (n2 < 0) + n2 = -n2; + n = n1 > n2 ? n1 : n2; + + if (n*n / (dy*dy + dx*dx) <= FLATNESS*FLATNESS) + appendPoint (points, pointi, x_4, y_4); + else { + flattenCurve (points, pointi, + (x_1 + x_2)/2, + (y_1 + y_2)/2, + (x_1 + x_2*2 + x_3)/4, + (y_1 + y_2*2 + y_3)/4, + (x_1 + 3*x_2 + 3*x_3 + x_4)/8, + (y_1 + 3*y_2 + 3*y_3 + y_4)/8); + flattenCurve (points, pointi, + (x_2 + x_3*2 + x_4)/4, + (y_2 + y_3*2 + y_4)/4, + (x_3 + x_4)/2, + (y_3 + y_4)/2, + x_4, + y_4); + } +} + +void +DrawSpline (DviWidget dw, int *v, int n) +{ + int sx, sy, tx, ty; + int ox, oy, dx, dy; + int i; + int pointi; + XPoint points[POINTS_MAX]; + + if (n == 0 || (n & 1) != 0) + return; + AdjustCacheDeltas (dw); + setGC (dw); + ox = XPos (dw); + oy = YPos (dw); + dx = v[0]; + dy = v[1]; + sx = ox; + sy = oy; + tx = sx + DeviceToX (dw, dx); + ty = sy + DeviceToX (dw, dy); + + pointi = 0; + + appendPoint (points, &pointi, sx, sy); + appendPoint (points, &pointi, (sx + tx)/2, (sy + ty)/2); + + for (i = 2; i < n; i += 2) { + int ux = ox + DeviceToX (dw, dx += v[i]); + int uy = oy + DeviceToX (dw, dy += v[i+1]); + flattenCurve (points, &pointi, + (sx + tx*5)/6, (sy + ty*5)/6, + (tx*5 + ux)/6, (ty*5 + uy)/6, + (tx + ux)/2, (ty + uy)/2); + sx = tx; + sy = ty; + tx = ux; + ty = uy; + } + + appendPoint (points, &pointi, tx, ty); + + XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC, + points, pointi, CoordModeOrigin); +} + + +/* +Local Variables: +c-indent-level: 8 +c-continued-statement-offset: 8 +c-brace-offset: -8 +c-argdecl-indent: 8 +c-label-offset: -8 +c-tab-always-indent: nil +End: +*/ diff --git a/src/devices/xditview/draw.h b/src/devices/xditview/draw.h new file mode 100644 index 0000000..7a3772c --- /dev/null +++ b/src/devices/xditview/draw.h @@ -0,0 +1,18 @@ +void HorizontalGoto(DviWidget, int); +void VerticalGoto(DviWidget, int); +void VerticalMove(DviWidget, int); +void FlushCharCache(DviWidget); +void Newline(DviWidget); +void Word(DviWidget); +int PutCharacter(DviWidget, char *); +void PutNumberedCharacter(DviWidget, int); +void DrawLine(DviWidget, int, int); +void DrawCircle(DviWidget, int); +void DrawFilledCircle(DviWidget, int); +void DrawEllipse(DviWidget, int, int); +void DrawFilledEllipse(DviWidget, int, int); +void DrawArc(DviWidget, int, int, int, int); +void DrawPolygon(DviWidget, int *, int); +void DrawFilledPolygon(DviWidget, int *, int); +void DrawSpline(DviWidget, int *, int); + diff --git a/src/devices/xditview/font.c b/src/devices/xditview/font.c new file mode 100644 index 0000000..8462608 --- /dev/null +++ b/src/devices/xditview/font.c @@ -0,0 +1,446 @@ +/* + * font.c + * + * map dvi fonts to X fonts + */ + +#include <config.h> + +#include <X11/Xos.h> +#include <X11/IntrinsicP.h> +#include <X11/StringDefs.h> +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> + +#include "DviP.h" +#include "XFontName.h" +#include "font.h" + +/* forward reference */ +static void DisposeFontSizes(DviWidget, DviFontSizeList *); + +static char * +savestr (const char *s) +{ + char *n; + + if (!s) + return 0; + n = XtMalloc (strlen (s) + 1); + if (n) + strcpy (n, s); + return n; +} + +static DviFontList * +LookupFontByPosition (DviWidget dw, int position) +{ + DviFontList *f; + + for (f = dw->dvi.fonts; f; f = f->next) + if (f->dvi_number == position) + break; + return f; +} + +int +MaxFontPosition (DviWidget dw) +{ + DviFontList *f; + int n = -1; + + for (f = dw->dvi.fonts; f; f = f->next) + if (f->dvi_number > n) + n = f->dvi_number; + return n; +} + +static DviFontSizeList * +LookupFontSizeBySize (DviWidget dw, DviFontList *f, int size) +{ + DviFontSizeList *fs, *best = 0, *smallest = 0; + int bestsize = 0; + XFontName fontName; + unsigned int fontNameAttributes; + char fontNameString[2048]; + int decipointsize; + + if (f->scalable) { + decipointsize = (10*size)/dw->dvi.sizescale; + for (best = f->sizes; best; best = best->next) + if (best->size == decipointsize) + return best; + best = (DviFontSizeList *) XtMalloc(sizeof *best); + best->next = f->sizes; + best->size = decipointsize; + f->sizes = best; + XParseFontName (f->x_name, &fontName, &fontNameAttributes); + fontNameAttributes &= ~(FontNamePixelSize|FontNameAverageWidth); + fontNameAttributes |= FontNameResolutionX; + fontNameAttributes |= FontNameResolutionY; + fontNameAttributes |= FontNamePointSize; + fontName.ResolutionX = dw->dvi.display_resolution; + fontName.ResolutionY = dw->dvi.display_resolution; + fontName.PointSize = decipointsize; + XFormatFontName (&fontName, fontNameAttributes, fontNameString); + best->x_name = savestr (fontNameString); + best->doesnt_exist = 0; + best->font = 0; + return best; + } + for (fs = f->sizes; fs; fs=fs->next) { + if (dw->dvi.sizescale*fs->size <= 10*size + && fs->size >= bestsize) { + best = fs; + bestsize = fs->size; + } + if (smallest == 0 || fs->size < smallest->size) + smallest = fs; + } + return best ? best : smallest; +} + +static char * +SkipFontNameElement (char *n) +{ + while (*n != '-') + if (!*++n) + return 0; + return n+1; +} + +# define SizePosition 8 +# define EncodingPosition 13 + +static int +ConvertFontNameToSize (char *n) +{ + int i, size; + + for (i = 0; i < SizePosition; i++) { + n = SkipFontNameElement (n); + if (!n) + return -1; + } + size = atoi (n); + return size; +} + +static char * +ConvertFontNameToEncoding (char *n) +{ + int i; + for (i = 0; i < EncodingPosition; i++) { + n = SkipFontNameElement (n); + if (!n) + return 0; + } + return n; +} + +static DviFontSizeList * +InstallFontSizes (DviWidget dw, const char *x_name, Boolean *scalablep) +{ + char fontNameString[2048]; + char **fonts; + int i, count; + int size; + DviFontSizeList *sizes, *new_size; + XFontName fontName; + unsigned int fontNameAttributes; + + *scalablep = FALSE; + if (!XParseFontName ((XFontNameString)x_name, &fontName, + &fontNameAttributes)) + return 0; + fontNameAttributes &= ~(FontNamePixelSize|FontNamePointSize + |FontNameAverageWidth); + fontNameAttributes |= FontNameResolutionX; + fontNameAttributes |= FontNameResolutionY; + fontName.ResolutionX = dw->dvi.display_resolution; + fontName.ResolutionY = dw->dvi.display_resolution; + XFormatFontName (&fontName, fontNameAttributes, fontNameString); + fonts = XListFonts (XtDisplay (dw), fontNameString, 10000000, &count); + sizes = 0; + for (i = 0; i < count; i++) { + size = ConvertFontNameToSize (fonts[i]); + if (size == 0) { + DisposeFontSizes (dw, sizes); + sizes = 0; + *scalablep = TRUE; + break; + } + if (size != -1) { + new_size = (DviFontSizeList *) XtMalloc (sizeof *new_size); + new_size->next = sizes; + new_size->size = size; + new_size->x_name = savestr (fonts[i]); + new_size->doesnt_exist = 0; + new_size->font = 0; + sizes = new_size; + } + } + XFreeFontNames (fonts); + return sizes; +} + +static void +DisposeFontSizes (DviWidget dw, DviFontSizeList *fs) +{ + DviFontSizeList *next; + + for (; fs; fs=next) { + next = fs->next; + if (fs->x_name) + XtFree (fs->x_name); + if (fs->font && fs->font != dw->dvi.default_font) { + XUnloadFont (XtDisplay (dw), fs->font->fid); + XFree ((char *)fs->font); + } + XtFree ((char *) fs); + } +} + +static DviFontList * +InstallFont (DviWidget dw, int position, + const char *dvi_name, const char *x_name) +{ + DviFontList *f; + char *encoding; + + if ((f = LookupFontByPosition (dw, position)) != NULL) { + /* + * ignore gratuitous font loading + */ + if (!strcmp (f->dvi_name, dvi_name) && + !strcmp (f->x_name, x_name)) + return f; + + DisposeFontSizes (dw, f->sizes); + if (f->dvi_name) + XtFree (f->dvi_name); + if (f->x_name) + XtFree (f->x_name); + f->device_font = 0; + } else { + f = (DviFontList *) XtMalloc (sizeof (*f)); + f->next = dw->dvi.fonts; + dw->dvi.fonts = f; + } + f->initialized = FALSE; + f->dvi_name = savestr (dvi_name); + f->device_font = device_find_font (dw->dvi.device, dvi_name); + f->x_name = savestr (x_name); + f->dvi_number = position; + f->sizes = 0; + f->scalable = FALSE; + if (f->x_name) { + encoding = ConvertFontNameToEncoding (f->x_name); + f->char_map = DviFindMap (encoding); + } else + f->char_map = 0; + /* + * force requery of fonts + */ + dw->dvi.font = 0; + dw->dvi.font_number = -1; + dw->dvi.cache.font = 0; + dw->dvi.cache.font_number = -1; + dw->dvi.device_font = 0; + dw->dvi.device_font_number = -1; + return f; +} + +void +ForgetFonts (DviWidget dw) +{ + DviFontList *f = dw->dvi.fonts; + + while (f) { + DviFontList *tem = f; + + if (f->sizes) + DisposeFontSizes (dw, f->sizes); + if (f->dvi_name) + XtFree (f->dvi_name); + if (f->x_name) + XtFree (f->x_name); + f = f->next; + XtFree ((char *) tem); + } + + /* + * force requery of fonts + */ + dw->dvi.font = 0; + dw->dvi.font_number = -1; + dw->dvi.cache.font = 0; + dw->dvi.cache.font_number = -1; + dw->dvi.device_font = 0; + dw->dvi.device_font_number = -1; + dw->dvi.fonts = 0; +} + + +static char * +MapDviNameToXName (DviWidget dw, const char *dvi_name) +{ + DviFontMap *fm; + + for (fm = dw->dvi.font_map; fm; fm=fm->next) + if (!strcmp (fm->dvi_name, dvi_name)) + return fm->x_name; + return 0; +} + +#if 0 +static char * +MapXNameToDviName (DviWidget dw, const char *x_name) +{ + DviFontMap *fm; + + for (fm = dw->dvi.font_map; fm; fm=fm->next) + if (!strcmp (fm->x_name, x_name)) + return fm->dvi_name; + return 0; +} +#endif + +void +ParseFontMap (DviWidget dw) +{ + char dvi_name[1024]; + char x_name[2048]; + char *m, *s; + DviFontMap *fm, *new_map; + + if (dw->dvi.font_map) + DestroyFontMap (dw->dvi.font_map); + fm = 0; + m = dw->dvi.font_map_string; + while (*m) { + s = m; + while (*m && !isspace (*m)) + ++m; + strncpy (dvi_name, s, m-s); + dvi_name[m-s] = '\0'; + while (isspace (*m)) + ++m; + s = m; + while (*m && *m != '\n') + ++m; + strncpy (x_name, s, m-s); + x_name[m-s] = '\0'; + new_map = (DviFontMap *) XtMalloc (sizeof *new_map); + new_map->x_name = savestr (x_name); + new_map->dvi_name = savestr (dvi_name); + new_map->next = fm; + fm = new_map; + ++m; + } + dw->dvi.font_map = fm; +} + +void +DestroyFontMap (DviFontMap *font_map) +{ + DviFontMap *next; + + for (; font_map; font_map = next) { + next = font_map->next; + if (font_map->x_name) + XtFree (font_map->x_name); + if (font_map->dvi_name) + XtFree (font_map->dvi_name); + XtFree ((char *) font_map); + } +} + +/* ARGSUSED */ + +void +SetFontPosition (DviWidget dw, int position, + const char *dvi_name, const char *extra) +{ + char *x_name; + + x_name = MapDviNameToXName (dw, dvi_name); + if (x_name) + (void) InstallFont (dw, position, dvi_name, x_name); + + extra = extra; /* unused; suppress compiler warning */ +} + +XFontStruct * +QueryFont (DviWidget dw, int position, int size) +{ + DviFontList *f; + DviFontSizeList *fs; + + f = LookupFontByPosition (dw, position); + if (!f) + return dw->dvi.default_font; + if (!f->initialized) { + f->sizes = InstallFontSizes (dw, f->x_name, &f->scalable); + f->initialized = TRUE; + } + fs = LookupFontSizeBySize (dw, f, size); + if (!fs) + return dw->dvi.default_font; + if (!fs->font) { + if (fs->x_name) + fs->font = XLoadQueryFont (XtDisplay (dw), fs->x_name); + if (!fs->font) + fs->font = dw->dvi.default_font; + } + return fs->font; +} + +DeviceFont * +QueryDeviceFont (DviWidget dw, int position) +{ + DviFontList *f; + + f = LookupFontByPosition (dw, position); + if (!f) + return 0; + return f->device_font; +} + +DviCharNameMap * +QueryFontMap (DviWidget dw, int position) +{ + DviFontList *f; + + f = LookupFontByPosition (dw, position); + if (f) + return f->char_map; + else + return 0; +} + +#if 0 +LoadFont (DviWidget dw, int position, int size) +{ + XFontStruct *font; + + font = QueryFont (dw, position, size); + dw->dvi.font_number = position; + dw->dvi.font_size = size; + dw->dvi.font = font; + XSetFont (XtDisplay (dw), dw->dvi.normal_GC, font->fid); + return; +} +#endif + +/* +Local Variables: +c-indent-level: 8 +c-continued-statement-offset: 8 +c-brace-offset: -8 +c-argdecl-indent: 8 +c-label-offset: -8 +c-tab-always-indent: nil +End: +*/ diff --git a/src/devices/xditview/font.h b/src/devices/xditview/font.h new file mode 100644 index 0000000..ae6c765 --- /dev/null +++ b/src/devices/xditview/font.h @@ -0,0 +1,6 @@ +void DestroyFontMap(DviFontMap *); +void ForgetFonts (DviWidget dw); +int MaxFontPosition (DviWidget dw); +void ParseFontMap (DviWidget dw); +void SetFontPosition (DviWidget dw, int position, const char *dvi_name, + const char *extra); diff --git a/src/devices/xditview/gray1.bm b/src/devices/xditview/gray1.bm new file mode 100644 index 0000000..c40a95e --- /dev/null +++ b/src/devices/xditview/gray1.bm @@ -0,0 +1,4 @@ +#define gray1_width 3 +#define gray1_height 3 +static char gray1_bits[] = { + 0x00, 0x02, 0x00}; diff --git a/src/devices/xditview/gray2.bm b/src/devices/xditview/gray2.bm new file mode 100644 index 0000000..e87a1bc --- /dev/null +++ b/src/devices/xditview/gray2.bm @@ -0,0 +1,4 @@ +#define gray2_width 3 +#define gray2_height 3 +static char gray2_bits[] = { + 0x00, 0x03, 0x00}; diff --git a/src/devices/xditview/gray3.bm b/src/devices/xditview/gray3.bm new file mode 100644 index 0000000..d9313eb --- /dev/null +++ b/src/devices/xditview/gray3.bm @@ -0,0 +1,4 @@ +#define gray3_width 3 +#define gray3_height 3 +static char gray3_bits[] = { + 0x00, 0x03, 0x02}; diff --git a/src/devices/xditview/gray4.bm b/src/devices/xditview/gray4.bm new file mode 100644 index 0000000..dad142a --- /dev/null +++ b/src/devices/xditview/gray4.bm @@ -0,0 +1,4 @@ +#define gray4_width 3 +#define gray4_height 3 +static char gray4_bits[] = { + 0x00, 0x07, 0x02}; diff --git a/src/devices/xditview/gray5.bm b/src/devices/xditview/gray5.bm new file mode 100644 index 0000000..5f57618 --- /dev/null +++ b/src/devices/xditview/gray5.bm @@ -0,0 +1,4 @@ +#define gray5_width 3 +#define gray5_height 3 +static char gray5_bits[] = { + 0x04, 0x07, 0x02}; diff --git a/src/devices/xditview/gray6.bm b/src/devices/xditview/gray6.bm new file mode 100644 index 0000000..b76701d --- /dev/null +++ b/src/devices/xditview/gray6.bm @@ -0,0 +1,4 @@ +#define gray6_width 3 +#define gray6_height 3 +static char gray6_bits[] = { + 0x04, 0x07, 0x03}; diff --git a/src/devices/xditview/gray7.bm b/src/devices/xditview/gray7.bm new file mode 100644 index 0000000..ef47bc6 --- /dev/null +++ b/src/devices/xditview/gray7.bm @@ -0,0 +1,4 @@ +#define gray7_width 3 +#define gray7_height 3 +static char gray7_bits[] = { + 0x05, 0x07, 0x03}; diff --git a/src/devices/xditview/gray8.bm b/src/devices/xditview/gray8.bm new file mode 100644 index 0000000..12de7cb --- /dev/null +++ b/src/devices/xditview/gray8.bm @@ -0,0 +1,4 @@ +#define gray8_width 3 +#define gray8_height 3 +static char gray8_bits[] = { + 0x05, 0x07, 0x07}; diff --git a/src/devices/xditview/gxditview.1.man b/src/devices/xditview/gxditview.1.man new file mode 100644 index 0000000..920ccfd --- /dev/null +++ b/src/devices/xditview/gxditview.1.man @@ -0,0 +1,815 @@ +.TH gxditview @MAN1EXT@ "@MDATE@" "groff @VERSION@" +.SH Name +gxditview \- display +.I groff +intermediate output files in X11 +. +. +.\" ==================================================================== +.\" Legal Terms +.\" ==================================================================== +.\" +.\" Copyright 1991 Massachusetts Institute of Technology +.\" +.\" Permission to use, copy, modify, distribute, and sell this software +.\" and its documentation for any purpose is hereby granted without fee, +.\" provided that the above copyright notice appear in all copies and +.\" that both that copyright notice and this permission notice appear in +.\" supporting documentation, and that the name of M.I.T. not be used in +.\" advertising or publicity pertaining to distribution of the software +.\" without specific, written prior permission. M.I.T. makes no +.\" representations about the suitability of this software for any +.\" purpose. It is provided "as is" without express or implied +.\" warranty. +.\" +.\" M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +.\" INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN +.\" NO EVENT SHALL M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR +.\" CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +.\" OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +.\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +.\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +. +. +.\" Save and disable compatibility mode (for, e.g., Solaris 10/11). +.do nr *groff_gxditview_1_man_C \n[.cp] +.cp 0 +. +.\" Define fallback for groff 1.23's MR macro if the system lacks it. +.nr do-fallback 0 +.if !\n(.f .nr do-fallback 1 \" mandoc +.if \n(.g .if !d MR .nr do-fallback 1 \" older groff +.if !\n(.g .nr do-fallback 1 \" non-groff *roff +.if \n[do-fallback] \{\ +. de MR +. ie \\n(.$=1 \ +. I \%\\$1 +. el \ +. IR \%\\$1 (\\$2)\\$3 +. . +.\} +.rr do-fallback +. +. +.\" ==================================================================== +.SH Synopsis +.\" ==================================================================== +. +.SY gxditview +.RI [ X-toolkit-option \~.\|.\|.\&] +.RB [ \-backingStore\~\c +.IR backing-store-type ] +.RB [ \-filename\~\c +.IR file ] +.\" While recognized, the relevant logic is "#if 0"ed out in draw.c. +.\" .RB [ \-noPolyText ] +.RB [ \-page\~\c +.IR page-number ] +.RB [ \-printCommand\~\c +.IR command ] +.RB [ \-resolution\~\c +.IR resolution ] +.I file +.YS +. +. +.SY gxditview +.B \-help +. +.SY gxditview +.B \-\-help +.YS +. +. +.SY gxditview +.B \-version +. +.SY gxditview +.B \-\-version +.YS +. +. +.\" ==================================================================== +.SH Description +.\" ==================================================================== +. +.I gxditview +interprets and displays the intermediate output format of +.MR groff @MAN1EXT@ +on an X11\~display. +. +It uses the standard X11 fonts, +so it does not require access to the server machine for font loading. +. +There are several ways to use +.IR gxditview . +. +. +.PP +The +intermediate output format of +.IR groff , +documented in +.MR groff_out @MAN5EXT@ , +is produced by +.I @g@troff +or the +.B \-Z +option to +.IR groff . +. +. +It can be viewed by explicitly calling +.RB \[lq] gxditview +.IR file \[rq]. +. +If the +.I file +operand is +.RB \[lq] \- \[rq], +.I gxditview +will read the standard input stream; +.I file +cannot be omitted. +. +The intermediate output format of +.I groff +is device-independent but not device-agnostic. +. +.I gxditview +can view it for all typesetter devices, +but the quality is device-dependent. +. +.I gxditview +will not display output for terminal +.RI ( nroff ) +devices. +. +. +.PP +The best results are achieved with the +.BR X * +devices for +.IR groff 's +.B \-T +option, +of which there are four: +.BR \-TX75 , +.BR \-TX75\-12 , +.BR \-TX100 , +and +.BR \-TX100\-12 . +. +They differ by the X\~resolution +(75 or 100 dots per inch) +and the base point size +(10 or 12 points). +. +They are especially built for +.IR gxditview . +. +When using one of these, +.I groff +generates the intermediate output for this device and calls +.I gxditview +automatically for viewing. +. +. +.P +.B \-X +produces good results only with +.BR \-Tps , +.BR \-TX75 , +.BR \-TX75\-12 , +.BR \-TX100 , +and +.BR \-TX100\-12 . +. +The default resolution for previewing +.B \-Tps +output is 75\|dpi; +this can be changed with the +.B \-resolution +option. +. +. +.PP +While +.I gxditview +is running, +the left mouse button brings up a menu with several entries. +. +. +.TP 13n +.B Next Page +Display the next page. +. +. +.TP +.B Previous Page +Display the previous page. +. +. +.TP +.B Select Page +Select a particular numbered page specified by a dialog box. +. +. +.TP +.B Print +Print the +.I groff +intermediate output using a command specified by a dialog box. +. +The default command initially displayed is controlled by the +.B printCommand +application resource, +and by the +.B \-printCommand +option. +. +. +.TP +.B Open +Open for display a new file specified by a dialog box. +. +The file should contain +.I groff +intermediate output. +. +If the filename starts with a bar or pipe symbol, +.RB \[lq] | \[rq] +it will be interpreted as a command from which to read. +. +. +.TP +.B Quit +Exit from +.BR gxditview . +. +. +.PP +The menu entries correspond to actions with similar but not identical +names, +which can also be accessed with keyboard accelerators. +. +The +.IR n , +.IR Space , +.IR Return , +and +.I Next +.RI ( PgDn ) +keys are bound to the +.B NextPage +action. +. +The +.IR p , +.IR b , +.IR BackSpace , +.IR Delete , +and +.I Prior +.RI ( PgUp ) +keys are bound to the +.B PreviousPage +action. +. +The +.I g +key is bound to the +.B SelectPage +action. +. +The +.I o +key is bound to the +.B OpenFile +action. +. +The +.I q +key is bound to the +.B Quit +action. +. +The +.I r +key is bound to a +.B Rerasterize +action which rereads the current file, +and redisplays the current page; +if the current file is a command, +the command will be re-executed. +. +Vertical scrolling can be done with the +.I k +and +.I j +keys; +horizontal scrolling is bound to the +.I h +and +.I l +keys. +. +The arrow keys +.RI ( up , +.IR down , +.IR left , +and +.IR right ) +are also bound to the obvious scrolling actions. +. +. +.PP +The +.B paperlength +and +.B paperwidth +commands in the +.I DESC +file describing a +.I groff +output device specify the length and width in machine units of the +virtual page displayed by +.IR gxditview ; +see +.MR groff_font @MAN5EXT@ . +. +. +.\" ==================================================================== +.SS "X defaults" +.\" ==================================================================== +. +This program uses the +.I Dvi +widget from the X\~Toolkit. +. +It understands all of the core resource names and classes as well as: +. +. +.TP +.BR width\~ (class\~ Width ) +Specifies the width of the window. +. +. +.TP +.BR height\~ (class\~ Height ) +Specifies the height of the window. +. +. +.TP +.BR foreground\~ (class\~ Foreground ) +Specifies the default foreground color. +. +. +.TP +.BR font\~ (class\~ Font ) +Specifies the font to be used for error messages. +. +. +.TP +.BR fontMap\~ (class\~ FontMap ) +Specifies the mapping from +.I groff +font names to X\~font names. +. +This must be a string containing a sequence of lines. +. +Each line contains two whitespace-separated fields: +firstly the +.I groff +font name, +and secondly the XLFD +(X Logical Font Description). +. +The default is shown in subsection \[lq]Default font map\[rq] below. +. +. +.\" ==================================================================== +.SS "Default font map" +.\" ==================================================================== +. +XLFDs are long and unwieldy, +so some lines are shown broken and indented below. +.\" Break them after the POINT_SIZE field (in "decipoints", so "100"). +. +. +.PP +.EX +TR \-adobe\-times\-medium\-r\-normal\-\-*\-100\ +\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +TI \-adobe\-times\-medium\-i\-normal\-\-*\-100\ +\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +TB \-adobe\-times\-bold\-r\-normal\-\-*\-100\ +\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +TBI \-adobe\-times\-bold\-i\-normal\ +\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +CR \-adobe\-courier\-medium\-r\-normal\-\-*\-100\" break + \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +CI \-adobe\-courier\-medium\-o\-normal\" break + \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +CB \-adobe\-courier\-bold\-r\-normal\ +\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +CBI \-adobe\-courier\-bold\-o\-normal\ +\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +HR \-adobe\-helvetica\-medium\-r\-normal\" break + \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +HI \-adobe\-helvetica\-medium\-o\-normal\" break + \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +HB \-adobe\-helvetica\-bold\-r\-normal\" break + \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +HBI \-adobe\-helvetica\-bold\-o\-normal\" break + \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +NR \-adobe\-new century schoolbook\-medium\-r\-normal\-\-*\-100\" break + \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +NI \-adobe\-new century schoolbook\-medium\-i\-normal\-\-*\-100\" break + \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +NB \-adobe\-new century schoolbook\-bold\-r\-normal\-\-*\-100\" break + \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +NBI \-adobe\-new century schoolbook\-bold\-i\-normal\-\-*\-100\" break + \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs] +S \-adobe\-symbol\-medium\-r\-normal\-\-*\-100\" break + \-*\-*\-*\-*\-adobe\-fontspecific\[rs]n\[rs] +SS \-adobe\-symbol\-medium\-r\-normal\-\-*\-100\" break + \-*\-*\-*\-*\-adobe\-fontspecific\[rs]n\[rs] +.EE +. +. +.br +.ne 3v +.\" ==================================================================== +.SH Options +.\" ==================================================================== +. +.B \-help +and +.B \-\-help +display a usage message, +while +.B \-version +and +.B \-\-version +show version information; +all exit afterward. +. +. +.P +.I gxditview +accepts all of the standard X\~Toolkit command-line options along with +the additional options listed below. +. +. +.TP +.B \-page +This option specifies the page number of the document to be displayed. +. +. +.TP +.BI \-backingStore\~ backing-store-type +Because redisplay of the +.I groff +intermediate output window can take a perceiptible amount of time, +this option causes the server to save the window contents so that when +it is scrolled around the viewport, +the window is painted from contents saved in backing store. +. +.I backing-store-type +can be one of +.BR Always , +.B WhenMapped +or +.BR NotUseful . +. +. +.TP +.BI \-printCommand\~ command +The default command displayed in the dialog box for the +.B Print +menu entry will be +.IR command . +. +. +.TP +.BI \-resolution\~ res +The +.I groff +intermediate output file will be displayed at a resolution of +.I res +dots per inch, +unless the +.I DESC +file contains the +.B X11 +command, +in which case the device resolution will be used. +. +This corresponds to the +.I Dvi +widget's +.B resolution +resource. +. +The default is +.BR 75 . +. +. +.TP +.BI \-filename\~ string +The default filename displayed in the dialog box for the +.B Open +menu entry will be +.IR string . +. +This can be either a filename, +or a command starting with +.RB \[lq] | \[rq]. +. +. +.PP +The following standard X\~Toolkit command-line arguments are commonly +used with +.IR gxditview . +. +. +.TP +.BI \-bg\~ color +This option specifies the color to use for the background of the window. +. +The default is +.RB \[lq] white \[rq]. +. +. +.TP +.BI \-bd\~ color +This option specifies the color to use for the border of the window. +. +The default is +.RB \[lq] black \[rq]. +. +. +.TP +.BI \-bw\~ number +This option specifies the width in pixels of the border surrounding the +window. +. +. +.TP +.BI \-fg\~ color +This option specifies the color to use for displaying text. +. +The default is +.RB \[lq] black \[rq]. +. +. +.TP +.BI \-fn\~ font +This option specifies the font to be used for displaying widget text. +. +The default is +.RB \[lq] fixed \[rq]. +. +. +.TP +.B \-rv +This option indicates that reverse video should be simulated by swapping +the foreground and background colors. +. +. +.TP +.BI \-geometry\~ geometry +This option specifies the preferred size and position of the window. +. +. +.TP +.BI \-display\~ host : display +This option specifies the X\~server to contact. +. +. +.TP +.BI \-xrm\~ resourcestring +This option specifies a resource string to be used. +. +. +.\" ==================================================================== +.SH Environment +.\" ==================================================================== +. +.TP +.I GROFF_FONT_PATH +A list of directories in which to seek the selected output device's +directory of device and font description files. +. +See +.MR @g@troff @MAN1EXT@ +and +.MR groff_font @MAN5EXT@ . +. +. +.\" ==================================================================== +.SH Files +.\" ==================================================================== +. +.TP +.I @APPDEFDIR@/\:\%GXditview +.TQ +.I @APPDEFDIR@/\:\%GXditview\-color +define X application defaults for +.IR gxditview . +. +Users can override these values in the +.I .Xdefaults +file, +normally located in the user's home directory. +. +See +.MR appres 1 +and +.MR xrdb 1 . +. +. +.TP +.I @FONTDIR@/\:\%devX100/\:DESC +describes the +.B X100 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devX100/ F +describes the font known +.RI as\~ F +on device +.BR X100 . +. +. +.TP +.I @FONTDIR@/\:\%devX100\-12/\:DESC +describes the +.B X100\-12 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devX100\-12/ F +describes the font known +.RI as\~ F +on device +.BR X100\-12 . +. +. +.TP +.I @FONTDIR@/\:\%devX75/\:DESC +describes the +.B X75 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devX75/ F +describes the font known +.RI as\~ F +on device +.BR X75 . +. +. +.TP +.I @FONTDIR@/\:\%devX75\-12/\:DESC +describes the +.B X75\-12 +output device. +. +. +.TP +.IR @FONTDIR@/\:\%devX75\-12/ F +describes the font known +.RI as\~ F +on device +.BR X75\-12 . +. +. +.TP +.I @MACRODIR@/\:X.tmac +defines macros for use with the +.BR X100 , +.BR X100\-12 , +.BR X75 , +and +.B X75\-12 +output devices. +. +It is automatically loaded by +.I troffrc +when any of those output devices is selected. +. +. +.TP +.I @MACRODIR@/\:Xps\:.tmac +sets up +.I @g@troff +to use +.I \%gxditview +as a previewer for device-independent output targeting the +.B ps +output device. +. +It is automatically loaded by +.I troffrc +when +.I @g@troff +is given the options +.B \-X +and +.BR \-Tps . +. +. +.\" ==================================================================== +.SH Examples +.\" ==================================================================== +. +The following command views this man page with a base point size of 12. +. +. +.RS +.P +.EX +groff \-TX100\-12 \-man gxditview.1 +.EE +.RE +. +. +.P +The quality of the result depends mainly on the chosen point size and +display resolution; +for rapid previewing, +however, +something like +. +.RS +.EX +.RI "groff \-X \-P\-resolution \-P100\~" document +.EE +.RE +. +yields acceptable results. +. +. +.\" ==================================================================== +.SH Authors +.\" ==================================================================== +. +.I gxditview +and its predecessor +.I xditview +were written by +Keith Packard (MIT X Consortium), +Richard L.\& Hyde (Purdue), +David Slattengren (Berkeley), +Malcolm Slaney (Schlumberger Palo Alto Research), +Mark Moraes (University of Toronto), +and +James Clark. +. +. +.PP +This program is derived from +.IR xditview ; +portions of +.I xditview +originated in +.IR xtroff , +which was derived from +.IR \%suntroff . +. +. +.\" ==================================================================== +.SH "See also" +.\" ==================================================================== +. +.UR https://\:www\:.x\:.org/\:releases/\:X11R7.6/\:doc/\:xorg\-docs/\ +\:specs/\:XLFD/xlfd\:.html +\[lq]X Logical Font Description Conventions\[rq] +.UE , +by Jim Flowers and Stephen Gildea. +. +. +.PP +.MR X 7 , +.MR xrdb 1 , +.MR xditview 1 , +.MR groff @MAN1EXT@ , +.MR groff_out @MAN5EXT@ +. +. +.\" Restore compatibility mode (for, e.g., Solaris 10/11). +.cp \n[*groff_gxditview_1_man_C] +.do rr *groff_gxditview_1_man_C +. +. +.\" Local Variables: +.\" fill-column: 72 +.\" mode: nroff +.\" End: +.\" vim: set filetype=groff textwidth=72: diff --git a/src/devices/xditview/lex.c b/src/devices/xditview/lex.c new file mode 100644 index 0000000..19cf292 --- /dev/null +++ b/src/devices/xditview/lex.c @@ -0,0 +1,100 @@ +#include <config.h> + +#include <X11/Xos.h> +#include <X11/IntrinsicP.h> +#include <X11/StringDefs.h> +#include <stdio.h> + +#include "DviP.h" +#include "lex.h" + +int +DviGetAndPut(DviWidget dw, int *cp) +{ + if (dw->dvi.ungot) { + dw->dvi.ungot = 0; + *cp = getc (dw->dvi.file); + } + else { + *cp = getc (dw->dvi.file); + if (*cp != EOF) + putc (*cp, dw->dvi.tmpFile); + } + return *cp; +} + +char * +GetLine(DviWidget dw, char *Buffer, int Length) +{ + int i = 0, c; + + Length--; /* Save room for final '\0' */ + + while (DviGetC (dw, &c) != EOF) { + if (Buffer && i < Length) + Buffer[i++] = c; + if (c == '\n') { + DviUngetC(dw, c); + break; + } + } + if (Buffer) + Buffer[i] = '\0'; + return Buffer; +} + +char * +GetWord(DviWidget dw, char *Buffer, int Length) +{ + int i = 0, c; + + Length--; /* Save room for final '\0' */ + while (DviGetC(dw, &c) == ' ' || c == '\n') + ; + while (c != EOF) { + if (Buffer && i < Length) + Buffer[i++] = c; + if (DviGetC(dw, &c) == ' ' || c == '\n') { + DviUngetC(dw, c); + break; + } + } + if (Buffer) + Buffer[i] = '\0'; + return Buffer; +} + +int +GetNumber(DviWidget dw) +{ + int i = 0, c; + int negative = 0; + + while (DviGetC(dw, &c) == ' ' || c == '\n') + ; + if (c == '-') { + negative = 1; + DviGetC(dw, &c); + } + + for (; c >= '0' && c <= '9'; DviGetC(dw, &c)) { + if (negative) + i = i*10 - (c - '0'); + else + i = i*10 + c - '0'; + } + if (c != EOF) + DviUngetC(dw, c); + return i; +} + +/* +Local Variables: +c-indent-level: 8 +c-continued-statement-offset: 8 +c-brace-offset: -8 +c-argdecl-indent: 8 +c-label-offset: -8 +c-tab-always-indent: nil +End: +*/ diff --git a/src/devices/xditview/lex.h b/src/devices/xditview/lex.h new file mode 100644 index 0000000..0a4cba0 --- /dev/null +++ b/src/devices/xditview/lex.h @@ -0,0 +1 @@ +int GetNumber(DviWidget); diff --git a/src/devices/xditview/page.c b/src/devices/xditview/page.c new file mode 100644 index 0000000..352d871 --- /dev/null +++ b/src/devices/xditview/page.c @@ -0,0 +1,86 @@ +/* + * page.c + * + * map page numbers to file position + */ + +#include <config.h> + +#include <X11/Xos.h> +#include <X11/IntrinsicP.h> +#include <X11/StringDefs.h> +#include <stdio.h> +#include <ctype.h> + +#include "DviP.h" +#include "page.h" + +#ifdef X_NOT_STDC_ENV +extern long ftell(); +#endif + +static DviFileMap * +MapPageNumberToFileMap (DviWidget dw, int number) +{ + DviFileMap *m; + + for (m = dw->dvi.file_map; m; m=m->next) + if (m->page_number == number) + break; + return m; +} + +void +DestroyFileMap (DviFileMap *m) +{ + DviFileMap *next; + + for (; m; m = next) { + next = m->next; + XtFree ((char *) m); + } +} + +void +ForgetPagePositions (DviWidget dw) +{ + DestroyFileMap (dw->dvi.file_map); + dw->dvi.file_map = 0; +} + +void +RememberPagePosition(DviWidget dw, int number) +{ + DviFileMap *m; + + if (!(m = MapPageNumberToFileMap (dw, number))) { + m = (DviFileMap *) XtMalloc (sizeof *m); + m->page_number = number; + m->next = dw->dvi.file_map; + dw->dvi.file_map = m; + } + if (dw->dvi.tmpFile) + m->position = ftell (dw->dvi.tmpFile); + else + m->position = ftell (dw->dvi.file); +} + +long +SearchPagePosition (DviWidget dw, int number) +{ + DviFileMap *m; + + if (!(m = MapPageNumberToFileMap (dw, number))) + return -1; + return m->position; +} + +void +FileSeek(DviWidget dw, long position) +{ + if (dw->dvi.tmpFile) { + dw->dvi.readingTmp = 1; + fseek (dw->dvi.tmpFile, position, 0); + } else + fseek (dw->dvi.file, position, 0); +} diff --git a/src/devices/xditview/page.h b/src/devices/xditview/page.h new file mode 100644 index 0000000..e2c938d --- /dev/null +++ b/src/devices/xditview/page.h @@ -0,0 +1,5 @@ +void DestroyFileMap(DviFileMap *); +void FileSeek(DviWidget, long); +void ForgetPagePositions(DviWidget); +void RememberPagePosition(DviWidget, int); +long SearchPagePosition(DviWidget, int); diff --git a/src/devices/xditview/parse.c b/src/devices/xditview/parse.c new file mode 100644 index 0000000..456c7da --- /dev/null +++ b/src/devices/xditview/parse.c @@ -0,0 +1,343 @@ +/* + * parse.c + * + * parse dvi input + */ + +#include <config.h> + +#include <X11/Xos.h> +#include <X11/IntrinsicP.h> +#include <X11/StringDefs.h> +#include <stdio.h> +#include <ctype.h> + +#include "DviP.h" +#include "draw.h" +#include "font.h" +#include "lex.h" +#include "page.h" +#include "parse.h" + +static int StopSeen = 0; +static void ParseDrawFunction(DviWidget, char *); +static void ParseDeviceControl(DviWidget); +static void push_env(DviWidget); +static void pop_env(DviWidget); + +#define HorizontalMove(dw, delta) ((dw)->dvi.state->x += (delta)) + + +int +ParseInput(register DviWidget dw) +{ + int n, k; + int c; + char Buffer[BUFSIZ]; + int NextPage; + int otherc; + + StopSeen = 0; + + /* + * make sure some state exists + */ + + if (!dw->dvi.state) + push_env (dw); + for (;;) { + switch (DviGetC(dw, &c)) { + case '\n': + break; + case ' ': /* when input is text */ + case 0: /* occasional noise creeps in */ + break; + case '{': /* push down current environment */ + push_env(dw); + break; + case '}': + pop_env(dw); + break; + /* + * two motion digits plus a character + */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + HorizontalMove(dw, (c-'0')*10 + + DviGetC(dw,&otherc)-'0'); + /* fall through */ + case 'c': /* single ascii character */ + DviGetC(dw,&c); + if (c == ' ') + break; + Buffer[0] = c; + Buffer[1] = '\0'; + (void) PutCharacter (dw, Buffer); + break; + case 'C': + GetWord (dw, Buffer, BUFSIZ); + (void) PutCharacter (dw, Buffer); + break; + case 't': + Buffer[1] = '\0'; + while (DviGetC (dw, &c) != EOF + && c != ' ' && c != '\n') { + Buffer[0] = c; + HorizontalMove (dw, PutCharacter (dw, Buffer)); + } + break; + case 'u': + n = GetNumber(dw); + Buffer[1] = '\0'; + while (DviGetC (dw, &c) == ' ') + ; + while (c != EOF && c != ' ' && c != '\n') { + Buffer[0] = c; + HorizontalMove (dw, + PutCharacter (dw, Buffer) + n); + DviGetC (dw, &c); + } + break; + + case 'D': /* draw function */ + (void) GetLine(dw, Buffer, BUFSIZ); + if (dw->dvi.display_enable) + ParseDrawFunction(dw, Buffer); + break; + case 's': /* ignore fractional sizes */ + n = GetNumber(dw); + dw->dvi.state->font_size = n; + break; + case 'f': + n = GetNumber(dw); + dw->dvi.state->font_number = n; + break; + case 'H': /* absolute horizontal motion */ + k = GetNumber(dw); + HorizontalGoto(dw, k); + break; + case 'h': /* relative horizontal motion */ + k = GetNumber(dw); + HorizontalMove(dw, k); + break; + case 'w': /* word space */ + Word (dw); + break; + case 'V': + n = GetNumber(dw); + VerticalGoto(dw, n); + break; + case 'v': + n = GetNumber(dw); + VerticalMove(dw, n); + break; + case 'P': /* new spread */ + break; + case 'p': /* new page */ + (void) GetNumber(dw); + NextPage = dw->dvi.current_page + 1; + RememberPagePosition(dw, NextPage); + FlushCharCache (dw); + return(NextPage); + case 'N': + n = GetNumber(dw); + PutNumberedCharacter (dw, n); + break; + case 'n': /* end of line */ + GetNumber(dw); + GetNumber(dw); + Newline (dw); + HorizontalGoto(dw, 0); + break; + case 'F': /* input files */ + case '+': /* continuation of X device control */ + case 'm': /* color */ + case '#': /* comment */ + GetLine(dw, NULL, 0); + break; + case 'x': /* device control */ + ParseDeviceControl(dw); + break; + case EOF: + dw->dvi.last_page = dw->dvi.current_page; + FlushCharCache (dw); + return dw->dvi.current_page; + default: + break; + } + } +} + +static void +push_env(DviWidget dw) +{ + DviState *new_state; + + new_state = (DviState *) XtMalloc (sizeof (*new_state)); + if (dw->dvi.state) + *new_state = *(dw->dvi.state); + else { + new_state->font_size = 10; + new_state->font_number = 1; + new_state->x = 0; + new_state->y = 0; + } + new_state->next = dw->dvi.state; + dw->dvi.state = new_state; +} + +static void +pop_env(DviWidget dw) +{ + DviState *old; + + old = dw->dvi.state; + dw->dvi.state = old->next; + XtFree ((char *) old); +} + +static void +InitTypesetter (DviWidget dw) +{ + while (dw->dvi.state) + pop_env (dw); + push_env (dw); + FlushCharCache (dw); +} + +#define DRAW_ARGS_MAX 128 + +static void +ParseDrawFunction(DviWidget dw, char *buf) +{ + int v[DRAW_ARGS_MAX]; + int i, no_move = 0; + char *ptr; + + v[0] = v[1] = v[2] = v[3] = 0; + + if (buf[0] == '\0') + return; + ptr = buf+1; + + for (i = 0; i < DRAW_ARGS_MAX; i++) { + if (sscanf(ptr, "%d", v + i) != 1) + break; + while (*ptr == ' ') + ptr++; + while (*ptr != '\0' && *ptr != ' ') + ptr++; + } + + switch (buf[0]) { + case 'l': /* draw a line */ + DrawLine(dw, v[0], v[1]); + break; + case 'c': /* circle */ + DrawCircle(dw, v[0]); + break; + case 'C': + DrawFilledCircle(dw, v[0]); + break; + case 'e': /* ellipse */ + DrawEllipse(dw, v[0], v[1]); + break; + case 'E': + DrawFilledEllipse(dw, v[0], v[1]); + break; + case 'a': /* arc */ + DrawArc(dw, v[0], v[1], v[2], v[3]); + break; + case 'p': + DrawPolygon(dw, v, i); + break; + case 'P': + DrawFilledPolygon(dw, v, i); + break; + case '~': /* wiggly line */ + DrawSpline(dw, v, i); + break; + case 't': + dw->dvi.line_thickness = v[0]; + break; + case 'f': + if (i > 0 && v[0] >= 0 && v[0] <= DVI_FILL_MAX) + dw->dvi.fill = v[0]; + no_move = 1; + break; + default: +#if 0 + warning("unknown drawing function %s", buf); +#endif + no_move = 1; + break; + } + + if (!no_move) { + if (buf[0] == 'e') { + if (i > 0) + dw->dvi.state->x += v[0]; + } + else { + while (--i >= 0) { + if (i & 1) + dw->dvi.state->y += v[i]; + else + dw->dvi.state->x += v[i]; + } + } + } +} + +static void +ParseDeviceControl(DviWidget dw) /* Parse the x commands */ +{ + char str[20], str1[50]; + int c, n; + + GetWord (dw, str, 20); + switch (str[0]) { /* crude for now */ + case 'T': /* output device */ + GetWord (dw, str, 20); + SetDevice (dw, str); + break; + case 'i': /* initialize */ + InitTypesetter (dw); + break; + case 't': /* trailer */ + break; + case 'p': /* pause -- can restart */ + break; + case 's': /* stop */ + StopSeen = 1; + return; + case 'r': /* resolution when prepared */ + break; + case 'f': /* font used */ + n = GetNumber (dw); + GetWord (dw, str, 20); + GetLine (dw, str1, 50); + SetFontPosition (dw, n, str, str1); + break; + case 'H': /* char height */ + break; + case 'S': /* slant */ + break; + } + while (DviGetC (dw, &c) != '\n') /* skip rest of input line */ + if (c == EOF) + return; + return; +} + + +/* +Local Variables: +c-indent-level: 8 +c-continued-statement-offset: 8 +c-brace-offset: -8 +c-argdecl-indent: 8 +c-label-offset: -8 +c-tab-always-indent: nil +End: +*/ diff --git a/src/devices/xditview/parse.h b/src/devices/xditview/parse.h new file mode 100644 index 0000000..636f3a6 --- /dev/null +++ b/src/devices/xditview/parse.h @@ -0,0 +1 @@ +int ParseInput(register DviWidget dw); diff --git a/src/devices/xditview/xdit.bm b/src/devices/xditview/xdit.bm new file mode 100644 index 0000000..0c7aa8c --- /dev/null +++ b/src/devices/xditview/xdit.bm @@ -0,0 +1,14 @@ +#define xdit_width 32 +#define xdit_height 32 +static unsigned char xdit_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x03, 0x02, 0x00, 0x00, 0x02, + 0x8a, 0xa2, 0xfc, 0x03, 0x52, 0x14, 0x03, 0x04, 0x02, 0x80, 0x00, 0x08, + 0x52, 0x54, 0x00, 0x10, 0x8a, 0x22, 0x8f, 0x23, 0x02, 0x20, 0x06, 0x21, + 0x8a, 0x12, 0x8c, 0x40, 0x52, 0x14, 0x8c, 0x40, 0x02, 0x10, 0x58, 0x40, + 0x52, 0x14, 0x30, 0x40, 0x8a, 0x12, 0x30, 0x40, 0x02, 0x10, 0x70, 0x40, + 0x8a, 0x12, 0xc8, 0x40, 0x52, 0x24, 0xc4, 0xe0, 0x02, 0x20, 0x84, 0xe1, + 0x52, 0x54, 0xce, 0xf3, 0x8a, 0xa2, 0x00, 0xf8, 0x02, 0x00, 0x03, 0xfc, + 0x8a, 0x22, 0xfc, 0xf3, 0x52, 0x14, 0x00, 0xc2, 0x02, 0x00, 0x00, 0x02, + 0x52, 0x14, 0x45, 0x02, 0x8a, 0xa2, 0x28, 0x02, 0x02, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x02, 0xfe, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/src/devices/xditview/xdit_mask.bm b/src/devices/xditview/xdit_mask.bm new file mode 100644 index 0000000..a584629 --- /dev/null +++ b/src/devices/xditview/xdit_mask.bm @@ -0,0 +1,14 @@ +#define xdit_mask_width 32 +#define xdit_mask_height 32 +static unsigned char xdit_mask_bits[] = { + 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, + 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x1f, + 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc7, + 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, + 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/src/devices/xditview/xditview.am b/src/devices/xditview/xditview.am new file mode 100644 index 0000000..7bb32ba --- /dev/null +++ b/src/devices/xditview/xditview.am @@ -0,0 +1,130 @@ +# Copyright (C) 2014-2020 Free Software Foundation, Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +GXDITVIEWSOURCES = \ + src/devices/xditview/device.c \ + src/devices/xditview/draw.c \ + src/devices/xditview/draw.h \ + src/devices/xditview/Dvi.c \ + src/devices/xditview/font.c \ + src/devices/xditview/font.h \ + src/devices/xditview/lex.c \ + src/devices/xditview/lex.h \ + src/devices/xditview/page.c \ + src/devices/xditview/page.h \ + src/devices/xditview/parse.c \ + src/devices/xditview/parse.h \ + src/devices/xditview/xditview.c \ + src/devices/xditview/device.h \ + src/devices/xditview/DviP.h \ + src/devices/xditview/Menu.h \ + src/devices/xditview/Dvi.h + +GXDITVIEW_GROFF_VERSION_H=src/devices/xditview/groff_version.h + +$(GXDITVIEW_GROFF_VERSION_H): $(top_srcdir)/.version + $(AM_V_at)$(MKDIR_P) `dirname $@` + $(AM_V_GEN)printf \ + 'const char *Version_string = "%s";\n' '@VERSION@' > $@ + +if WITHOUT_X11 +GXDITVIEW_MAN1 = +EXTRA_DIST += $(GXDITVIEWSOURCES) +else +GXDITVIEW_MAN1 = src/devices/xditview/gxditview.1 +xditview_srcdir = $(top_srcdir)/src/devices/xditview +bin_PROGRAMS += gxditview +gxditview_CPPFLAGS = $(AM_CPPFLAGS) $(X_CFLAGS) -Dlint \ + -I$(top_builddir)/src/devices/xditview +gxditview_LDADD = $(X_LIBS) $(X_PRE_LIBS) -lXaw -lXmu -lXt -lX11 \ + $(X_EXTRA_LIBS) $(LIBM) libxutil.a lib/libgnu.a +XDITVIEW_GENHDRS = \ + src/devices/xditview/GXditview-ad.h \ + $(GXDITVIEW_GROFF_VERSION_H) +gxditview_SOURCES = $(GXDITVIEWSOURCES) +nodist_gxditview_SOURCES = $(XDITVIEW_GENHDRS) +CLEANFILES += $(XDITVIEW_GENHDRS) + +man1_MANS += $(GXDITVIEW_MAN1) + +# Because we defined gxditview_CPPFLAGS, automake renames all of +# xditview's objects, adding an "gxditview-" prefix. +src/devices/xditview/gxditview-device.$(OBJEXT): defs.h +src/devices/xditview/gxditview-xditview.$(OBJEXT): $(XDITVIEW_GENHDRS) + +src/devices/xditview/GXditview-ad.h: $(xditview_srcdir)/GXditview.ad + $(AM_V_GEN)$(SHELL) $(xditview_srcdir)/ad2c \ + $(xditview_srcdir)/GXditview.ad > $@ +endif + +EXTRA_DIST += \ + src/devices/xditview/ad2c \ + src/devices/xditview/ChangeLog \ + src/devices/xditview/DESC.in \ + src/devices/xditview/FontMap-X11 \ + src/devices/xditview/GXditview-color.ad \ + src/devices/xditview/GXditview.ad \ + src/devices/xditview/README \ + src/devices/xditview/TODO \ + src/devices/xditview/gray1.bm \ + src/devices/xditview/gray2.bm \ + src/devices/xditview/gray3.bm \ + src/devices/xditview/gray4.bm \ + src/devices/xditview/gray5.bm \ + src/devices/xditview/gray6.bm \ + src/devices/xditview/gray7.bm \ + src/devices/xditview/gray8.bm \ + src/devices/xditview/xdit.bm \ + src/devices/xditview/xdit_mask.bm \ + src/devices/xditview/gxditview.1.man + +# Custom installation of GXditview.ad and GXditview-color.ad +install-data-local: install_xditview +uninstall-local: uninstall_xditview + +if WITHOUT_X11 +install_xditview: +uninstall_xditview: +else +install_xditview: \ + $(xditview_srcdir)/FontMap-X11 \ + $(xditview_srcdir)/GXditview.ad \ + $(xditview_srcdir)/GXditview-color.ad + $(MKDIR_P) $(DESTDIR)$(fontdir) + $(INSTALL_DATA) $(xditview_srcdir)/FontMap-X11 \ + $(DESTDIR)$(fontdir)/FontMap-X11 + -test -d $(DESTDIR)$(appdefdir) \ + || $(mkinstalldirs) $(DESTDIR)$(appdefdir) + $(INSTALL_DATA) $(xditview_srcdir)/GXditview.ad \ + $(DESTDIR)$(appdefdir)/GXditview + $(INSTALL_DATA) $(xditview_srcdir)/GXditview-color.ad \ + $(DESTDIR)$(appdefdir)/GXditview-color + +uninstall_xditview: + rm -f $(DESTDIR)$(appdefdir)/GXditview + rm -f $(DESTDIR)$(appdefdir)/GXditview-color + rm -f $(DESTDIR)$(fontdir)/FontMap-X11 + -rmdir $(DESTDIR)$(fontdir) 2>/dev/null + +endif + + +# Local Variables: +# fill-column: 72 +# mode: makefile-automake +# End: +# vim: set autoindent filetype=automake textwidth=72: diff --git a/src/devices/xditview/xditview.c b/src/devices/xditview/xditview.c new file mode 100644 index 0000000..1f56940 --- /dev/null +++ b/src/devices/xditview/xditview.c @@ -0,0 +1,684 @@ +/* + * Copyright 1991 Massachusetts Institute of Technology + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of M.I.T. not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. M.I.T. makes no representations about the + * suitability of this software for any purpose. It is provided "as is" + * without express or implied warranty. + * + * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T. + * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +/* + * xditview -- + * + * Display ditroff output in an X window + */ + +#ifndef SABER +#ifndef lint +static char rcsid[] = "$XConsortium: xditview.c,v 1.17 89/12/10 17:05:08 rws Exp $"; +#endif /* lint */ +#endif /* SABER */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xos.h> +#include <X11/Intrinsic.h> +#include <X11/StringDefs.h> +#include <X11/Shell.h> +#include <X11/Xaw/Paned.h> +#include <X11/Xaw/Viewport.h> +#include <X11/Xaw/Box.h> +#include <X11/Xaw/Command.h> +#include <X11/Xaw/Dialog.h> +#include <X11/Xaw/Label.h> +#include <X11/Xaw/SimpleMenu.h> +#include <X11/Xaw/SmeBSB.h> + +#include <stdbool.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> + +#include "Dvi.h" +#include "groff_version.h" + +#include "xdit.bm" +#include "xdit_mask.bm" + +#ifdef NEED_DECLARATION_POPEN +FILE *popen(const char *, const char *); +#endif /* NEED_DECLARATION_POPEN */ + +#ifdef NEED_DECLARATION_PCLOSE +int pclose (FILE *); +#endif /* NEED_DECLARATION_PCLOSE */ + +typedef void (*MakePromptFunc)(const char *); + +static String fallback_resources[] = { +#include "GXditview-ad.h" + NULL +}; + +static struct app_resources { + char *print_command; + char *filename; +} app_resources; + +#define offset(field) XtOffset(struct app_resources *, field) + +/* Application resources. */ + +static XtResource resources[] = { + {(String)"printCommand", (String)"PrintCommand", (String)XtRString, + sizeof(char*), offset(print_command), (String)XtRString, NULL}, + {(String)"filename", (String)"Filename", (String)XtRString, + sizeof(char*), offset(filename), (String)XtRString, NULL}, +}; + +#undef offset + +/* Command line options table. Only resources are entered here...there is a + pass over the remaining options after XrmParseCommand is let loose. */ + +static XrmOptionDescRec options[] = { +{(char *)"-page", (char *)"*dvi.pageNumber", + XrmoptionSepArg, NULL}, +{(char *)"-backingStore", (char *)"*dvi.backingStore", + XrmoptionSepArg, NULL}, +{(char *)"-resolution", (char *)"*dvi.resolution", + XrmoptionSepArg, NULL}, +{(char *)"-printCommand", (char *)".printCommand", + XrmoptionSepArg, NULL}, +{(char *)"-filename", (char *)".filename", + XrmoptionSepArg, NULL}, +{(char *)"-noPolyText", (char *)"*dvi.noPolyText", + XrmoptionNoArg, (XPointer)"TRUE"}, +}; + +static char current_print_command[1024]; + +static char current_file_name[1024]; +static FILE *current_file; + +/* + * Report the syntax for calling xditview. + */ + +static void +Syntax(const char *progname, bool had_error) +{ + FILE *stream = stdout; + if (had_error) + stream = stderr; + (void) fprintf (stream, "usage: %s [X-toolkit-option]" + " [-backingStore backing-store-type]" + " [-filename file]" +// See draw.c:FlushCharCache(). +#if 0 + " [-noPolyText]" +#endif + " [-page page-number]" + " [-printCommand command]" + " [-resolution resolution]" + " [file]\n", progname); + (void) fprintf (stream, "usage: %s {-version | --version}\n", + progname); + (void) fprintf (stream, "usage: %s {-help | --help}\n", + progname); + if (had_error) + exit(EXIT_FAILURE); + else + exit(EXIT_SUCCESS); +} + +static void NewFile (const char *); +static void SetPageNumber (int); +static Widget toplevel, paned, viewport, dvi; +static Widget page; +static Widget simpleMenu; + +static void NextPage(Widget, XtPointer, XtPointer); +static void PreviousPage(Widget, XtPointer, XtPointer); +static void SelectPage(Widget, XtPointer, XtPointer); +static void OpenFile(Widget, XtPointer, XtPointer); +static void Quit(Widget, XtPointer, XtPointer); +static void Print(Widget, XtPointer, XtPointer); + +static struct menuEntry { + const char *name; + XtCallbackProc function; +} menuEntries[] = { + {"nextPage", NextPage}, + {"previousPage", PreviousPage}, + {"selectPage", SelectPage}, + {"print", Print}, + {"openFile", OpenFile}, + {"quit", Quit}, +}; + +static void NextPageAction(Widget, XEvent *, String *, Cardinal *); +static void PreviousPageAction(Widget, XEvent *, String *, Cardinal *); +static void SelectPageAction(Widget, XEvent *, String *, Cardinal *); +static void OpenFileAction(Widget, XEvent *, String *, Cardinal *); +static void QuitAction(Widget, XEvent *, String *, Cardinal *); +static void AcceptAction(Widget, XEvent *, String *, Cardinal *); +static void CancelAction(Widget, XEvent *, String *, Cardinal *); +static void PrintAction(Widget, XEvent *, String *, Cardinal *); +static void RerasterizeAction(Widget, XEvent *, String *, Cardinal *); + +static void MakePrompt(Widget, const char *, MakePromptFunc, const char *); + +XtActionsRec xditview_actions[] = { + {(String)"NextPage", NextPageAction}, + {(String)"PreviousPage", PreviousPageAction}, + {(String)"SelectPage", SelectPageAction}, + {(String)"Print", PrintAction}, + {(String)"OpenFile", OpenFileAction}, + {(String)"Rerasterize", RerasterizeAction}, + {(String)"Quit", QuitAction}, + {(String)"Accept", AcceptAction}, + {(String)"Cancel", CancelAction}, +}; + +#define MenuNextPage 0 +#define MenuPreviousPage 1 +#define MenuSelectPage 2 +#define MenuPrint 3 +#define MenuOpenFile 4 +#define MenuQuit 5 + +static char pageLabel[256] = "Page <none>"; + +int main(int argc, char **argv) +{ + char *file_name = 0; + Cardinal i; + static Arg labelArgs[] = { + {XtNlabel, (XtArgVal) pageLabel}, + }; + XtAppContext xtcontext; + Arg topLevelArgs[2]; + Widget entry; + Arg pageNumberArgs[1]; + int page_number; + + toplevel = XtAppInitialize(&xtcontext, "GXditview", + options, XtNumber (options), + &argc, argv, fallback_resources, NULL, 0); + if (argc > 2) + Syntax(argv[0], true /* had error */); + else if (argc == 2) { + if ((strcmp(argv[1], "-help") == 0) + || (strcmp(argv[1], "--help") == 0)) + Syntax(argv[0], false /* did not have error */); + else if ((strcmp(argv[1], "-version") == 0) + || (strcmp(argv[1], "--version") == 0)) { + (void) printf("GNU gxditview (groff) version %s\n", + Version_string); + exit(EXIT_SUCCESS); + } + } + + XtGetApplicationResources(toplevel, (XtPointer)&app_resources, + resources, XtNumber(resources), + NULL, (Cardinal) 0); + if (app_resources.print_command) + strcpy(current_print_command, app_resources.print_command); + + XtAppAddActions(xtcontext, xditview_actions, XtNumber (xditview_actions)); + + XtSetArg (topLevelArgs[0], XtNiconPixmap, + XCreateBitmapFromData (XtDisplay (toplevel), + XtScreen(toplevel)->root, + (char *)xdit_bits, + xdit_width, xdit_height)); + + XtSetArg (topLevelArgs[1], XtNiconMask, + XCreateBitmapFromData (XtDisplay (toplevel), + XtScreen(toplevel)->root, + (char *)xdit_mask_bits, + xdit_mask_width, xdit_mask_height)); + XtSetValues (toplevel, topLevelArgs, 2); + if (argc > 1) + file_name = argv[1]; + + /* + * create the menu and insert the entries + */ + simpleMenu = XtCreatePopupShell ("menu", simpleMenuWidgetClass, toplevel, + NULL, 0); + for (i = 0; i < XtNumber (menuEntries); i++) { + entry = XtCreateManagedWidget(menuEntries[i].name, + smeBSBObjectClass, simpleMenu, + NULL, (Cardinal) 0); + XtAddCallback(entry, XtNcallback, menuEntries[i].function, NULL); + } + + paned = XtCreateManagedWidget("paned", panedWidgetClass, toplevel, + NULL, (Cardinal) 0); + viewport = XtCreateManagedWidget("viewport", viewportWidgetClass, paned, + NULL, (Cardinal) 0); + dvi = XtCreateManagedWidget ("dvi", dviWidgetClass, viewport, NULL, 0); + page = XtCreateManagedWidget ("label", labelWidgetClass, paned, + labelArgs, XtNumber (labelArgs)); + XtSetArg (pageNumberArgs[0], XtNpageNumber, &page_number); + XtGetValues (dvi, pageNumberArgs, 1); + if (file_name) + NewFile (file_name); + /* NewFile modifies current_file_name, so do this here. */ + if (app_resources.filename) + strcpy(current_file_name, app_resources.filename); + XtRealizeWidget (toplevel); + if (file_name) + SetPageNumber (page_number); + XtInstallAllAccelerators(paned,paned); + XtAppMainLoop(xtcontext); + return 0; +} + +static void +SetPageNumber (int number) +{ + Arg arg[2]; + int actual_number, last_page; + + XtSetArg (arg[0], XtNpageNumber, number); + XtSetValues (dvi, arg, 1); + XtSetArg (arg[0], XtNpageNumber, &actual_number); + XtSetArg (arg[1], XtNlastPageNumber, &last_page); + XtGetValues (dvi, arg, 2); + if (actual_number == 0) + sprintf (pageLabel, "Page <none>"); + else if (last_page > 0) + sprintf (pageLabel, "Page %d of %d", actual_number, last_page); + else + sprintf (pageLabel, "Page %d", actual_number); + XtSetArg (arg[0], XtNlabel, pageLabel); + XtSetValues (page, arg, 1); +} + +static void +SelectPageNumber (const char *number_string) +{ + SetPageNumber (atoi(number_string)); +} + +static int hadFile = 0; + +static void +NewFile (const char *name) +{ + Arg arg[2]; + char *n; + FILE *new_file; + Boolean seek = 0; + + if (current_file) { + if (!strcmp (current_file_name, "-")) + ; + else if (current_file_name[0] == '|') + pclose (current_file); + else + fclose (current_file); + } + if (!strcmp (name, "-")) + new_file = stdin; + else if (name[0] == '|') + new_file = popen (name+1, "r"); + else { + new_file = fopen (name, "r"); + seek = 1; + } + if (!new_file) { + /* XXX display error message */ + return; + } + XtSetArg (arg[0], XtNfile, new_file); + XtSetArg (arg[1], XtNseek, seek); + XtSetValues (dvi, arg, 2); + if (hadFile || name[0] != '-' || name[1] != '\0') { + XtSetArg (arg[0], XtNtitle, name); + if (name[0] != '/' && (n = strrchr (name, '/'))) + n = n + 1; + else + n = (char *)name; + XtSetArg (arg[1], XtNiconName, n); + XtSetValues (toplevel, arg, 2); + } + hadFile = 1; + SelectPageNumber ("1"); + strcpy (current_file_name, name); + current_file = new_file; +} + +static char fileBuf[1024]; + +static void +ResetMenuEntry (Widget entry) +{ + Arg arg[1]; + + XtSetArg (arg[0], (String)XtNpopupOnEntry, entry); + XtSetValues (XtParent(entry) , arg, (Cardinal) 1); +} + +/*ARGSUSED*/ + +static void +NextPage (Widget entry, XtPointer name, XtPointer data) +{ + name = name; /* unused; suppress compiler warning */ + data = data; + + NextPageAction((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0); + ResetMenuEntry (entry); +} + +static void +NextPageAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + Arg args[1]; + int number; + + XtSetArg (args[0], XtNpageNumber, &number); + XtGetValues (dvi, args, 1); + SetPageNumber (number+1); + + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; +} + +/*ARGSUSED*/ + +static void +PreviousPage (Widget entry, XtPointer name, XtPointer data) +{ + name = name; /* unused; suppress compiler warning */ + data = data; + + PreviousPageAction ((Widget)NULL, (XEvent *) 0, (String *) 0, + (Cardinal *) 0); + ResetMenuEntry (entry); +} + +static void +PreviousPageAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + Arg args[1]; + int number; + + XtSetArg (args[0], XtNpageNumber, &number); + XtGetValues (dvi, args, 1); + SetPageNumber (number-1); + + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; +} + +/* ARGSUSED */ + +static void +SelectPage (Widget entry, XtPointer name, XtPointer data) +{ + name = name; /* unused; suppress compiler warning */ + data = data; + + SelectPageAction ((Widget)NULL, (XEvent *) 0, (String *) 0, + (Cardinal *) 0); + ResetMenuEntry (entry); +} + +static void +SelectPageAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; + + MakePrompt (toplevel, "Page number", SelectPageNumber, ""); +} + + +static void +DoPrint (const char *name) +{ + FILE *print_file; + RETSIGTYPE (*handler)(int); + + /* Avoid dying because of an invalid command. */ + handler = signal(SIGPIPE, SIG_IGN); + + print_file = popen(name, "w"); + if (!print_file) + /* XXX print error message */ + return; + DviSaveToFile(dvi, print_file); + pclose(print_file); + signal(SIGPIPE, handler); + strcpy(current_print_command, name); +} + +static void +RerasterizeAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + Arg args[1]; + int number; + + if (current_file_name[0] == 0) { + /* XXX display an error message */ + return; + } + XtSetArg (args[0], XtNpageNumber, &number); + XtGetValues (dvi, args, 1); + NewFile(current_file_name); + SetPageNumber (number); + + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; +} + +/* ARGSUSED */ + +static void +Print (Widget entry, XtPointer name, XtPointer data) +{ + name = name; /* unused; suppress compiler warning */ + data = data; + + PrintAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0); + ResetMenuEntry (entry); +} + +static void +PrintAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; + + if (current_print_command[0]) + strcpy (fileBuf, current_print_command); + else + fileBuf[0] = '\0'; + MakePrompt (toplevel, "Print command:", DoPrint, fileBuf); +} + + +/* ARGSUSED */ + +static void +OpenFile (Widget entry, XtPointer name, XtPointer data) +{ + name = name; /* unused; suppress compiler warning */ + data = data; + + OpenFileAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0); + ResetMenuEntry (entry); +} + +static void +OpenFileAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; + + if (current_file_name[0]) + strcpy (fileBuf, current_file_name); + else + fileBuf[0] = '\0'; + MakePrompt (toplevel, "File to open:", NewFile, fileBuf); +} + +/* ARGSUSED */ + +static void +Quit (Widget entry, XtPointer closure, XtPointer data) +{ + entry = entry; /* unused; suppress compiler warning */ + closure = closure; + data = data; + + QuitAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0); +} + +static void +QuitAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; + + exit (0); +} + +Widget promptShell, promptDialog; +MakePromptFunc promptfunction; + +/* ARGSUSED */ +static +void CancelAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + widget = widget; /* unused; suppress compiler warning */ + event = event; + params = params; + num_params = num_params; + + if (promptShell) { + XtSetKeyboardFocus(toplevel, (Widget) None); + XtDestroyWidget(promptShell); + promptShell = (Widget) 0; + } +} + +static +void AcceptAction (Widget widget, XEvent *event, + String *params, Cardinal *num_params) +{ + (*promptfunction)(XawDialogGetValueString(promptDialog)); + CancelAction (widget, event, params, num_params); +} + +static void +MakePrompt(Widget centerw, const char *prompt, + MakePromptFunc func, const char *def) +{ + static Arg dialogArgs[] = { + {XtNlabel, 0}, + {XtNvalue, 0}, + }; + Arg valueArgs[1]; + Arg centerArgs[2]; + Position source_x, source_y; + Position dest_x, dest_y; + Dimension center_width, center_height; + Dimension prompt_width, prompt_height; + Widget valueWidget; + + CancelAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0); + promptShell = XtCreatePopupShell ("promptShell", transientShellWidgetClass, + toplevel, NULL, (Cardinal) 0); + dialogArgs[0].value = (XtArgVal)prompt; + dialogArgs[1].value = (XtArgVal)def; + promptDialog = XtCreateManagedWidget( "promptDialog", dialogWidgetClass, + promptShell, dialogArgs, XtNumber (dialogArgs)); + XawDialogAddButton(promptDialog, "accept", NULL, (XtPointer) 0); + XawDialogAddButton(promptDialog, "cancel", NULL, (XtPointer) 0); + valueWidget = XtNameToWidget (promptDialog, "value"); + if (valueWidget) { + XtSetArg (valueArgs[0], (String)XtNresizable, TRUE); + XtSetValues (valueWidget, valueArgs, 1); + /* + * as resizable isn't set until just above, the + * default value will be displayed incorrectly. + * rectify the situation by resetting the values + */ + XtSetValues (promptDialog, dialogArgs, XtNumber (dialogArgs)); + } + XtSetKeyboardFocus (promptDialog, valueWidget); + XtSetKeyboardFocus (toplevel, valueWidget); + XtRealizeWidget (promptShell); + /* + * place the widget in the center of the "parent" + */ + XtSetArg (centerArgs[0], XtNwidth, ¢er_width); + XtSetArg (centerArgs[1], XtNheight, ¢er_height); + XtGetValues (centerw, centerArgs, 2); + XtSetArg (centerArgs[0], XtNwidth, &prompt_width); + XtSetArg (centerArgs[1], XtNheight, &prompt_height); + XtGetValues (promptShell, centerArgs, 2); + source_x = (center_width - prompt_width) / 2; + source_y = (center_height - prompt_height) / 3; + XtTranslateCoords (centerw, source_x, source_y, &dest_x, &dest_y); + XtSetArg (centerArgs[0], XtNx, dest_x); + XtSetArg (centerArgs[1], XtNy, dest_y); + XtSetValues (promptShell, centerArgs, 2); + XtMapWidget(promptShell); + promptfunction = func; +} + +/* +Local Variables: +c-indent-level: 4 +c-continued-statement-offset: 4 +c-brace-offset: -4 +c-argdecl-indent: 4 +c-label-offset: -4 +c-tab-always-indent: nil +End: +*/ |