diff options
Diffstat (limited to 'contrib/groff/src/devices')
24 files changed, 12387 insertions, 207 deletions
diff --git a/contrib/groff/src/devices/grodvi/Makefile.sub b/contrib/groff/src/devices/grodvi/Makefile.sub index 74e627d..16dc24c 100644 --- a/contrib/groff/src/devices/grodvi/Makefile.sub +++ b/contrib/groff/src/devices/grodvi/Makefile.sub @@ -3,4 +3,4 @@ MAN1=grodvi.n XLIBS=$(LIBDRIVER) $(LIBGROFF) MLIB=$(LIBM) OBJS=dvi.$(OBJEXT) -CCSRCS=$(srcdir)/dvi.cc +CCSRCS=$(srcdir)/dvi.cpp diff --git a/contrib/groff/src/devices/grodvi/dvi.cpp b/contrib/groff/src/devices/grodvi/dvi.cpp new file mode 100644 index 0000000..df16939 --- /dev/null +++ b/contrib/groff/src/devices/grodvi/dvi.cpp @@ -0,0 +1,973 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003 + 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#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(int, font *, const environment *, int w, const char *name); + void special(char *arg, const environment *env, char type); + void end_of_line(); + void draw(int code, int *p, int np, const environment *env); +}; + + +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) / color::MAX_COLOR_VAL, + double(Green) / color::MAX_COLOR_VAL, + double(Blue) / 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) / color::MAX_COLOR_VAL, + double(Magenta) / color::MAX_COLOR_VAL, + double(Yellow) / color::MAX_COLOR_VAL, + double(Black) / color::MAX_COLOR_VAL); + break; + case GRAY: + sprintf(buf, "color gray %.3g", + double(Gray) / color::MAX_COLOR_VAL); + break; + } + do_special(buf); +} + +void dvi_printer::set_char(int index, font *f, const environment *env, + int w, const char *) +{ + if (*env->col != cur_color) + set_color(env->col); + int code = f->get_code(index); + 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(index, 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)/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); + 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])); + sprintf(buf, "ar 0 0 %d %d %f %f", + rad, + rad, + atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0]), + atan2(-c[1], -c[0])); + 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: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 'p': + if (!font::scan_papersize(optarg, 0, + &user_paper_length, &user_paper_width)) + error("invalid custom paper size `%1' ignored", 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("bad line width"); + 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 [-dv] [-F dir] [-w n] [files ...]\n", + program_name); +} diff --git a/contrib/groff/src/devices/grodvi/grodvi.man b/contrib/groff/src/devices/grodvi/grodvi.man index f732a42..793bfe2 100644 --- a/contrib/groff/src/devices/grodvi/grodvi.man +++ b/contrib/groff/src/devices/grodvi/grodvi.man @@ -1,5 +1,5 @@ .ig -Copyright (C) 1989-2000, 2001, 2002 Free Software Foundation, Inc. +Copyright (C) 1989-2000, 2001, 2002, 2003 Free Software Foundation, Inc. Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice @@ -20,11 +20,22 @@ the original English. . .ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X .el .ds tx TeX +. .\" Like TP, but if specified indent is more than half .\" the current line-length - indent, use the default indent. .de Tp -.ie \\n(.$=0:((0\\$1)*2u>(\\n(.lu-\\n(.iu)) .TP -.el .TP "\\$1" +. ie \\n(.$=0:((0\\$1)*2u>(\\n(.lu-\\n(.iu)) .TP +. el .TP "\\$1" +.. +. +.de TQ +. br +. ns +. TP \$1 +.. +. +.de FT +. if '\\*(.T'dvi' .ft \\$1 .. . . @@ -36,12 +47,14 @@ grodvi \- convert groff output to TeX dvi format .SH SYNOPSIS .B grodvi [ -.B \-dv -] [ -.BI \-w n +.B \-dlv ] [ .BI \-F dir ] [ +.BI \-p papersize +] [ +.BI \-w n +] [ .IR files \|.\|.\|.\& ] .PP @@ -60,12 +73,8 @@ Normally it should be run by . This will run .BR @g@troff\ \-Tdvi ; -it will also input the macros -.BR @MACRODIR@/dvi.tmac ; -if the input is being preprocessed with -.B @g@eqn -it will also input -.BR @FONTDIR@/devdvi/eqnchar . +it will also input the macros in +.BR @MACRODIR@/dvi.tmac . . .LP The dvi file generated by @@ -116,8 +125,8 @@ automatically, providing the macro. . Please check -.B grops (@MAN1EXT@) -for a detailed description of this macro. +.BR groff_tmac (@MAN5EXT@) +for a detailed description. . .LP Font files for @@ -169,6 +178,21 @@ escape sequence can be used to access characters by their position in the corresponding tfm file; all characters in the tfm file can be accessed this way. . +.LP +By design, the DVI format doesn't care about physical dimensions of the +output medium. +. +Instead, +.B grodvi +emits the equivalent to \*(tx's +.BI \[rs]special{papersize= width , length } +on the first page; +.B dvips +(and possibly other DVI drivers) then sets the page size accordingly. +. +If either the page width or length is not positive, no papersize special +is output. +. . .SH OPTIONS .TP @@ -180,6 +204,36 @@ Horizontal and vertical lines will be implemented by rules. Other drawing commands will be ignored. . .TP +.BI \-F dir +Prepend directory +.IB 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 +Specify landscape orientation. +. +.TP +.BI \-p papersize +Specify paper dimensions. +. +This overrides the +.BR papersize , +.BR paperlength , +and +.B paperwidth +commands in the +.B DESC +file; it accepts the same arguments as the +.B papersize +command (see +.BR groff_font (@MAN5EXT@) +for details). +. +.TP .B \-v Print the version number. . @@ -190,15 +244,6 @@ Set the default line thickness to thousandths of an em. If this option isn't specified, the line thickness defaults to 0.04\~em. . -.TP -.BI \-F dir -Prepend directory -.IB dir /dev name -to the search path for font and device description files; -.I name -is the name of the device, usually -.BR dvi . -. . .SH USAGE There are styles called @@ -207,17 +252,13 @@ There are styles called .BR B , and .B BI -mounted at font positions 1 to 4. +mounted at font positions 1 to\ 4. The fonts are grouped into families .B T and -.B H +.B H\ \c having members in each of these styles: . -.de FT -.if '\\*(.T'dvi' .ft \\$1 -.. -. .RS .TP .B TR @@ -225,43 +266,43 @@ having members in each of these styles: CM Roman (cmr10) .FT . -.TP +.TQ .B TI .FT TI CM Text Italic (cmti10) .FT . -.TP +.TQ .B TB .FT TB CM Bold Extended Roman (cmbx10) .FT . -.TP +.TQ .B TBI .FT TBI CM Bold Extended Text Italic (cmbxti10) .FT . -.TP +.TQ .B HR .FT HR CM Sans Serif (cmss10) .FT . -.TP +.TQ .B HI .FT HI CM Slanted Sans Serif (cmssi10) .FT . -.TP +.TQ .B HB .FT HB CM Sans Serif Bold Extended (cmssbx10) .FT . -.TP +.TQ .B HBI .FT HBI CM Slanted Sans Serif Bold Extended (cmssbxo10) @@ -278,7 +319,7 @@ CM Typewriter Text (cmtt10) .FT CW .FT . -.TP +.TQ .B CWI CM Italic Typewriter Text (cmitt10) .FT CWI @@ -293,6 +334,9 @@ Special fonts are (cmsy10), .B EX (cmex10), +.B SC +(cmtex10, only for +.BR CW ), and, perhaps surprisingly, .BR TR , .BR TI , @@ -318,9 +362,9 @@ These two fonts are not mounted by default. .LP Using the option .B \-mec -(loading the file +(which loads the file .BR ec.tmac ) -EC and TC fonts are used. +provides the EC and TC fonts. . The design of the EC family is very similar to that of the CM fonts; additionally, they give a much better coverage of groff symbols. @@ -378,7 +422,8 @@ and widths of rules be rounded. .BR @g@troff (@MAN1EXT@), .BR groff_out (@MAN5EXT@), .BR groff_font (@MAN5EXT@), -.BR groff_char (@MAN7EXT@) +.BR groff_char (@MAN7EXT@), +.BR groff_tmac (@MAN5EXT@) . .\" Local Variables: .\" mode: nroff diff --git a/contrib/groff/src/devices/grohtml/Makefile.sub b/contrib/groff/src/devices/grohtml/Makefile.sub index 33e41f8..ad673cf 100644 --- a/contrib/groff/src/devices/grohtml/Makefile.sub +++ b/contrib/groff/src/devices/grohtml/Makefile.sub @@ -8,10 +8,10 @@ OBJS=\ html-text.$(OBJEXT) \ output.$(OBJEXT) CCSRCS=\ - $(srcdir)/post-html.cc \ - $(srcdir)/html-table.cc \ - $(srcdir)/html-text.cc \ - $(srcdir)/output.cc + $(srcdir)/post-html.cpp \ + $(srcdir)/html-table.cpp \ + $(srcdir)/html-text.cpp \ + $(srcdir)/output.cpp HDRS=\ $(srcdir)/html.h \ $(srcdir)/html-table.h \ diff --git a/contrib/groff/src/devices/grohtml/html-table.cpp b/contrib/groff/src/devices/grohtml/html-table.cpp new file mode 100644 index 0000000..3585cd3 --- /dev/null +++ b/contrib/groff/src/devices/grohtml/html-table.cpp @@ -0,0 +1,728 @@ +// -*- C++ -*- +/* Copyright (C) 2002, 2003 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" +#include "html-table.h" +#include "ctype.h" +#include "html.h" + +#if !defined(TRUE) +# define TRUE (1==1) +#endif +#if !defined(FALSE) +# define FALSE (1==0) +#endif + +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; + free(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 = (tab_position *)malloc(sizeof(tab_position)); + last = tab; + } else { + last->next = (tab_position *)malloc(sizeof(tab_position)); + last = last->next; + } + last->alignment = align; + last->position = total; + last->next = NULL; + } +} + +/* + * 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) + : columns(NULL), out(op), 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; + free(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; + free(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) + 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) { + int n = no_columns() + no_gaps(); + + out->nl(); + out->nl(); + if (space) + out->put_string("<p>"); + start_space = space; + out->put_string("<table width=\"100%\" border=0 rules=\"none\" frame=\"void\"\n cols=\"").put_number(n).put_string("\" cellspacing=\"0\" cellpadding=\"0\">").nl(); + out->put_string("<tr valign=\"top\" align=\"left\">").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; +} + +/* + * 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) { + if (is_gap(b)) + out->put_string("<td width=\"") + .put_number(is_gap(b)) + .put_string("%\"></td>") + .nl(); + 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(); + if (width) + out->put_string("<td width=\"") + .put_number(width) + .put_string("%\"></td>") + .nl(); + // have we a gap? + if (is_gap(b)) + out->put_string("<td width=\"") + .put_number(is_gap(b)) + .put_string("%\"></td>") + .nl(); + 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': + out->put_string("<td width=\"") + .put_number(width) + .put_string("%\" align=center>") + .nl(); + break; + case 'R': + out->put_string("<td width=\"") + .put_number(width) + .put_string("%\" align=right>") + .nl(); + break; + default: + out->put_string("<td width=\"") + .put_number(width) + .put_string("%\">") + .nl(); + } + // 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); + out->put_string("</td>").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\">").nl(); + last_col = NULL; +} + +void html_table::emit_finish_table (void) +{ + finish_row(); + // out->put_string("linelength = ").put_number(linelength).nl(); + out->put_string("</table>"); + if (start_space) + out->put_string("</p>"); + out->nl(); +} + +/* + * 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 = NULL; + cols *n = NULL; + + 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 = (cols *)malloc(sizeof(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; + is_used = FALSE; +} + +html_indent::~html_indent (void) +{ + end(); + delete table; +} + +void html_indent::begin (int space) +{ + if (! is_used) { + table->emit_table_header(space); + table->emit_col(1); + is_used = TRUE; + } +} + +void html_indent::end (void) +{ + if (is_used) + table->emit_finish_table(); + is_used = FALSE; +} + +/* + * 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/contrib/groff/src/devices/grohtml/html-table.h b/contrib/groff/src/devices/grohtml/html-table.h index 3152060..c26eb6d 100644 --- a/contrib/groff/src/devices/grohtml/html-table.h +++ b/contrib/groff/src/devices/grohtml/html-table.h @@ -1,7 +1,7 @@ // -*- C++ -*- -/* Copyright (C) 2002 Free Software Foundation, Inc. +/* Copyright (C) 2002, 2003 Free Software Foundation, Inc. * - * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.cc + * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.h * * html-table.h * diff --git a/contrib/groff/src/devices/grohtml/html-text.cpp b/contrib/groff/src/devices/grohtml/html-text.cpp new file mode 100644 index 0000000..21a79eb --- /dev/null +++ b/contrib/groff/src/devices/grohtml/html-text.cpp @@ -0,0 +1,965 @@ +// -*- C++ -*- +/* Copyright (C) 2000, 2001, 2002, 2003 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#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" + +// #define DEBUGGING + +html_text::html_text (simple_output *op) : + stackptr(NULL), lastptr(NULL), out(op), 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: out->put_string("</p>"); + if (t->indent != NULL) { + delete t->indent; + t->indent = NULL; + } + out->nl(); 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->nl(); out->enable_newlines(TRUE); + blank_para = TRUE; break; + case SMALL_TAG: out->put_string("</small>"); break; + case BIG_TAG: out->put_string("</big>"); break; + case COLOR_TAG: out->put_string("</font>"); break; + + default: + error("unrecognised tag"); + } +} + +/* + * issue_tag - writes out an html tag with argument. + */ + +void html_text::issue_tag (const char *tagname, const char *arg) +{ + if ((arg == 0) || (strlen(arg) == 0)) { + out->put_string(tagname); + out->put_string(">"); + } else { + out->put_string(tagname); + out->put_string(" "); + out->put_string(arg); + out->put_string(">"); + } +} + +/* + * issue_color_begin - writes out an html color tag. + */ + +void html_text::issue_color_begin (color *c) +{ + unsigned int r, g, b; + char buf[6+1]; + + 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(); + issue_tag("\n<p", (char *)t->arg1); + } else { + out->nl(); + out->simple_comment("INDENTATION"); + t->indent->begin(FALSE); + start_space = FALSE; + issue_tag("<p", (char *)t->arg1); + } + + 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: if (t->indent != NULL) { + out->nl(); + out->simple_comment("INDENTATION"); + t->indent->begin(FALSE); + start_space = FALSE; + } + out->enable_newlines(TRUE); + out->nl(); issue_tag("<pre", (char *)t->arg1); + out->enable_newlines(FALSE); break; + case SMALL_TAG: issue_tag("<small", (char *)t->arg1); break; + case BIG_TAG: issue_tag("<big", (char *)t->arg1); break; + case BREAK_TAG: break; + case COLOR_TAG: 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; + free(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; +} + +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=(tag_definition *)malloc(sizeof(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=(tag_definition *)malloc(sizeof(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); + (void)done_para(); + if (! is_present(PRE_TAG)) + push_para(PRE_TAG, NULL, i); + } 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; + free(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; + free(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(""); + + if (is_present(BREAK_TAG)) { + int text = remove_break(); + check_emit_text(stackptr); + if (text) { + if (is_present(PRE_TAG)) { + out->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) +{ + if (! is_present(P_TAG)) { + if (is_present(PRE_TAG)) { + html_indent *i = remove_indent(PRE_TAG); + done_pre(); + if (i == in || in == NULL) + in = i; + else + delete i; + } + remove_sub_sup(); + push_para(P_TAG, (void *)arg, in); + space_emitted = TRUE; + } +} + +void html_text::do_para (const char *arg) +{ + do_para(arg, NULL); +} + +void html_text::do_para (simple_output *op, const char *arg1, + int indentation, int pageoffset, int linelength) +{ + html_indent *indent; + + if (indentation == 0) + indent = NULL; + else + indent = new html_indent(op, indentation, pageoffset, linelength); + do_para(arg1, indent); +} + +/* + * done_para - shuts down a paragraph tag. + */ + +char *html_text::done_para (void) +{ + space_emitted = TRUE; + return shutdown(P_TAG); +} + +/* + * 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; +} + +/* + * do_space - issues an end of paragraph + */ + +void html_text::do_space (void) +{ + if (is_in_pre()) { + if (blank_para) + start_space = TRUE; + else { + do_emittext("", 0); + out->nl(); + space_emitted = TRUE; + } + } else { + html_indent *i = remove_indent(P_TAG); + + do_para(done_para(), i); + space_emitted = TRUE; + start_space = 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 have start this paragraph with a .sp + */ + +int html_text::starts_with_space (void) +{ + return start_space; +} + +/* + * emit_space - writes a space providing that text was written beforehand. + */ + +void html_text::emit_space (void) +{ + if (space_emitted) { + 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; + tag_definition *q = 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; + 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; + } + free(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; + } + free(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); + done_para(); + do_para("", i); + return; + } + p = p->next; + } + } +} + +/* + * 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/contrib/groff/src/devices/grohtml/html-text.h b/contrib/groff/src/devices/grohtml/html-text.h index c43cbda..aa4dd80 100644 --- a/contrib/groff/src/devices/grohtml/html-text.h +++ b/contrib/groff/src/devices/grohtml/html-text.h @@ -1,7 +1,7 @@ // -*- C++ -*- -/* Copyright (C) 2000, 2001, 2002 Free Software Foundation, Inc. +/* Copyright (C) 2000, 2001, 2002, 2003 Free Software Foundation, Inc. * - * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.cc + * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.h * * html-text.h * @@ -47,7 +47,7 @@ typedef struct tag_definition { /* * the state of the current paragraph. - * It allows post-html.cc to request font changes, paragraph start/end + * It allows post-html.cpp to request font changes, paragraph start/end * and emits balanced tags with a small amount of peephole optimization. */ @@ -116,7 +116,7 @@ private: char *shutdown (HTML_TAG t); void check_emit_text (tag_definition *t); int remove_break (void); - void issue_tag (char *tagname, char *arg); + void issue_tag (const char *tagname, const char *arg); void issue_color_begin (color *c); void remove_def (tag_definition *t); html_indent *remove_indent (HTML_TAG tag); diff --git a/contrib/groff/src/devices/grohtml/output.cpp b/contrib/groff/src/devices/grohtml/output.cpp new file mode 100644 index 0000000..2705b9e --- /dev/null +++ b/contrib/groff/src/devices/grohtml/output.cpp @@ -0,0 +1,356 @@ +// -*- C++ -*- +/* Copyright (C) 2000, 2001, 2003 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" + +#include <time.h> +#include "html.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#undef DEBUGGING +// #define DEBUGGING + +#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 = (char *)malloc(n+1); + strncpy(s, w, n); + s[n] = (char)0; +} + +/* + * destroy word and the string copy. + */ + +word::~word () +{ + free(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; +} + +/* + * 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); + if (col != 0) { + 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/contrib/groff/src/devices/grohtml/post-html.cpp b/contrib/groff/src/devices/grohtml/post-html.cpp new file mode 100644 index 0000000..7c96deb --- /dev/null +++ b/contrib/groff/src/devices/grohtml/post-html.cpp @@ -0,0 +1,3812 @@ +// -*- C++ -*- +/* Copyright (C) 2000, 2001, 2002, 2003 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" +#include "html.h" +#include "html-text.h" +#include "html-table.h" + +#include <time.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <stdio.h> +#include <fcntl.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 BASE_POINT_SIZE 10 /* 10 points is the base size ie html size 3 */ +#define CENTER_TOLERANCE 2 /* how many pixels off center will we still */ +#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 + + +/* + * prototypes + */ + +char *get_html_translation (font *f, const string &name); +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 color *default_background = NULL; /* has user requested initial bg color? */ + + +/* + * 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; + + file (FILE *f); +}; + +/* + * file - initialize all fields to NULL + */ + +file::file (FILE *f) + : fp(f), next(0) +{ +} + +class files { +public: + files (); + FILE *get_file (void); + void start_of_list (void); + void move_next (void); + void add_new_file (FILE *f); +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 (head == 0) { + head = new file(f); + tail = head; + } else { + tail->next = new file(f); + tail = tail->next; + } + ptr = tail; +} + +/* + * 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) +{ +} + +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(NULL), used(0), next(0) +{ +} + +char_block::char_block(int length) +: used(0), next(0) +{ + buffer = (char *)malloc(max(length, char_block::SIZE)); + if (buffer == NULL) + fatal("out of memory error"); +} + +char_block::~char_block() +{ + if (buffer != NULL) + free(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 (s == NULL || length == 0) + return NULL; + + if (tail == 0) { + 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_ce (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 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, 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), thickness(line_thickness), tab(NULL) +{ +} + +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(NULL) +{ +} + +text_glob::~text_glob () +{ + if (tab != NULL) + 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) +{ + text_glob *g = new text_glob(s, "", 0, + min_vertical, min_horizontal, max_vertical, max_horizontal, + FALSE, FALSE, FALSE, TRUE, thickness); + *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, "html-tag: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, "html-tag:eol.ce") == 0) ); +} + + +/* + * is_nf - returns TRUE if glob contains the tag .nf + */ + +int text_glob::is_nf (void) +{ + return( is_tag && (strcmp(text_string, "html-tag:.nf") == 0) ); +} + +/* + * is_fi - returns TRUE if glob contains the tag .fi + */ + +int text_glob::is_fi (void) +{ + return( is_tag && (strcmp(text_string, "html-tag:.fi") == 0) ); +} + +/* + * is_ce - returns TRUE if glob contains the tag .ce + */ + +int text_glob::is_ce (void) +{ + return( is_tag && (strcmp(text_string, "html-tag:.ce") == 0) ); +} + +/* + * is_in - returns TRUE if glob contains the tag .in + */ + +int text_glob::is_in (void) +{ + return( is_tag && (strncmp(text_string, "html-tag:.in ", strlen("html-tag:.in ")) == 0) ); +} + +/* + * is_po - returns TRUE if glob contains the tag .po + */ + +int text_glob::is_po (void) +{ + return( is_tag && (strncmp(text_string, "html-tag:.po ", strlen("html-tag:.po ")) == 0) ); +} + +/* + * is_ti - returns TRUE if glob contains the tag .ti + */ + +int text_glob::is_ti (void) +{ + return( is_tag && (strncmp(text_string, "html-tag:.ti ", strlen("html-tag:.ti ")) == 0) ); +} + +/* + * is_col - returns TRUE if glob contains the tag .col + */ + +int text_glob::is_col (void) +{ + return( is_tag && (strncmp(text_string, "html-tag:.col", strlen("html-tag:.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, "html-tag:.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, "html-tag:.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, "html-tag:.ta ", strlen("html-tag:.ta ")) == 0) ); +} + +/* + * is_tab - returns TRUE if glob contains the tag tab + */ + +int text_glob::is_tab (void) +{ + return( is_tag && (strncmp(text_string, "html-tag:tab ", strlen("html-tag:tab ")) == 0) ); +} + +/* + * is_tab0 - returns TRUE if glob contains the tag tab0 + */ + +int text_glob::is_tab0 (void) +{ + return( is_tag && (strncmp(text_string, "html-tag:tab0", strlen("html-tag: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 ("html-tag:.br", text_string) == 0) || + (strncmp("html-tag:.sp", text_string, 11) == 0) || + (strcmp ("html-tag:.ce", text_string) == 0)) ); +} + +int text_glob::get_arg (void) +{ + if (strncmp("html-tag:", text_string, strlen("html-tag:")) == 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("html-tag:", text_string, strlen("html-tag:")) == 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 != NULL) + 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 != NULL) + 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(NULL), tail(NULL), ptr(NULL) +{ +} + +/* + * ~list - destroy a complete list. + */ + +list::~list() +{ + element_list *temp=head; + + do { + temp = head; + if (temp != NULL) { + head = head->right; + delete temp; + } + } while ((head != NULL) && (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 (head == 0) { + 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( head == 0 ); +} + +/* + * 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 (ptr == 0) + 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); + 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(), "html-tag:.auto-image", 20) == 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 x1, int y1, int x2, int y2, + int thickness) +{ + if (y1 == y2) { + text_glob *g = new text_glob(); + g->text_glob_line(s, + min(y1, y2), min(x1, y2), max(y1, y2), max(x1, x2), + thickness); + glyphs.add(g, line_number, min(y1, y2), min(x1, y2), max(y1, y2), max(x1, x2)); + } +} + +/* + * 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) +{ + string html_string; + char *html_glyph; + int i=0; + + if (s->f == NULL) + return; + while (i < str.length()) { + if ((i+1<str.length()) && (str.substring(i, 2) == string("\\("))) { + // start of escape + i += 2; // move over \( + int a = i; + while ((i+1<str.length()) && (str.substring(i, 2) != string("\\)"))) { + i++; + } + int n = i; + if ((i+1<str.length()) && (str.substring(i, 2) == string("\\)"))) + i++; + else + n = -1; + if (n > 0) { + string troff_charname = str.substring(a, n-a); + html_glyph = get_html_translation(s->f, troff_charname); + if (html_glyph) + html_string += html_glyph; + else { + int index=s->f->name_to_index((troff_charname + '\0').contents()); + + if (s->f->contains(index) && (index != 0)) + html_string += s->f->get_code(index); + } + } + } else + html_string += str[i]; + i++; + } + if (html_string.length() > 0) { + text_glob *g=new text_glob(); + 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() != NULL) + 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_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 + 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_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(); + do { + g = headers.get_data(); + fputs("<a href=\"#", f); + if (simple_anchors) { + string buffer(ANCHOR_TEMPLATE); + + buffer += as_string(h); + buffer += '\0'; + fprintf(f, buffer.contents()); + } else + fputs(g->text_string, f); + h++; + fputs("\">", f); + fputs(g->text_string, f); + fputs("</a><br>\n", f); + headers.move_right(); + } while (! headers.is_equal_to_head()); + fputs("\n", f); + } + } +} + +class html_printer : public printer { + files file_list; + simple_output html; + int res; + int space_char_index; + 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 supress_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 indentation; + int prev_indent; + int pointsize; + int vertical_spacing; + int line_number; + color *background; + + 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 (int index, const string &s); + void write_title (int in_head); + int sbuf_continuation (int index, 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_courier_until_eol (void); + void start_size (int from, int to); + void do_font (text_glob *g); + void do_center (char *arg); + void do_break (void); + void do_eol (void); + void do_eol_ce (void); + void do_title (void); + void do_fill (int on); + 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); + 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 (int index, 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 *); + // ADD HERE + +public: + html_printer (); + ~html_printer (); + void set_char (int i, font *f, const environment *env, int w, const char *name); + void set_numbered_char(int num, const environment *env, int *widthp); + 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); + 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 (fontname == NULL) + 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 font of, f, exists then 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 NULL; +} + +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 + html.put_string("<hr>"); +} + +/* + * 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 { + current_paragraph->done_para(); + switch (next_tag) { + + case CENTERED: + current_paragraph->do_para("align=center"); + break; + case LEFT: + current_paragraph->do_para(&html, "align=left", indentation, pageoffset, linelength); + break; + case RIGHT: + current_paragraph->do_para(&html, "align=right", indentation, pageoffset, linelength); + break; + default: + fatal("unknown enumeration"); + } + current_paragraph->do_emittext(g->text_string, g->text_length); + current_paragraph->done_para(); + next_tag = INLINE; + supress_sub_sup = TRUE; + if (indentation > 0) { + /* + * restore indentation + */ + int newin = indentation; + indentation = 0; + do_indent(newin, pageoffset, linelength); + } + } +} + +/* + * do_center - handle the .ce commands from troff. + */ + +void html_printer::do_center (char *arg) +{ + int n = atoi(arg); + current_paragraph->do_break(); + + if (n > 0) { + current_paragraph->done_para(); + supress_sub_sup = TRUE; + current_paragraph->do_para("align=center"); + end_center += n; + } else { + end_center = 0; + current_paragraph->remove_para_align(); + } +} + +/* + * do_centered_image - set a flag such that the next html-tag 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 html-tag 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 html-tag 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 + "\">"; + 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; + int eol_ce = 0; + + 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_eol_ce()) { + /* process the eol associated with .ce + */ + eol_ce++; + page_contents->glyphs.sub_move_right(); /* move onto next word */ + } else if (t->is_eol()) { + /* end of title found + */ + title.has_been_found = TRUE; + outstanding_eol(eol_ce); + return; + } else if (t->is_a_tag()) { + /* end of title found, but move back so that we read this tag and process it + */ + page_contents->glyphs.move_left(); /* move backwards to last word */ + title.has_been_found = TRUE; + outstanding_eol(eol_ce); + return; + } 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)); + } + outstanding_eol(eol_ce); + } +} + +void html_printer::write_header (void) +{ + if (! header.header_buffer.empty()) { + if (header.header_level > 7) { + header.header_level = 7; + } + + // firstly we must terminate any font and type faces + current_paragraph->done_para(); + supress_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; + + text_glob *h=new text_glob(); + h->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); + + header.headers.add(h, + header.no_of_headings, + header.no_of_headings, header.no_of_headings, + header.no_of_headings, header.no_of_headings); // and add this header to the header list + + // lastly we generate a tag + + html.nl().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(); + } + + 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></b>").nl(); + } + else { + html.put_string("<b>"); + html.put_string(header.header_buffer); + html.put_string("</b>").nl(); + } + } + else { + // and now we issue the real header + html.put_string("<h"); + html.put_number(header.header_level); + html.put_string(">"); + html.put_string(header.header_buffer); + html.put_string("</h"); + html.put_number(header.header_level); + html.put_string(">").nl(); + } + + current_paragraph->do_para(&html, "", indentation, pageoffset, linelength); + } +} + +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; +} + +/* + * do_heading - handle the .SH and .NH and equivalent commands from troff. + */ + +void html_printer::do_heading (char *arg) +{ + text_glob *g; + text_glob *l = 0; + int level=atoi(arg); + + header.header_buffer.clear(); + page_contents->glyphs.move_right(); + if (! page_contents->glyphs.is_equal_to_head()) { + g = page_contents->glyphs.get_data(); + do { + if (g->is_auto_img()) { + string img=generate_img_src((char *)(g->text_string + 20)); + + if (! img.empty()) { + simple_anchors = TRUE; // we cannot use full heading anchors with images + if (l != 0) + header.header_buffer += " "; + + l = g; + header.header_buffer += img; + } + } else if (! (g->is_a_line() || g->is_a_tag())) { + /* + * we ignore tags commands when constructing a heading + */ + if (l != 0) + header.header_buffer += " "; + l = g; + + 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_br())); + } + + determine_header_level(level); + write_header(); + + // finally set the output to neutral for after the header + 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); + + if (fill_on) + do_indent(indentation, pageoffset, atoi(arg)); +} + +/* + * do_pageoffset - handle the .po command from troff. + */ + +void html_printer::do_pageoffset (char *arg) +{ + if (fill_on) + do_indent(indentation, atoi(arg), linelength); +} + +/* + * do_indentation - handle the .in command from troff. + */ + +void html_printer::do_indentation (char *arg) +{ + if (fill_on) + do_indent(atoi(arg), pageoffset, linelength); +} + +/* + * do_tempindent - handle the .ti command from troff. + */ + +void html_printer::do_tempindent (char *arg) +{ + if (fill_on) { + end_tempindent = 1; + prev_indent = indentation; + do_indent(atoi(arg), pageoffset, linelength); + } +} + +/* + * shutdown_table - shuts down the current table. + */ + +void html_printer::shutdown_table (void) +{ + if (table != NULL) { + current_paragraph->done_para(); + table->emit_finish_table(); + // dont delete this table as it will be deleted when we destroy the text_glob + table = NULL; + } +} + +/* + * 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 ((indentation != -1) && + (pageoffset+indentation != in+pageoff)) { + + current_paragraph->done_para(); + + indentation = in; + pageoffset = pageoff; + if (linelen <= max_linelength) + linelength = linelen; + + current_paragraph->do_para(&html, "", indentation, pageoffset, max_linelength); + } +} + +/* + * 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) +{ + pointsize = atoi(arg); +} + +/* + * do_fill - records whether troff has requested that text be filled. + */ + +void html_printer::do_fill (int on) +{ + current_paragraph->do_break(); + output_hpos = indentation+pageoffset; + supress_sub_sup = TRUE; + + if (fill_on != on) { + if (on) + current_paragraph->do_para(""); + else + current_paragraph->do_pre(); + 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 = indentation+pageoffset; +} + +/* + * 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(); + supress_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) +{ + current_paragraph->done_para(); + auto_links = FALSE; /* from now on only emit under user request */ + file_list.add_new_file(xtmpfile()); + html.set_file(file_list.get_file()); +} + +/* + * do_break - handles the ".br" request and also + * undoes an outstanding ".ti" command. + */ + +void html_printer::do_break (void) +{ + current_paragraph->do_break(); + if (end_tempindent > 0) { + end_tempindent--; + if (end_tempindent == 0) + do_indent(prev_indent, pageoffset, linelength); + } + output_hpos = indentation+pageoffset; + supress_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 != NULL) { + current_paragraph->done_pre(); + current_paragraph->done_para(); + + html.simple_comment("TABS"); + + t->set_linelength(max_linelength); + t->add_indent(pageoffset); + t->emit_table_header(FALSE); + } + + table = t; +} + +/* + * do_tab_te - finish a table. + */ + +void html_printer::do_tab_te (void) +{ + if (table) { + current_paragraph->done_para(); + table->emit_finish_table(); + } + + table = NULL; + + if (indentation > 0) { + /* + * restore indentation + */ + int newin = indentation; + indentation = 0; + do_indent(newin, pageoffset, linelength); + } +} + +/* + * do_tab - handle the "html-tag:tab" tag + */ + +void html_printer::do_tab (char *s) +{ + if (table) { + while (isspace(*s)) + s++; + s++; + int col = table->find_column(atoi(s) + pageoffset + indentation); + if (col > 0) { + current_paragraph->done_para(); + table->emit_col(col); + } + } +} + +/* + * do_tab0 - handle the "html-tag:tab0" tag + */ + +void html_printer::do_tab0 (void) +{ + if (table) { + int col = table->find_column(pageoffset+indentation); + 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) { + current_paragraph->done_para(); + table->emit_col(atoi(s)); + } +} + +/* + * troff_tag - processes the troff tag and manipulates the troff state machine. + */ + +void html_printer::troff_tag (text_glob *g) +{ + /* + * firstly skip over html-tag: + */ + char *t=(char *)g->text_string+9; + + if (g->is_eol()) { + do_eol(); + } else if (g->is_eol_ce()) { + do_eol_ce(); + } else if (strncmp(t, ".sp", 3) == 0) { + if (g->get_arg() > 0) + current_paragraph->do_space(); + else + current_paragraph->do_break(); + supress_sub_sup = TRUE; + } else if (strncmp(t, ".br", 3) == 0) { + 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; + supress_sub_sup = TRUE; + do_center(a); + } else if (strncmp(t, ".tl", 3) == 0) { + supress_sub_sup = TRUE; + title.with_h1 = TRUE; + do_title(); + } else if (strncmp(t, ".html-tl", 8) == 0) { + supress_sub_sup = TRUE; + title.with_h1 = FALSE; + do_title(); + } else if (strncmp(t, ".fi", 3) == 0) { + do_fill(TRUE); + } else if (strncmp(t, ".nf", 3) == 0) { + do_fill(FALSE); + } 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 (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(); + } +} + +/* + * 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 (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 { + emit_html(g); + } + /* + * 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 != NULL) { + if (g->is_fi()) + return FALSE; + if (g->is_nf()) + return TRUE; + } + 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()) + indentation = g->get_arg(); + else if (g->is_po()) + pageoffset = g->get_arg(); + else if (g->is_ti()) { + prev_indent = indentation; + indentation = g->get_arg(); + end_tempindent = 1; + } else if (g->is_br() && ((end_tempindent > 0) || (nf && g->is_eol()))) { + end_tempindent = 0; + indentation = prev_indent; + } +} + +/* + * 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 != NULL) && (g->is_br() || (nf && g->is_eol()))) + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.move_right_get_data(); + if (g == NULL) + page_contents->glyphs.start_from_head(); + 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(); + page_contents->insert_tag(string("html-tag:.tab-ts")); // tab table start + 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(); + + page_contents->insert_tag(string("html-tag:.tab-te")); // tab table end + 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(); + page_contents->insert_tag(string("html-tag:tab0")); // tab0 start of line + 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(); + line_start = TRUE; + do { + g = page_contents->glyphs.get_data(); + + 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()); + 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 = NULL; + text_glob *g = NULL; + int seen_tab = FALSE; + int seen_col = FALSE; + int nf = FALSE; + + if (! page_contents->glyphs.is_empty()) { + page_contents->glyphs.start_from_head(); + start_of_line = page_contents->glyphs.get_data(); + do { + g = page_contents->glyphs.get_data(); + + 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(); + 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 = NULL; + text_glob *start_of_table = NULL; + text_glob *last = NULL; + colType type_of_col = none; + int left = 0; + int found_col = FALSE; + int seen_text = FALSE; + int ncol = 0; + int colmin; + int colmax; + html_table *table = new html_table(&html, -1); + const char *tab_defs = NULL; + 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(); + g = page_contents->glyphs.get_data(); + 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 != NULL) { + page_contents->glyphs.move_left(); + insert_tab_te(); + start_of_table->remember_table(table); + table = new html_table(&html, -1); + page_contents->insert_tag(string("*** TAB -> COL ***")); + if (tab_defs != NULL) + table->tab_stops->init(tab_defs); + start_of_table = NULL; + last = NULL; + } + 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 = table->find_tab_column(colmin); + colmin += pageoffset + indentation; + colmax = table->get_tab_pos(ncol+1); + if (colmax > 0) + colmax += pageoffset + indentation; + } else if (g->is_tab0()) { + if (type_of_col == col_tag && start_of_table != NULL) { + page_contents->glyphs.move_left(); + insert_tab_te(); + start_of_table->remember_table(table); + table = new html_table(&html, -1); + page_contents->insert_tag(string("*** COL -> TAB ***")); + start_of_table = NULL; + last = NULL; + } + if (tab_defs != NULL) + table->tab_stops->init(tab_defs); + + type_of_col = tab0_tag; + ncol = 1; + colmin = 0; + colmax = table->get_tab_pos(2) + pageoffset + indentation; + } else if (! g->is_a_tag()) + update_min_max(type_of_col, &colmin, &colmax, g); + + if ((! g->is_a_tag()) || g->is_tab()) + seen_text = TRUE; + + if ((g->is_col() || g->is_tab() || g->is_tab0()) + && (start_of_line != NULL) && (start_of_table == NULL)) { + start_of_table = insert_tab_ts(start_of_line); + start_of_line = NULL; + seen_text = FALSE; + } else if (g->is_ce() && (start_of_table != NULL)) { + add_table_end("*** CE ***"); + start_of_table->remember_table(table); + start_of_table = NULL; + last = NULL; + } else if (g->is_ta()) { + tab_defs = g->text_string; + if (!table->tab_stops->compatible(tab_defs)) { + if (start_of_table != NULL) { + add_table_end("*** TABS ***"); + start_of_table->remember_table(table); + table = new html_table(&html, -1); + start_of_table = NULL; + type_of_col = none; + last = NULL; + } + table->tab_stops->init(tab_defs); + } + } + + if (((! g->is_a_tag()) || g->is_tab()) && (start_of_table != NULL)) { + // we are in a table and have a glyph + if ((ncol == 0) || (! table->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(table); + table = new html_table(&html, -1); + start_of_table = NULL; + type_of_col = none; + last = NULL; + } + } + + /* + * move onto next glob, check whether we are starting a new line + */ + g = page_contents->glyphs.move_right_get_data(); + + if (g == NULL) { + if (found_col) { + page_contents->glyphs.start_from_head(); + last = g; + found_col = FALSE; + } + } else if (g->is_br() || (nf && g->is_eol())) { + do { + g = page_contents->glyphs.move_right_get_data(); + nf = calc_nf(g, nf); + } while ((g != NULL) && (g->is_br() || (nf && g->is_eol()))); + start_of_line = g; + seen_text = FALSE; + ncol = 0; + left = next_horiz_pos(g, nf); + if (found_col) + last = g; + found_col = FALSE; + } + } while ((g != NULL) && (! 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 != NULL) { + if (last != NULL) + while (last != page_contents->glyphs.get_data()) + page_contents->glyphs.move_left(); + + insert_tab_te(); + start_of_table->remember_table(table); + table = NULL; + page_contents->insert_tag(string("*** LAST ***")); + } + } + if (table != NULL) + delete table; + + // and reset the registers + pageoffset = old_pageoffset; + indentation = 0; + prev_indent = 0; + end_tempindent = 0; +} + +void html_printer::flush_page (void) +{ + supress_sub_sup = TRUE; + flush_sbuf(); + page_contents->dump_page(); + lookahead_for_tables(); + page_contents->dump_page(); + + flush_globs(); + current_paragraph->done_para(); + + // 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 ajoining then no space emitted. + */ + +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_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())) { + current_paragraph->do_pre(); + } + current_paragraph->do_tt(); + } else if (strcmp(fontname, "CI") == 0) { + if ((! fill_on) && (is_courier_until_eol())) { + 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())) { + 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())) { + 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 increase <big> and <small> decrease alters 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 (! supress_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(); + } + } +} + +/* + * 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; + supress_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 != NULL) + 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 != NULL) + 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), + supress_sub_sup(TRUE), + cutoff_heading(100), + indent(NULL), + table(NULL), + end_center(0), + end_tempindent(0), + next_tag(INLINE), + fill_on(TRUE), + max_linelength(-1), + linelength(0), + pageoffset(0), + indentation(0), + prev_indent(0), + pointsize(0), + line_number(0), + background(default_background) +{ + file_list.add_new_file(xtmpfile()); + html.set_file(file_list.get_file()); + if (font::hor != 24) + fatal("horizontal resolution must be 24"); + if (font::vert != 40) + fatal("vertical resolution 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_char_index = font::name_to_index("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 (int index, const string &s) +{ + if (sbuf_style.f == NULL) + return; + + char *html_glyph = NULL; + unsigned int code = sbuf_style.f->get_code(index); + + if (s.empty()) { + if (sbuf_style.f->contains(index)) + html_glyph = (char *)sbuf_style.f->get_special_device_encoding(index); + else + html_glyph = NULL; + + if ((html_glyph == NULL) && (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 (html_glyph == NULL) + sbuf += ((char)code); + else + sbuf += html_glyph; +} + +int html_printer::sbuf_continuation (int index, 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(index, 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(index, 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. + */ + +char *get_html_translation (font *f, const string &name) +{ + int index; + + if ((f == 0) || name.empty()) + return NULL; + else { + index = f->name_to_index((char *)(name + '\0').contents()); + if (index == 0) { + error("character `%s' not found", (name + '\0').contents()); + return NULL; + } else + if (f->contains(index)) + return (char *)f->get_special_device_encoding(index); + else + return NULL; + } +} + +/* + * 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(int index, 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(index, 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(index, 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(int i, 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("silly slant `%1' degrees", sty.slant); + sty.slant = 0; + } + } + if (((! sbuf.empty()) && (sty == sbuf_style) && (sbuf_vpos == env->vpos)) + && (sbuf_continuation(i, name, env, w) || overstrike(i, name, env, w))) + return; + + flush_sbuf(); + add_to_sbuf(i, 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; // + } + int i = font::number_to_index(num); + int fn = env->fontno; + if (fn < 0 || fn >= nfonts) { + error("bad font position `%1'", fn); + return; + } + font *f = font_table[fn]; + if (f == 0) { + error("no font mounted at `%1'", fn); + return; + } + if (!f->contains(i)) { + 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(i, env->size); + if (widthp) + *widthp = w; + set_char(i, f, env, w, 0); +} + +/* + * 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) { + 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 a html rule tag, if the auto_rule boolean is true. + */ + +static void write_rule (void) +{ + if (auto_rule) + 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); + do_indent(indentation, pageoffset, linelength); + current_paragraph->do_para(""); +} + +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 (background == NULL) + fputs("<body>\n\n", stdout); + else { + unsigned int r, g, b; + char buf[6+1]; + + 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); + } +} + +html_printer::~html_printer() +{ +#ifdef LONG_FOR_TIME_T + long t; +#else + time_t t; +#endif + + current_paragraph->flush_text(); + html.end_line(); + html.set_file(stdout); + html.begin_comment("Creator : ") + .put_string("groff ") + .put_string("version ") + .put_string(Version_string) + .end_comment(); + + t = time(0); + html.begin_comment("CreationDate: ") + .put_string(ctime(&t), strlen(ctime(&t))-1) + .end_comment(); + + 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); + write_title(TRUE); + 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(); + /* + * now run through the file list copying each temporary file in turn and emitting the links. + */ + file_list.start_of_list(); + 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.get_file() != 0) + header.write_headings(stdout, TRUE); + } + write_rule(); + fputs("</body>\n", stdout); + fputs("</html>\n", stdout); +} + +/* + * 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 + * also allow troff to emit tags to indicate when a: .br, .sp etc occurs. + */ + +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 (f == NULL) { + int found=FALSE; + + f = font::load_font("TR", &found); + } + + /* + * need to 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); + + /* + * 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, "html-tag:", 9) == 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); + } + } +} + +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:g:o:i:I:D:F:vbdhlrnp", long_options, NULL)) + != EOF) + switch(c) { + case 'v': + printf("GNU post-grohtml (groff) version %s\n", Version_string); + exit(0); + break; + case 'a': + /* text antialiasing bits - handled by pre-html */ + break; + case 'g': + /* graphic 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 'F': + font::command_line_font_dir(optarg); + break; + case 'l': + auto_links = FALSE; + break; + case 'r': + auto_rule = FALSE; + break; + case 'd': + /* handled by pre-html */ + break; + case 'h': + /* do not use the Hn headings of html, but manufacture our own */ + manufacture_headings = TRUE; + break; + case 'o': + /* handled by pre-html */ + break; + case 'p': + /* handled by pre-html */ + break; + case 'i': + /* handled by pre-html */ + break; + case 'I': + /* handled by pre-html */ + break; + case 'D': + /* handled by pre-html */ + break; + case 'n': + simple_anchors = TRUE; + break; + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + 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 [-vblnh] [-D dir] [-I image_stem] [-F dir] [files ...]\n", + program_name); +} diff --git a/contrib/groff/src/devices/grolbp/Makefile.sub b/contrib/groff/src/devices/grolbp/Makefile.sub index d8000d1..a237293 100644 --- a/contrib/groff/src/devices/grolbp/Makefile.sub +++ b/contrib/groff/src/devices/grolbp/Makefile.sub @@ -3,4 +3,4 @@ MAN1=grolbp.n XLIBS=$(LIBDRIVER) $(LIBGROFF) MLIB=$(LIBM) OBJS=lbp.$(OBJEXT) -CCSRCS=$(srcdir)/lbp.cc +CCSRCS=$(srcdir)/lbp.cpp diff --git a/contrib/groff/src/devices/grolbp/charset.h b/contrib/groff/src/devices/grolbp/charset.h index adc76f4..a0cd057 100644 --- a/contrib/groff/src/devices/grolbp/charset.h +++ b/contrib/groff/src/devices/grolbp/charset.h @@ -1,6 +1,6 @@ // Definition of the WP54 character set -char symset[] = { +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, diff --git a/contrib/groff/src/devices/grolbp/lbp.cpp b/contrib/groff/src/devices/grolbp/lbp.cpp new file mode 100644 index 0000000..b34a1e0 --- /dev/null +++ b/contrib/groff/src/devices/grolbp/lbp.cpp @@ -0,0 +1,739 @@ +// -*- C++ -*- +/* Copyright (C) 1994, 2000, 2001, 2002, 2003 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* +TODO + + - Add X command to include bitmaps +*/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#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; // papersize +static int orientation = -1; // orientation +static double user_paperlength = 0; // Custom Paper size +static double user_paperwidth = 0; +static int ncopies = 1; // Number of copies + +#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(int, 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 + int papersize; + int paperlength; // custom paper size + int paperwidth; +}; + +// Compatibility section. +// +// Here we define some functions not present in some of the targets +// platforms +#ifndef HAVE_STRSEP +// Solaris 8 doesn't have the strsep function +static char *strsep(char **pcadena, const char *delim) +{ + char *p; + p = strtok(*pcadena, delim); + *pcadena = strtok(NULL, delim); + return p; +} +#endif + +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 size handling + if (orientation < 0) + orientation = 0; // Default orientation is portrait + papersize = 14; // Default paper size 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(int index, font *f, const environment *env, + int w, const char *) +{ + int code = f->get_code(index); + 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 + vdmsetfillmode(mode, 1, 1); // To get black we must use white + // inverted + 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, "Poligon (%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 size + 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 size + return 82; +} + +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 [-lvh] [-c n] [-p paper_size] [-F dir] [-o or]\n" + " [-w width] [files ...]\n" + "\n" + " -o --orientation=[portrait|landscape]\n" + " -v --version\n" + " -c --copies=numcopies\n" + " -l --landscape\n" + " -p --papersize=paper_size\n" + " -w --linewidth=width\n" + " -F --fontdir=dir\n" + " -h --help\n", + program_name); +} + +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 = 0; + int option_index = 0; + while (c >= 0) { + c = getopt_long (argc, argv, "F:p:lvo:c:hw:", + long_options, &option_index); + switch (c) { + case 'F': + font::command_line_font_dir(optarg); + break; + case 'p': + { + const char *s; + if (!font::scan_papersize(optarg, &s, + &user_paperlength, &user_paperwidth)) + error("invalid paper size `%1' ignored", 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(0); + 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); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + } + } + if (optind >= argc) + do_file("-"); + while (optind < argc) + do_file(argv[optind++]); + lbpputs("\033c\033<"); + return 0; +} diff --git a/contrib/groff/src/devices/grolbp/lbp.h b/contrib/groff/src/devices/grolbp/lbp.h index 3b8a941..e669ff2 100644 --- a/contrib/groff/src/devices/grolbp/lbp.h +++ b/contrib/groff/src/devices/grolbp/lbp.h @@ -1,5 +1,5 @@ // -*- C -*- -/* Copyright (C) 1994, 2000, 2001 Free Software Foundation, Inc. +/* Copyright (C) 1994, 2000, 2001, 2003 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 @@ -52,7 +52,7 @@ lbpputs(const char *data) }; static inline void -lbpputc(char c) +lbpputc(unsigned char c) { fputc(c,lbpoutput); }; diff --git a/contrib/groff/src/devices/grolj4/Makefile.sub b/contrib/groff/src/devices/grolj4/Makefile.sub index 21c3780..9899b7a 100644 --- a/contrib/groff/src/devices/grolj4/Makefile.sub +++ b/contrib/groff/src/devices/grolj4/Makefile.sub @@ -3,4 +3,4 @@ MAN1=grolj4.n XLIBS=$(LIBDRIVER) $(LIBGROFF) MLIB=$(LIBM) OBJS=lj4.$(OBJEXT) -CCSRCS=$(srcdir)/lj4.cc +CCSRCS=$(srcdir)/lj4.cpp diff --git a/contrib/groff/src/devices/grolj4/lj4.cpp b/contrib/groff/src/devices/grolj4/lj4.cpp new file mode 100644 index 0000000..1332e19 --- /dev/null +++ b/contrib/groff/src/devices/grolj4/lj4.cpp @@ -0,0 +1,706 @@ +// -*- C++ -*- +/* Copyright (C) 1994, 2000, 2001, 2002, 2003 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* +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 "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 size 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(int, 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("unknown paper size `%1'", font::papersize); + else + paper_size = n; + } + if (ps >= 0) + paper_size = ps; + printf("\033&l%dA" // paper size + "\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(int index, font *f, const environment *env, + int w, const char *) +{ + int code = f->get_code(index); + + 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, ":F:p:d:lvw:c:", long_options, NULL)) + != EOF) + switch(c) { + case 'l': + landscape_flag = 1; + break; + case ':': + if (optopt == 'd') { + fprintf(stderr, "duplex assumed to be long-side\n"); + duplex_flag = 1; + } else + fprintf(stderr, "option -%c requires an operand\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("unknown paper size `%1'", optarg); + 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 [-lv] [-d [n]] [-c n] [-p paper_size]\n" + " [-w n] [-F dir] [files ...]\n", + program_name); +} diff --git a/contrib/groff/src/devices/grops/Makefile.sub b/contrib/groff/src/devices/grops/Makefile.sub index ee2e6d1..e1595cb 100644 --- a/contrib/groff/src/devices/grops/Makefile.sub +++ b/contrib/groff/src/devices/grops/Makefile.sub @@ -6,7 +6,7 @@ OBJS=\ ps.$(OBJEXT) \ psrm.$(OBJEXT) CCSRCS=\ - $(srcdir)/ps.cc \ - $(srcdir)/psrm.cc + $(srcdir)/ps.cpp \ + $(srcdir)/psrm.cpp HDRS=\ $(srcdir)/ps.h diff --git a/contrib/groff/src/devices/grops/TODO b/contrib/groff/src/devices/grops/TODO index 7ab3b69..eab8f83 100644 --- a/contrib/groff/src/devices/grops/TODO +++ b/contrib/groff/src/devices/grops/TODO @@ -1,7 +1,5 @@ Read PFB files directly. -Generate %%DocumentMedia comment. - Generate %%For comment. Generate %%Title comment. diff --git a/contrib/groff/src/devices/grops/grops.man b/contrib/groff/src/devices/grops/grops.man index e7d2305..e9f8f02 100644 --- a/contrib/groff/src/devices/grops/grops.man +++ b/contrib/groff/src/devices/grops/grops.man @@ -1,5 +1,5 @@ .ig -Copyright (C) 1989-2000, 2001, 2002 Free Software Foundation, Inc. +Copyright (C) 1989-2000, 2001, 2002, 2003 Free Software Foundation, Inc. Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice @@ -23,8 +23,18 @@ the original English. .\" Like TP, but if specified indent is more than half .\" the current line-length - indent, use the default indent. .de Tp -.ie \\n(.$=0:((0\\$1)*2u>(\\n(.lu-\\n(.iu)) .TP -.el .TP "\\$1" +. ie \\n(.$=0:((0\\$1)*2u>(\\n(.lu-\\n(.iu)) .TP +. el .TP "\\$1" +.. +. +.de TQ +. br +. ns +. TP \$1 +.. +. +.de FT +. if '\\*(.T'ps' .ft \\$1 .. . . @@ -106,52 +116,56 @@ option. .SH OPTIONS .TP .BI \-b n -Workaround broken spoolers and previewers. +Provide workarounds for older printers, broken spoolers, and previewers. . Normally .B grops -produces output that conforms -the Document Structuring Conventions version 3.0. +produces output at PostScript LanguageLevel\~2 that conforms to the +Document Structuring Conventions version 3.0. . -Unfortunately some spoolers and previewers can't handle such output. +Some older printers, spoolers, and previewers can't handle such output. . The value of\~\c .I n controls what .B grops -does to its output acceptable to such programs. +does to make its output acceptable to such programs. . A value of\~0 will cause grops not to employ any workarounds. . +.IP Add\~1 if no -.B %%BeginDocumentSetup +.B %%Begin\%Document\%Setup and -.B %%EndDocumentSetup +.B %%End\%Document\%Setup comments should be generated; this is needed for early versions of TranScript that get confused by anything between the -.B %%EndProlog +.B %%End\%Prolog comment and the first .B %%Page comment. . +.IP Add\~2 if lines in included files beginning with .B %! should be stripped out; this is needed for Sun's pageview previewer. . +.IP Add\~4 if .BR %%Page , .BR %%Trailer and -.B %%EndProlog +.B %%End\%Prolog comments should be stripped out of included files; this is needed for spoolers that don't understand the -.B %%BeginDocument +.B %%Begin\%Document and -.B %%EndDocument +.B %%End\%Document comments. . +.IP Add\~8 if the first line of the PostScript output should be .B %!PS-Adobe-2.0 rather than @@ -159,6 +173,18 @@ rather than this is needed when using Sun's Newsprint with a printer that requires page reversal. . +.IP +Add\~16 if no media size information should be included in the document +(this is, neither use +.B %%Document\%Media +nor the +.B setpagedevice +PostScript command). +. +This was the behaviour of groff version 1.18.1 and earlier; it is needed +for older printers which don't understand PostScript LanguageLevel\~2. +. +.IP The default value can be specified by a . .RS @@ -211,15 +237,20 @@ Turn manual feed on for the document. Set physical dimension of output medium. . This overrides the -.B papersize +.BR papersize , +.BR paperlength , and -.B paperlength +.B paperwidth commands in the .B DESC file; it accepts the same arguments as the .B papersize command. . +See +.B groff_font (@MAN5EXT@) +for details. +. .TP .BI \-P prologue-file Use the file @@ -258,15 +289,11 @@ The fonts are grouped into families .BR H , .BR HN , .BR N , -.B P +.BR P , and\~\c .B T having members in each of these styles: . -.de FT -.if '\\*(.T'ps' .ft \\$1 -.. -. .RS .TP .B AR @@ -274,187 +301,187 @@ having members in each of these styles: AvantGarde-Book .FT . -.TP +.TQ .B AI .FT AI AvantGarde-BookOblique .FT . -.TP +.TQ .B AB .FT AB AvantGarde-Demi .FT . -.TP +.TQ .B ABI .FT ABI AvantGarde-DemiOblique .FT . -.TP +.TQ .B BMR .FT BMR Bookman-Light .FT . -.TP +.TQ .B BMI .FT BMI Bookman-LightItalic .FT . -.TP +.TQ .B BMB .FT BMB Bookman-Demi .FT . -.TP +.TQ .B BMBI .FT BMBI Bookman-DemiItalic .FT . -.TP +.TQ .B CR .FT CR Courier .FT . -.TP +.TQ .B CI .FT CI Courier-Oblique .FT . -.TP +.TQ .B CB .FT CB Courier-Bold .FT . -.TP +.TQ .B CBI .FT CBI Courier-BoldOblique .FT . -.TP +.TQ .B HR .FT HR Helvetica .FT . -.TP +.TQ .B HI .FT HI Helvetica-Oblique .FT . -.TP +.TQ .B HB .FT HB Helvetica-Bold .FT . -.TP +.TQ .B HBI .FT HBI Helvetica-BoldOblique .FT . -.TP +.TQ .B HNR .FT HNR Helvetica-Narrow .FT . -.TP +.TQ .B HNI .FT HNI Helvetica-Narrow-Oblique .FT . -.TP +.TQ .B HNB .FT HNB Helvetica-Narrow-Bold .FT . -.TP +.TQ .B HNBI .FT HNBI Helvetica-Narrow-BoldOblique .FT . -.TP +.TQ .B NR .FT NR NewCenturySchlbk-Roman .FT . -.TP +.TQ .B NI .FT NI NewCenturySchlbk-Italic .FT . -.TP +.TQ .B NB .FT NB NewCenturySchlbk-Bold .FT . -.TP +.TQ .B NBI .FT NBI NewCenturySchlbk-BoldItalic .FT . -.TP +.TQ .B PR .FT PR Palatino-Roman .FT . -.TP +.TQ .B PI .FT PI Palatino-Italic .FT . -.TP +.TQ .B PB .FT PB Palatino-Bold .FT . -.TP +.TQ .B PBI .FT PBI Palatino-BoldItalic .FT . -.TP +.TQ .B TR .FT TR Times-Roman .FT . -.TP +.TQ .B TI .FT TI Times-Italic .FT . -.TP +.TQ .B TB .FT TB Times-Bold .FT . -.TP +.TQ .B TBI .FT TBI Times-BoldItalic @@ -474,9 +501,10 @@ ZapfChancery-MediumItalic . .LP There are also some special fonts called -.B SS -and\~\c -.BR S . +.B S +for the PS Symbol font, and +.BR SS , +containing slanted lowercase Greek letters taken from PS Symbol. . Zapf Dingbats is available as .BR ZD @@ -497,6 +525,10 @@ is used, for `cmy' and `cmyk' .BR setcmykcolor , and for `gray' .BR setgray . +Note that +.B setcmykcolor +is a PostScript LanguageLevel\~2 command and thus not available on some +older printers. . .LP .B grops @@ -686,70 +718,23 @@ arguments are not allowed to have attached scaling indicators. . If the PostScript file complies with the Adobe Document Structuring Conventions and contains a -.B %%BoundingBox +.B %%Bounding\%Box comment, then the bounding box can be automatically extracted from within groff by using the .B psbb request. . -.RS -.LP -The -.B \-mps -macros (which are automatically loaded when -.B grops -is run by the groff command) include a -.B PSPIC -macro which allows a picture to be easily imported. -. -This has the format .IP -\&\fB.PSPIC\fP [\fB\-L\fP|\fB-R\fP|\fB\-I\fP \fIn\fP]\ \fI\|file\fP [\fIwidth\fP [\fIheight\fP]] -. -.LP -.I file -is the name of the file containing the illustration; -.I width -and -.I height -give the desired width and height of the graphic. -. -The -.I width -and -.I height -arguments may have scaling indicators attached; -the default scaling indicator is\~\c -.BR i . -. -This macro will scale the graphic uniformly -in the x and y\~directions so that it is no more than -.I width -wide -and -.I height -high. -. -By default, the graphic will be horizontally centered. -. -The -.BI \-L -and -.BI \-R -cause the graphic to be left-aligned and right-aligned -respectively. -. -The -.B \-I -option causes the graphic to be indented by\~\c -.IR n . -.RE +See +.BR 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'ps:\ invis' -.br -.ns -.TP +.TQ .B \[rs]X'ps:\ endinvis' No output will be generated for text and drawing commands that are bracketed with these @@ -796,7 +781,12 @@ whereas will print the .B \[rs](em character -and ignore the line. +and ignore the line (this code is already in file +.B Xps.tmac +which will be loaded if a documented intended for +.B grops +is previewed with +.BR gxditview ). .RE . .LP @@ -808,7 +798,8 @@ must be in the format output by This is described in .BR groff_out (@MAN5EXT@). . -In addition the device and font description files for the device used +.LP +In addition, the device and font description files for the device used must meet certain requirements. . The device and font description files supplied for @@ -825,25 +816,12 @@ The .B ps device uses a resolution of 72000 and a sizescale of 1000. . -The device description file should contain a command -.IP -.BI paperlength\ n .LP -which says that output should be generated which is suitable for -printing on a page whose length is -.IR n \~\c -machine units. -. -Common values are 792000 for letter paper and 841890 for paper in A4 format. -. -Alternatively, it can contain -.IP -.BI papersize\ string -.LP -to specify a paper size; see +The device description file must contain a valid paper size; see .BR groff_font (@MAN5EXT@) for more information. . +.LP Each font description file must contain a command .IP .BI internalname\ psname @@ -868,7 +846,8 @@ where is the PostScript name of the character, and .I code -is its position in the encoding expressed as a decimal integer. +is its position in the encoding expressed as a decimal integer; valid +values are in the range 0 to\~255. . Lines starting with .B # @@ -897,10 +876,24 @@ it can make use of such a character to generate more efficient and compact PostScript output. . .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 .B grops can automatically include the downloadable fonts necessary to print the document. -. +Such fonts must be in PFA format. +Use +.BR pfbtops (@MAN1EXT@) +to convert a Type\~1 font in PFB format. Any downloadable fonts which should, when required, be included by .B grops must be listed in the file @@ -951,7 +944,7 @@ 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 be appear before Garamond-Outline +then it is necessary for Garamond to appear before Garamond-Outline in the PostScript document. . .B grops @@ -981,30 +974,30 @@ in the file. . A downloadable font should not include its own name in a -.B %%DocumentSuppliedResources +.B %%Document\%Supplied\%Resources comment. . .LP .B grops will not interpret -.B %%DocumentFonts +.B %%Document\%Fonts comments. . The -.BR %%DocumentNeededResources , -.BR %%DocumentSuppliedResources , -.BR %%IncludeResource , -.BR %%BeginResource +.BR %%Document\%Needed\%Resources , +.BR %%Document\%Supplied\%Resources , +.BR %%Include\%Resource , +.BR %%Begin\%Resource , and -.BR %%EndResource +.BR %%End\%Resource comments (or possibly the old -.BR %%DocumentNeededFonts , -.BR %%DocumentSuppliedFonts , -.BR %%IncludeFont , -.BR %%BeginFont +.BR %%Document\%Needed\%Fonts , +.BR %%Document\%Supplied\%Fonts , +.BR %%Include\%Font , +.BR %%Begin\%Font , and -.BR %%EndFont +.BR %%End\%Font comments) should be used. . @@ -1104,10 +1097,11 @@ Temporary file. .BR afmtodit (@MAN1EXT@), .BR groff (@MAN1EXT@), .BR @g@troff (@MAN1EXT@), -.BR psbb (@MAN1EXT@), +.BR pfbtops (@MAN1EXT@), .BR groff_out (@MAN5EXT@), .BR groff_font (@MAN5EXT@), -.BR groff_char (@MAN7EXT@) +.BR groff_char (@MAN7EXT@), +.BR groff_tmac (@MAN5EXT@) . .\" Local Variables: .\" mode: nroff diff --git a/contrib/groff/src/devices/grops/ps.cpp b/contrib/groff/src/devices/grops/ps.cpp new file mode 100644 index 0000000..f1c094b --- /dev/null +++ b/contrib/groff/src/devices/grops/ps.cpp @@ -0,0 +1,1869 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003 + 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* + * PostScript documentation: + * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf + * http://partners.adobe.com/asn/developer/pdfs/tn/5001.DSC_Spec.pdf + */ + +#include "driver.h" +#include "stringclass.h" +#include "cset.h" +#include "nonposix.h" +#include "paper.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; + +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, "%.3g", d); + 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_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) / 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() +{ + a_delete encoding; + a_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() +{ + a_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; + int space_char_index; + 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 c); + int set_encoding_index(ps_font *); + subencoding *set_subencoding(font *, int, 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 *c, int fill = 0); + + const char *media_name(); + int media_width(); + int media_height(); + void media_set(); + +public: + ps_printer(double); + ~ps_printer(); + void set_char(int i, font *f, const environment *env, int w, + const char *name); + 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); + 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("horizontal resolution must be 1"); + if (font::vert != 1) + fatal("vertical resolution must be 1"); + if (font::res % (font::sizescale*72) != 0) + fatal("res must be a multiple of 72*sizescale"); + int r = font::res; + int point = 0; + while (r % 10 == 0) { + r /= 10; + point++; + } + res = r; + out.set_fixed_point(point); + space_char_index = font::name_to_index("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, int i, unsigned char *codep) +{ + unsigned int idx = f->get_code(i); + *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(i); + 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, next_subencoding_index); + sub->subfont = tem; + } + return sub->subfont; +} + +void ps_printer::set_char(int i, font *f, const environment *env, int w, + const char *) +{ + if (i == space_char_index || invis_count > 0) + return; + unsigned char code; + subencoding *sub = set_subencoding(f, i, &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_char_index)) { + sbuf_space_code = f->get_code(space_char_index); + 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; + 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++; + } + a_delete 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]); + a_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); + } + } + a_delete done_encoding; +} + +void ps_printer::encode_subfont(subencoding *sub) +{ + assert(sub->glyphs != 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_char_index, 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 size is defined as 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 = time(0); + 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 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) { + (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) +{ + flush_sbuf(); + while (csspace(*arg)) + arg++; + if (*arg == '\0') { + error("missing argument to X exec command"); + return; + } + if (!check_line_lengths(arg)) { + error("lines in X exec command must not be more than 255 characters long"); + return; + } + 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) +{ + flush_sbuf(); + 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 *) +{ + flush_sbuf(); + while (csspace(*arg)) + arg++; + if (!check_line_lengths(arg)) { + error("lines in X def command must not be more than 255 characters long"); + return; + } + 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 *) +{ + flush_sbuf(); + 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)) { + error("lines in X mdef command must not be more than 255 characters long"); + return; + } + defs += arg; + if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n') + defs += '\n'; + ndefs += n; +} + +void ps_printer::do_import(char *arg, const environment *env) +{ + flush_sbuf(); + 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 indicators 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:glmp: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 `%s'", optarg); + ncopies = 1; + } + break; + case 'F': + font::command_line_font_dir(optarg); + break; + case 'g': + guess_flag = 1; + 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("invalid custom paper size `%1' ignored", 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("bad linewidth `%1'", 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 [-glmv] [-b n] [-c n] [-w n] [-P prologue] [-F dir] [files ...]\n", + program_name); +} diff --git a/contrib/groff/src/devices/grops/ps.h b/contrib/groff/src/devices/grops/ps.h index 0e149fc..a4b4169 100644 --- a/contrib/groff/src/devices/grops/ps.h +++ b/contrib/groff/src/devices/grops/ps.h @@ -1,5 +1,6 @@ // -*- C++ -*- -/* Copyright (C) 1989, 1990, 1991, 1992, 2002 Free Software Foundation, Inc. +/* Copyright (C) 1989, 1990, 1991, 1992, 2002, 2003 + Free Software Foundation, Inc. Written by James Clark (jjc@jclark.com) This file is part of groff. @@ -119,5 +120,6 @@ enum { NO_SETUP_SECTION = 01, STRIP_PERCENT_BANG = 02, STRIP_STRUCTURE_COMMENTS = 04, - USE_PS_ADOBE_2_0 = 010 + USE_PS_ADOBE_2_0 = 010, + NO_PAPERSIZE = 020 }; diff --git a/contrib/groff/src/devices/grops/psrm.cpp b/contrib/groff/src/devices/grops/psrm.cpp new file mode 100644 index 0000000..ab94ffe --- /dev/null +++ b/contrib/groff/src/devices/grops/psrm.cpp @@ -0,0 +1,1178 @@ +// -*- C++ -*- +/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003 + 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#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]); + +const char *resource_table[] = { + "font", + "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() +{ + a_delete 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]; + } + a_delete head; + a_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("can't find `%1'", prologue); + fputs("%%BeginResource: ", outfp); + procset_resource->print_type_and_name(outfp); + putc('\n', outfp); + process_file(-1, fp, path, outfp); + fclose(fp); + a_delete 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; + FILE *fp = 0; + if (r->filename != 0) { + if (r->type == RESOURCE_FONT) { + fp = font::open_file(r->filename, &path); + if (!fp) { + error("can't find `%1'", r->filename); + a_delete r->filename; + r->filename = 0; + } + } + else { + errno = 0; + fp = fopen(r->filename, "r"); + if (!fp) { + error("can't open `%1': %2", r->filename, strerror(errno)); + a_delete r->filename; + r->filename = 0; + } + 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) + a_delete 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) + && memcmp(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(c, 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 (ie 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 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; + FILE *fp = font::open_file("download", &path); + if (!fp) + fatal("can't find `download'"); + char buf[512]; + int lineno = 0; + while (fgets(buf, sizeof(buf), fp)) { + lineno++; + char *p = strtok(buf, " \t\r\n"); + if (p == 0 || *p == '#') + continue; + char *q = strtok(0, " \t\r\n"); + if (!q) + fatal_with_file_and_line(path, lineno, "missing filename"); + lookup_font(p)->filename = strsave(q); + } + a_delete 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); +} diff --git a/contrib/groff/src/devices/grotty/Makefile.sub b/contrib/groff/src/devices/grotty/Makefile.sub index 8802674..2ec77a5 100644 --- a/contrib/groff/src/devices/grotty/Makefile.sub +++ b/contrib/groff/src/devices/grotty/Makefile.sub @@ -3,4 +3,4 @@ MAN1=grotty.n XLIBS=$(LIBDRIVER) $(LIBGROFF) MLIB=$(LIBM) OBJS=tty.$(OBJEXT) -CCSRCS=$(srcdir)/tty.cc +CCSRCS=$(srcdir)/tty.cpp diff --git a/contrib/groff/src/devices/grotty/tty.cpp b/contrib/groff/src/devices/grotty/tty.cpp new file mode 100644 index 0000000..812ebf8 --- /dev/null +++ b/contrib/groff/src/devices/grotty/tty.cpp @@ -0,0 +1,815 @@ +// -*- C++ -*- +/* Copyright (C) 1989-2000, 2001, 2002, 2003 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 2, 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 groff; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include "driver.h" +#include "device.h" +#include "ptable.h" + +declare_ptable(char) +implement_ptable(char) + +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 + +static int horizontal_tab_flag = 0; +static int form_feed_flag = 0; +static int bold_flag = 1; +static int underline_flag = 1; +static int overstrike_flag = 1; +static int draw_flag = 1; +static int italic_flag = 0; +static int reverse_flag = 0; +static int old_drawing_scheme = 0; + +enum { + UNDERLINE_MODE = 0x01, + BOLD_MODE = 0x02, + VDRAW_MODE = 0x04, + HDRAW_MODE = 0x08, + CU_MODE = 0x10, + COLOR_CHANGE = 0x20 +}; + +// Mode to use for bold-underlining. +static unsigned char bold_underline_mode = BOLD_MODE|UNDERLINE_MODE; + +#ifndef IS_EBCDIC_HOST +#define CSI "\033[" +#else +#define CSI "\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 bachground color, respectively; thus we 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 = int(n & (BOLD_MODE|UNDERLINE_MODE)); + if (!underline_flag) + f->mode &= ~UNDERLINE_MODE; + if (!bold_flag) + f->mode &= ~BOLD_MODE; + if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE)) + f->mode = (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 glyph { + static glyph *free_list; +public: + glyph *next; + short hpos; + unsigned int code; + unsigned char mode; + char back_color_idx; + char fore_color_idx; + void *operator new(size_t); + void operator delete(void *); + inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); } + inline int order() { + return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); } +}; + +glyph *glyph::free_list = 0; + +void *glyph::operator new(size_t) +{ + if (!free_list) { + const int BLOCK = 1024; + free_list = (glyph *)new char[sizeof(glyph) * BLOCK]; + for (int i = 0; i < BLOCK - 1; i++) + free_list[i].next = free_list + i + 1; + free_list[BLOCK - 1].next = 0; + } + glyph *p = free_list; + free_list = free_list->next; + p->next = 0; + return p; +} + +void glyph::operator delete(void *p) +{ + if (p) { + ((glyph *)p)->next = free_list; + free_list = (glyph *)p; + } +} + +class tty_printer : public printer { + int is_utf8; + glyph **lines; + int nlines; + int cached_v; + int cached_vpos; + char curr_fore_idx; + char curr_back_idx; + int is_underline; + int is_bold; + int cu_flag; + PTABLE(char) tty_colors; + void make_underline(); + void make_bold(unsigned int); + char color_to_idx(color *col); + void add_char(unsigned int, int, int, color *, color *, unsigned char); + char *make_rgb_string(unsigned int, unsigned int, unsigned int); + int tty_color(unsigned int, unsigned int, unsigned int, char *, + char = DEFAULT_COLOR_IDX); +public: + tty_printer(const char *device); + ~tty_printer(); + void set_char(int, font *, const environment *, int, const char *name); + void draw(int code, int *p, int np, const environment *env); + void special(char *arg, const environment *env, char type); + void change_color(const environment * const env); + void change_fill_color(const environment * const env); + void put_char(unsigned int); + void put_color(char, int); + void begin_page(int) { } + void end_page(int page_length); + 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; +} + +int tty_printer::tty_color(unsigned int r, + unsigned int g, + unsigned int b, char *idx, char value) +{ + int unknown_color = 0; + char *s = make_rgb_string(r, g, b); + char *i = tty_colors.lookup(s); + if (!i) { + unknown_color = 1; + i = new char[1]; + *i = value; + tty_colors.define(s, i); + } + *idx = *i; + a_delete s; + return unknown_color; +} + +tty_printer::tty_printer(const char *device) : cached_v(0) +{ + is_utf8 = !strcmp(device, "utf8"); + char 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 glyph *[nlines]; + for (int i = 0; i < nlines; i++) + lines[i] = 0; + cu_flag = 0; +} + +tty_printer::~tty_printer() +{ + a_delete lines; +} + +void tty_printer::make_underline() +{ + if (old_drawing_scheme) { + putchar('_'); + putchar('\b'); + } + else { + if (!is_underline) { + if (italic_flag) + putstring(SGR_ITALIC); + else if (reverse_flag) + putstring(SGR_REVERSE); + else + putstring(SGR_UNDERLINE); + } + is_underline = 1; + } +} + +void tty_printer::make_bold(unsigned int c) +{ + if (old_drawing_scheme) { + put_char(c); + putchar('\b'); + } + else { + if (!is_bold) + putstring(SGR_BOLD); + is_bold = 1; + } +} + +char 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); + char idx; + if (tty_color(r, g, b, &idx)) { + char *s = col->print_color(); + error("Unknown color (%1) mapped to default", s); + a_delete s; + } + return idx; +} + +void tty_printer::set_char(int i, font *f, const environment *env, + int w, const char *) +{ + if (w != font::hor) + fatal("width of character not equal to horizontal resolution"); + add_char(f->get_code(i), + env->hpos, env->vpos, + env->col, env->fill, + ((tty_font *)f)->get_mode()); +} + +void tty_printer::add_char(unsigned int c, + 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 resolution"); +#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 resolution"); + vpos = v / font::vert; + if (vpos > nlines) { + glyph **old_lines = lines; + lines = new glyph *[vpos + 1]; + memcpy(lines, old_lines, nlines * sizeof(glyph *)); + for (int i = nlines; i <= vpos; i++) + lines[i] = 0; + a_delete old_lines; + nlines = vpos + 1; + } + // Note that the first output line corresponds to groff + // position font::vert. + if (vpos <= 0) { + error("character above first line discarded"); + return; + } + cached_v = v; + cached_vpos = vpos; + } + glyph *g = new glyph; + 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. + + 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::special(char *arg, const environment *env, char type) +{ + if (type == 'u') { + add_char(*arg - '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, "sgr", p - command) == 0) { + for (; *p == ' ' || *p == '\n'; p++) + ; + int n; + if (*p != '\0' && sscanf(p, "%d", &n) == 1 && n == 0) + old_drawing_scheme = 1; + else + old_drawing_scheme = 0; + } +} + +void tty_printer::change_color(const environment * const env) +{ + add_char(0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE); +} + +void tty_printer::change_fill_color(const environment * const env) +{ + add_char(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 (code != 'l' || !draw_flag) + return; + if (np != 2) { + error("2 arguments required for line"); + return; + } + if (p[0] == 0) { + // vertical line + int v = env->vpos; + int len = p[1]; + if (len < 0) { + v += len; + len = -len; + } + while (len >= 0) { + add_char('|', env->hpos, v, env->col, env->fill, VDRAW_MODE); + len -= font::vert; + v += font::vert; + } + } + if (p[1] == 0) { + // horizontal line + int h = env->hpos; + int len = p[0]; + if (len < 0) { + h += len; + len = -len; + } + while (len >= 0) { + add_char('-', h, env->vpos, env->col, env->fill, HDRAW_MODE); + len -= font::hor; + h += font::hor; + } + } +} + +void tty_printer::put_char(unsigned int wc) +{ + if (is_utf8 && 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(char color_index, int back) +{ + if (color_index == DEFAULT_COLOR_IDX) { + putstring(SGR_DEFAULT); + // set bold and underline again + if (is_bold) + putstring(SGR_BOLD); + if (is_underline) { + if (italic_flag) + putstring(SGR_ITALIC); + else if (reverse_flag) + 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'); + } +} + +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 resolution"); + 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]) { + 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++) { + glyph *p = lines[i]; + lines[i] = 0; + glyph *g = 0; + while (p) { + glyph *tem = p->next; + p->next = g; + g = p; + p = tem; + } + int hpos = 0; + glyph *nextp; + curr_fore_idx = DEFAULT_COLOR_IDX; + curr_back_idx = DEFAULT_COLOR_IDX; + is_underline = 0; + is_bold = 0; + for (p = g; p; delete p, p = nextp) { + nextp = p->next; + if (p->mode & CU_MODE) { + cu_flag = p->code; + continue; + } + if (nextp && p->hpos == nextp->hpos) { + if (p->draw_mode() == HDRAW_MODE && + nextp->draw_mode() == VDRAW_MODE) { + nextp->code = '+'; + continue; + } + if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode()) { + nextp->code = p->code; + continue; + } + if (!overstrike_flag) + continue; + } + if (hpos > p->hpos) { + do { + putchar('\b'); + hpos--; + } while (hpos > p->hpos); + } + else { + if (horizontal_tab_flag) { + for (;;) { + int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH; + if (next_tab_pos > p->hpos) + break; + if (cu_flag) + make_underline(); + else if (!old_drawing_scheme && is_underline) { + if (italic_flag) + putstring(SGR_NO_ITALIC); + else if (reverse_flag) + putstring(SGR_NO_REVERSE); + else + putstring(SGR_NO_UNDERLINE); + is_underline = 0; + } + putchar('\t'); + hpos = next_tab_pos; + } + } + for (; hpos < p->hpos; hpos++) { + if (cu_flag) + make_underline(); + else if (!old_drawing_scheme && is_underline) { + if (italic_flag) + putstring(SGR_NO_ITALIC); + else if (reverse_flag) + putstring(SGR_NO_REVERSE); + else + putstring(SGR_NO_UNDERLINE); + is_underline = 0; + } + putchar(' '); + } + } + assert(hpos == p->hpos); + if (p->mode & COLOR_CHANGE) { + if (!old_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(); + else if (!old_drawing_scheme && is_underline) { + if (italic_flag) + putstring(SGR_NO_ITALIC); + else if (reverse_flag) + putstring(SGR_NO_REVERSE); + else + putstring(SGR_NO_UNDERLINE); + is_underline = 0; + } + if (p->mode & BOLD_MODE) + make_bold(p->code); + else if (!old_drawing_scheme && is_bold) { + putstring(SGR_NO_BOLD); + is_bold = 0; + } + if (!old_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++; + } + if (!old_drawing_scheme + && (is_bold || is_underline + || curr_fore_idx != DEFAULT_COLOR_IDX + || curr_back_idx != DEFAULT_COLOR_IDX)) + putstring(SGR_DEFAULT); + putchar('\n'); + } + if (form_feed_flag) { + 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(device); +} + +static void usage(FILE *stream); + +int main(int argc, char **argv) +{ + program_name = argv[0]; + static char stderr_buf[BUFSIZ]; + if (getenv("GROFF_NO_SGR")) + old_drawing_scheme = 1; + 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, "bBcdfF:hioruUv", long_options, NULL)) + != EOF) + switch(c) { + case 'v': + printf("GNU grotty (groff) version %s\n", Version_string); + exit(0); + break; + case 'i': + // Use italic font instead of underlining. + italic_flag = 1; + break; + case 'b': + // Do not embolden by overstriking. + bold_flag = 0; + break; + case 'c': + // Use old scheme for emboldening and underline. + old_drawing_scheme = 1; + break; + case 'u': + // Do not underline. + underline_flag = 0; + break; + case 'o': + // Do not overstrike (other than emboldening and underlining). + overstrike_flag = 0; + break; + case 'r': + // Use reverse mode instead of underlining. + reverse_flag = 1; + break; + case 'B': + // Do bold-underlining as bold. + bold_underline_mode = BOLD_MODE; + break; + case 'U': + // Do bold-underlining as underlining. + bold_underline_mode = UNDERLINE_MODE; + break; + case 'h': + // Use horizontal tabs. + horizontal_tab_flag = 1; + break; + case 'f': + form_feed_flag = 1; + break; + case 'F': + font::command_line_font_dir(optarg); + break; + case 'd': + // Ignore \D commands. + draw_flag = 0; + break; + case CHAR_MAX + 1: // --help + usage(stdout); + exit(0); + break; + case '?': + usage(stderr); + exit(1); + break; + default: + assert(0); + } + if (old_drawing_scheme) { + italic_flag = 0; + reverse_flag = 0; + } + else { + bold_underline_mode = BOLD_MODE|UNDERLINE_MODE; + bold_flag = 1; + underline_flag = 1; + } + 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 [-bBcdfhioruUv] [-F dir] [files ...]\n", + program_name); +} |