summaryrefslogtreecommitdiffstats
path: root/src/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/grodvi/dvi.cpp988
-rw-r--r--src/devices/grodvi/grodvi.1.man633
-rw-r--r--src/devices/grodvi/grodvi.am32
-rw-r--r--src/devices/grohtml/grohtml.1.man731
-rw-r--r--src/devices/grohtml/grohtml.am40
-rw-r--r--src/devices/grohtml/html-table.cpp848
-rw-r--r--src/devices/grohtml/html-table.h133
-rw-r--r--src/devices/grohtml/html-text.cpp1056
-rw-r--r--src/devices/grohtml/html-text.h138
-rw-r--r--src/devices/grohtml/html.h97
-rw-r--r--src/devices/grohtml/output.cpp363
-rw-r--r--src/devices/grohtml/post-html.cpp5684
-rw-r--r--src/devices/grolbp/charset.h89
-rw-r--r--src/devices/grolbp/grolbp.1.man504
-rw-r--r--src/devices/grolbp/grolbp.am36
-rw-r--r--src/devices/grolbp/lbp.cpp738
-rw-r--r--src/devices/grolbp/lbp.h544
-rw-r--r--src/devices/grolj4/grolj4.1.man896
-rw-r--r--src/devices/grolj4/grolj4.am32
-rw-r--r--src/devices/grolj4/lj4.cpp715
-rw-r--r--src/devices/gropdf/TODO31
-rw-r--r--src/devices/gropdf/gropdf.1.man1845
-rw-r--r--src/devices/gropdf/gropdf.am58
-rw-r--r--src/devices/gropdf/gropdf.pl3928
-rw-r--r--src/devices/gropdf/pdfmom.1.man229
-rw-r--r--src/devices/gropdf/pdfmom.pl150
-rw-r--r--src/devices/grops/TODO24
-rw-r--r--src/devices/grops/grops.1.man1831
-rw-r--r--src/devices/grops/grops.am38
-rw-r--r--src/devices/grops/ps.cpp1894
-rw-r--r--src/devices/grops/ps.h129
-rw-r--r--src/devices/grops/psfig.diff106
-rw-r--r--src/devices/grops/psrm.cpp1189
-rw-r--r--src/devices/grotty/TODO3
-rw-r--r--src/devices/grotty/grotty.1.man810
-rw-r--r--src/devices/grotty/grotty.am39
-rwxr-xr-xsrc/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh205
-rwxr-xr-xsrc/devices/grotty/tests/osc8_works.sh119
-rw-r--r--src/devices/grotty/tty.cpp1043
-rw-r--r--src/devices/xditview/ChangeLog556
-rw-r--r--src/devices/xditview/DESC.in9
-rw-r--r--src/devices/xditview/Dvi.c603
-rw-r--r--src/devices/xditview/Dvi.h46
-rw-r--r--src/devices/xditview/DviP.h235
-rw-r--r--src/devices/xditview/FontMap-X1117
-rw-r--r--src/devices/xditview/GXditview-color.ad15
-rw-r--r--src/devices/xditview/GXditview.ad71
-rw-r--r--src/devices/xditview/Menu.h46
-rw-r--r--src/devices/xditview/README13
-rw-r--r--src/devices/xditview/TODO21
-rw-r--r--src/devices/xditview/ad2c64
-rw-r--r--src/devices/xditview/device.c565
-rw-r--r--src/devices/xditview/device.h21
-rw-r--r--src/devices/xditview/draw.c709
-rw-r--r--src/devices/xditview/draw.h18
-rw-r--r--src/devices/xditview/font.c446
-rw-r--r--src/devices/xditview/font.h6
-rw-r--r--src/devices/xditview/gray1.bm4
-rw-r--r--src/devices/xditview/gray2.bm4
-rw-r--r--src/devices/xditview/gray3.bm4
-rw-r--r--src/devices/xditview/gray4.bm4
-rw-r--r--src/devices/xditview/gray5.bm4
-rw-r--r--src/devices/xditview/gray6.bm4
-rw-r--r--src/devices/xditview/gray7.bm4
-rw-r--r--src/devices/xditview/gray8.bm4
-rw-r--r--src/devices/xditview/gxditview.1.man815
-rw-r--r--src/devices/xditview/lex.c100
-rw-r--r--src/devices/xditview/lex.h1
-rw-r--r--src/devices/xditview/page.c86
-rw-r--r--src/devices/xditview/page.h5
-rw-r--r--src/devices/xditview/parse.c343
-rw-r--r--src/devices/xditview/parse.h1
-rw-r--r--src/devices/xditview/xdit.bm14
-rw-r--r--src/devices/xditview/xdit_mask.bm14
-rw-r--r--src/devices/xditview/xditview.am130
-rw-r--r--src/devices/xditview/xditview.c684
76 files changed, 33654 insertions, 0 deletions
diff --git a/src/devices/grodvi/dvi.cpp b/src/devices/grodvi/dvi.cpp
new file mode 100644
index 0000000..f9e8a57
--- /dev/null
+++ b/src/devices/grodvi/dvi.cpp
@@ -0,0 +1,988 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <assert.h>
+
+#include "driver.h"
+#include "nonposix.h"
+#include "paper.h"
+
+extern "C" const char *Version_string;
+
+#define DEFAULT_LINEWIDTH 40
+static int linewidth = DEFAULT_LINEWIDTH;
+
+static int draw_flag = 1;
+
+static int landscape_flag = 0;
+static double user_paper_length = 0;
+static double user_paper_width = 0;
+
+/* These values were chosen because:
+
+(MULTIPLIER*SIZESCALE)/(RES*UNITWIDTH) == 1/(2^20 * 72.27)
+
+and 57816 is an exact multiple of both 72.27*SIZESCALE and 72.
+
+The width in the groff font file is the product of MULTIPLIER and the
+width in the tfm file. */
+
+#define RES 57816
+#define RES_7227 (RES/7227)
+#define UNITWIDTH 131072
+#define SIZESCALE 100
+#define MULTIPLIER 1
+
+class dvi_font : public font {
+ dvi_font(const char *);
+public:
+ int checksum;
+ int design_size;
+ ~dvi_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static dvi_font *load_dvi_font(const char *);
+};
+
+dvi_font *dvi_font::load_dvi_font(const char *s)
+{
+ dvi_font *f = new dvi_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+dvi_font::dvi_font(const char *nm)
+: font(nm), checksum(0), design_size(0)
+{
+}
+
+dvi_font::~dvi_font()
+{
+}
+
+void dvi_font::handle_unknown_font_command(const char *command,
+ const char *arg,
+ const char *filename, int lineno)
+{
+ char *ptr;
+ if (strcmp(command, "checksum") == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'checksum' command requires an argument");
+ checksum = int(strtol(arg, &ptr, 10));
+ if (checksum == 0 && ptr == arg) {
+ fatal_with_file_and_line(filename, lineno, "bad checksum");
+ }
+ }
+ else if (strcmp(command, "designsize") == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'designsize' command requires an argument");
+ design_size = int(strtol(arg, &ptr, 10));
+ if (design_size == 0 && ptr == arg) {
+ fatal_with_file_and_line(filename, lineno, "bad design size");
+ }
+ }
+}
+
+#define FONTS_MAX 256
+
+struct output_font {
+ dvi_font *f;
+ int point_size;
+ output_font() : f(0) { }
+};
+
+class dvi_printer : public printer {
+ FILE *fp;
+ int max_drift;
+ int byte_count;
+ int last_bop;
+ int page_count;
+ int cur_h;
+ int cur_v;
+ int end_h;
+ int max_h;
+ int max_v;
+ output_font output_font_table[FONTS_MAX];
+ font *cur_font;
+ int cur_point_size;
+ color cur_color;
+ int pushed;
+ int pushed_h;
+ int pushed_v;
+ int have_pushed;
+ void preamble();
+ void postamble();
+ void define_font(int);
+ void set_font(int);
+ void possibly_begin_line();
+ void set_color(color *);
+protected:
+ enum {
+ id_byte = 2,
+ set1 = 128,
+ put1 = 133,
+ put_rule = 137,
+ bop = 139,
+ eop = 140,
+ push = 141,
+ pop = 142,
+ right1 = 143,
+ down1 = 157,
+ fnt_num_0 = 171,
+ fnt1 = 235,
+ xxx1 = 239,
+ fnt_def1 = 243,
+ pre = 247,
+ post = 248,
+ post_post = 249,
+ filler = 223
+ };
+ int line_thickness;
+
+ void out1(int);
+ void out2(int);
+ void out3(int);
+ void out4(int);
+ void moveto(int, int);
+ void out_string(const char *);
+ void out_signed(unsigned char, int);
+ void out_unsigned(unsigned char, int);
+ void do_special(const char *);
+public:
+ dvi_printer();
+ ~dvi_printer();
+ font *make_font(const char *);
+ void begin_page(int);
+ void end_page(int);
+ void set_char(glyph *, font *, const environment *, int, const char *);
+ void special(char *, const environment *, char);
+ void end_of_line();
+ void draw(int, int *, int, const environment *);
+};
+
+
+class draw_dvi_printer : public dvi_printer {
+ int output_pen_size;
+ void set_line_thickness(const environment *);
+ void fill_next(const environment *);
+public:
+ draw_dvi_printer();
+ ~draw_dvi_printer();
+ void draw(int code, int *p, int np, const environment *env);
+ void end_page(int);
+};
+
+dvi_printer::dvi_printer()
+: fp(stdout), byte_count(0), last_bop(-1), page_count(0), max_h(0), max_v(0),
+ cur_font(0), cur_point_size(-1), pushed(0), line_thickness(-1)
+{
+ if (font::res != RES)
+ fatal("resolution must be %1", RES);
+ if (font::unitwidth != UNITWIDTH)
+ fatal("unitwidth must be %1", UNITWIDTH);
+ if (font::hor != 1)
+ fatal("hor must be equal to 1");
+ if (font::vert != 1)
+ fatal("vert must be equal to 1");
+ if (font::sizescale != SIZESCALE)
+ fatal("sizescale must be equal to %1", SIZESCALE);
+ max_drift = font::res/1000; // this is fairly arbitrary
+ preamble();
+}
+
+dvi_printer::~dvi_printer()
+{
+ postamble();
+}
+
+
+draw_dvi_printer::draw_dvi_printer()
+: output_pen_size(-1)
+{
+}
+
+draw_dvi_printer::~draw_dvi_printer()
+{
+}
+
+
+void dvi_printer::out1(int n)
+{
+ byte_count += 1;
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out2(int n)
+{
+ byte_count += 2;
+ putc((n >> 8) & 0xff, fp);
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out3(int n)
+{
+ byte_count += 3;
+ putc((n >> 16) & 0xff, fp);
+ putc((n >> 8) & 0xff, fp);
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out4(int n)
+{
+ byte_count += 4;
+ putc((n >> 24) & 0xff, fp);
+ putc((n >> 16) & 0xff, fp);
+ putc((n >> 8) & 0xff, fp);
+ putc(n & 0xff, fp);
+}
+
+void dvi_printer::out_string(const char *s)
+{
+ out1(strlen(s));
+ while (*s != 0)
+ out1(*s++);
+}
+
+
+void dvi_printer::end_of_line()
+{
+ if (pushed) {
+ out1(pop);
+ pushed = 0;
+ cur_h = pushed_h;
+ cur_v = pushed_v;
+ }
+}
+
+void dvi_printer::possibly_begin_line()
+{
+ if (!pushed) {
+ have_pushed = pushed = 1;
+ pushed_h = cur_h;
+ pushed_v = cur_v;
+ out1(push);
+ }
+}
+
+int scale(int x, int z)
+{
+ int sw;
+ int a, b, c, d;
+ int alpha, beta;
+ alpha = 16*z; beta = 16;
+ while (z >= 040000000L) {
+ z /= 2; beta /= 2;
+ }
+ d = x & 255;
+ c = (x >> 8) & 255;
+ b = (x >> 16) & 255;
+ a = (x >> 24) & 255;
+ sw = (((((d * z) / 0400) + (c * z)) / 0400) + (b * z)) / beta;
+ if (a == 255)
+ sw -= alpha;
+ else
+ assert(a == 0);
+ return sw;
+}
+
+void dvi_printer::set_color(color *col)
+{
+ cur_color = *col;
+ char buf[256];
+ unsigned int components[4];
+ color_scheme cs = col->get_components(components);
+ switch (cs) {
+ case DEFAULT:
+ sprintf(buf, "color gray 0");
+ break;
+ case RGB:
+ sprintf(buf, "color rgb %.3g %.3g %.3g",
+ double(Red) / double(color::MAX_COLOR_VAL),
+ double(Green) / double(color::MAX_COLOR_VAL),
+ double(Blue) / double(color::MAX_COLOR_VAL));
+ break;
+ case CMY:
+ col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black);
+ // fall through
+ case CMYK:
+ sprintf(buf, "color cmyk %.3g %.3g %.3g %.3g",
+ double(Cyan) / double(color::MAX_COLOR_VAL),
+ double(Magenta) / double(color::MAX_COLOR_VAL),
+ double(Yellow) / double(color::MAX_COLOR_VAL),
+ double(Black) / double(color::MAX_COLOR_VAL));
+ break;
+ case GRAY:
+ sprintf(buf, "color gray %.3g",
+ double(Gray) / double(color::MAX_COLOR_VAL));
+ break;
+ }
+ do_special(buf);
+}
+
+void dvi_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ if (*env->col != cur_color)
+ set_color(env->col);
+ int code = f->get_code(g);
+ if (env->size != cur_point_size || f != cur_font) {
+ cur_font = f;
+ cur_point_size = env->size;
+ int i;
+ for (i = 0;; i++) {
+ if (i >= FONTS_MAX) {
+ fatal("too many output fonts required");
+ }
+ if (output_font_table[i].f == 0) {
+ output_font_table[i].f = (dvi_font *)cur_font;
+ output_font_table[i].point_size = cur_point_size;
+ define_font(i);
+ }
+ if (output_font_table[i].f == cur_font
+ && output_font_table[i].point_size == cur_point_size)
+ break;
+ }
+ set_font(i);
+ }
+ int distance = env->hpos - cur_h;
+ if (env->hpos != end_h && distance != 0) {
+ out_signed(right1, distance);
+ cur_h = env->hpos;
+ }
+ else if (distance > max_drift) {
+ out_signed(right1, distance - max_drift);
+ cur_h = env->hpos - max_drift;
+ }
+ else if (distance < -max_drift) {
+ out_signed(right1, distance + max_drift);
+ cur_h = env->hpos + max_drift;
+ }
+ if (env->vpos != cur_v) {
+ out_signed(down1, env->vpos - cur_v);
+ cur_v = env->vpos;
+ }
+ possibly_begin_line();
+ end_h = env->hpos + w;
+ cur_h += scale(f->get_width(g, UNITWIDTH) / MULTIPLIER,
+ cur_point_size * RES_7227);
+ if (cur_h > max_h)
+ max_h = cur_h;
+ if (cur_v > max_v)
+ max_v = cur_v;
+ if (code >= 0 && code <= 127)
+ out1(code);
+ else
+ out_unsigned(set1, code);
+}
+
+void dvi_printer::define_font(int i)
+{
+ out_unsigned(fnt_def1, i);
+ dvi_font *f = output_font_table[i].f;
+ out4(f->checksum);
+ out4(output_font_table[i].point_size*RES_7227);
+ out4(int((double(f->design_size)/(1<<20))*RES_7227*100 + .5));
+ const char *nm = f->get_internal_name();
+ out1(0);
+ out_string(nm);
+}
+
+void dvi_printer::set_font(int i)
+{
+ if (i >= 0 && i <= 63)
+ out1(fnt_num_0 + i);
+ else
+ out_unsigned(fnt1, i);
+}
+
+void dvi_printer::out_signed(unsigned char base, int param)
+{
+ if (-128 <= param && param < 128) {
+ out1(base);
+ out1(param);
+ }
+ else if (-32768 <= param && param < 32768) {
+ out1(base+1);
+ out2(param);
+ }
+ else if (-(1 << 23) <= param && param < (1 << 23)) {
+ out1(base+2);
+ out3(param);
+ }
+ else {
+ out1(base+3);
+ out4(param);
+ }
+}
+
+void dvi_printer::out_unsigned(unsigned char base, int param)
+{
+ if (param >= 0) {
+ if (param < 256) {
+ out1(base);
+ out1(param);
+ }
+ else if (param < 65536) {
+ out1(base+1);
+ out2(param);
+ }
+ else if (param < (1 << 24)) {
+ out1(base+2);
+ out3(param);
+ }
+ else {
+ out1(base+3);
+ out4(param);
+ }
+ }
+ else {
+ out1(base+3);
+ out4(param);
+ }
+}
+
+void dvi_printer::preamble()
+{
+ out1(pre);
+ out1(id_byte);
+ out4(254000);
+ out4(font::res);
+ out4(1000);
+ out1(0);
+}
+
+void dvi_printer::postamble()
+{
+ int tem = byte_count;
+ out1(post);
+ out4(last_bop);
+ out4(254000);
+ out4(font::res);
+ out4(1000);
+ out4(max_v);
+ out4(max_h);
+ out2(have_pushed); // stack depth
+ out2(page_count);
+ int i;
+ for (i = 0; i < FONTS_MAX && output_font_table[i].f != 0; i++)
+ define_font(i);
+ out1(post_post);
+ out4(tem);
+ out1(id_byte);
+ for (i = 0; i < 4 || byte_count % 4 != 0; i++)
+ out1(filler);
+}
+
+void dvi_printer::begin_page(int i)
+{
+ page_count++;
+ int tem = byte_count;
+ out1(bop);
+ out4(i);
+ for (int j = 1; j < 10; j++)
+ out4(0);
+ out4(last_bop);
+ last_bop = tem;
+ // By convention position (0,0) in a dvi file is placed at (1in, 1in).
+ cur_h = font::res;
+ cur_v = font::res;
+ end_h = 0;
+ if (page_count == 1) {
+ char buf[256];
+ // at least dvips uses this
+ double length = user_paper_length ? user_paper_length :
+ double(font::paperlength) / font::res;
+ double width = user_paper_width ? user_paper_width :
+ double(font::paperwidth) / font::res;
+ if (width > 0 && length > 0) {
+ sprintf(buf, "papersize=%.3fin,%.3fin",
+ landscape_flag ? length : width,
+ landscape_flag ? width : length);
+ do_special(buf);
+ }
+ }
+ if (cur_color != default_color)
+ set_color(&cur_color);
+}
+
+void dvi_printer::end_page(int)
+{
+ set_color(&default_color);
+ if (pushed)
+ end_of_line();
+ out1(eop);
+ cur_font = 0;
+}
+
+void draw_dvi_printer::end_page(int len)
+{
+ dvi_printer::end_page(len);
+ output_pen_size = -1;
+}
+
+void dvi_printer::do_special(const char *s)
+{
+ int len = strlen(s);
+ if (len == 0)
+ return;
+ possibly_begin_line();
+ out_unsigned(xxx1, len);
+ while (*s)
+ out1(*s++);
+}
+
+void dvi_printer::special(char *arg, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+ moveto(env->hpos, env->vpos);
+ do_special(arg);
+}
+
+void dvi_printer::moveto(int h, int v)
+{
+ if (h != cur_h) {
+ out_signed(right1, h - cur_h);
+ cur_h = h;
+ if (cur_h > max_h)
+ max_h = cur_h;
+ }
+ if (v != cur_v) {
+ out_signed(down1, v - cur_v);
+ cur_v = v;
+ if (cur_v > max_v)
+ max_v = cur_v;
+ }
+ end_h = 0;
+}
+
+void dvi_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if (code == 'l') {
+ int x = 0, y = 0;
+ int height = 0, width = 0;
+ int thickness;
+ if (line_thickness < 0)
+ thickness = env->size*RES_7227*linewidth/1000;
+ else if (line_thickness > 0)
+ thickness = line_thickness;
+ else
+ thickness = 1;
+ if (np != 2) {
+ error("2 arguments required for line");
+ }
+ else if (p[0] == 0) {
+ // vertical rule
+ if (p[1] > 0) {
+ x = env->hpos - thickness/2;
+ y = env->vpos + p[1] + thickness/2;
+ height = p[1] + thickness;
+ width = thickness;
+ }
+ else if (p[1] < 0) {
+ x = env->hpos - thickness/2;
+ y = env->vpos + thickness/2;
+ height = thickness - p[1];
+ width = thickness;
+ }
+ }
+ else if (p[1] == 0) {
+ if (p[0] > 0) {
+ x = env->hpos - thickness/2;
+ y = env->vpos + thickness/2;
+ height = thickness;
+ width = p[0] + thickness;
+ }
+ else if (p[0] < 0) {
+ x = env->hpos - p[0] - thickness/2;
+ y = env->vpos + thickness/2;
+ height = thickness;
+ width = thickness - p[0];
+ }
+ }
+ if (height != 0) {
+ moveto(x, y);
+ out1(put_rule);
+ out4(height);
+ out4(width);
+ }
+ }
+ else if (code == 't') {
+ if (np == 0) {
+ line_thickness = -1;
+ }
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2)
+ error("0 or 1 argument required for thickness");
+ else
+ line_thickness = p[0];
+ }
+ }
+ else if (code == 'R') {
+ if (np != 2)
+ error("2 arguments required for rule");
+ else if (p[0] != 0 || p[1] != 0) {
+ int dh = p[0];
+ int dv = p[1];
+ int oh = env->hpos;
+ int ov = env->vpos;
+ if (dv > 0) {
+ ov += dv;
+ dv = -dv;
+ }
+ if (dh < 0) {
+ oh += dh;
+ dh = -dh;
+ }
+ moveto(oh, ov);
+ out1(put_rule);
+ out4(-dv);
+ out4(dh);
+ }
+ }
+}
+
+// XXX Will this overflow?
+
+inline int milliinches(int n)
+{
+ return (n*1000 + font::res/2)/font::res;
+}
+
+void draw_dvi_printer::set_line_thickness(const environment *env)
+{
+ int desired_pen_size
+ = milliinches(line_thickness < 0
+ // Will this overflow?
+ ? env->size*RES_7227*linewidth/1000
+ : line_thickness);
+ if (desired_pen_size != output_pen_size) {
+ char buf[256];
+ sprintf(buf, "pn %d", desired_pen_size);
+ do_special(buf);
+ output_pen_size = desired_pen_size;
+ }
+}
+
+void draw_dvi_printer::fill_next(const environment *env)
+{
+ unsigned int g;
+ if (env->fill->is_default())
+ g = 0;
+ else {
+ // currently, only BW support
+ env->fill->get_gray(&g);
+ }
+ char buf[256];
+ sprintf(buf, "sh %.3g", 1 - double(g) / double(color::MAX_COLOR_VAL));
+ do_special(buf);
+}
+
+void draw_dvi_printer::draw(int code, int *p, int np, const environment *env)
+{
+ char buf[1024];
+ int fill_flag = 0;
+ switch (code) {
+ case 'C':
+ fill_flag = 1;
+ // fall through
+ case 'c':
+ {
+ // troff adds an extra argument to C
+ if (np != 1 && !(code == 'C' && np == 2)) {
+ error("1 argument required for circle");
+ break;
+ }
+ moveto(env->hpos+p[0]/2, env->vpos);
+ if (fill_flag)
+ fill_next(env);
+ else
+ set_line_thickness(env);
+ int rad;
+ rad = milliinches(p[0]/2);
+ sprintf(buf, "%s 0 0 %d %d 0 6.28319",
+ (fill_flag ? "ia" : "ar"),
+ rad,
+ rad);
+ do_special(buf);
+ break;
+ }
+ case 'l':
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ moveto(env->hpos, env->vpos);
+ set_line_thickness(env);
+ do_special("pa 0 0");
+ sprintf(buf, "pa %d %d", milliinches(p[0]), milliinches(p[1]));
+ do_special(buf);
+ do_special("fp");
+ break;
+ case 'E':
+ fill_flag = 1;
+ // fall through
+ case 'e':
+ if (np != 2) {
+ error("2 arguments required for ellipse");
+ break;
+ }
+ moveto(env->hpos+p[0]/2, env->vpos);
+ if (fill_flag)
+ fill_next(env);
+ else
+ set_line_thickness(env);
+ sprintf(buf, "%s 0 0 %d %d 0 6.28319",
+ (fill_flag ? "ia" : "ar"),
+ milliinches(p[0]/2),
+ milliinches(p[1]/2));
+ do_special(buf);
+ break;
+ case 'P':
+ fill_flag = 1;
+ // fall through
+ case 'p':
+ {
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ break;
+ }
+ moveto(env->hpos, env->vpos);
+ if (fill_flag)
+ fill_next(env);
+ else
+ set_line_thickness(env);
+ do_special("pa 0 0");
+ int h = 0, v = 0;
+ for (int i = 0; i < np; i += 2) {
+ h += p[i];
+ v += p[i+1];
+ sprintf(buf, "pa %d %d", milliinches(h), milliinches(v));
+ do_special(buf);
+ }
+ do_special("pa 0 0");
+ do_special(fill_flag ? "ip" : "fp");
+ break;
+ }
+ case '~':
+ {
+ if (np & 1) {
+ error("even number of arguments required for spline");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for spline");
+ break;
+ }
+ moveto(env->hpos, env->vpos);
+ set_line_thickness(env);
+ do_special("pa 0 0");
+ int h = 0, v = 0;
+ for (int i = 0; i < np; i += 2) {
+ h += p[i];
+ v += p[i+1];
+ sprintf(buf, "pa %d %d", milliinches(h), milliinches(v));
+ do_special(buf);
+ }
+ do_special("sp");
+ break;
+ }
+ case 'a':
+ {
+ if (np != 4) {
+ error("4 arguments required for arc");
+ break;
+ }
+ set_line_thickness(env);
+ double c[2];
+ if (adjust_arc_center(p, c)) {
+ int rad = milliinches(int(sqrt(c[0]*c[0] + c[1]*c[1]) + .5));
+ moveto(env->hpos + int(c[0]), env->vpos + int(c[1]));
+ double start = atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0]);
+ double end = atan2(-c[1], -c[0]);
+ if (end - start < 0)
+ start -= 2 * 3.14159265358;
+ sprintf(buf, "ar 0 0 %d %d %f %f", rad, rad, start, end);
+ do_special(buf);
+ }
+ else {
+ moveto(env->hpos, env->vpos);
+ do_special("pa 0 0");
+ sprintf(buf,
+ "pa %d %d",
+ milliinches(p[0] + p[2]),
+ milliinches(p[1] + p[3]));
+ do_special(buf);
+ do_special("fp");
+ }
+ break;
+ }
+ case 't':
+ {
+ if (np == 0) {
+ line_thickness = -1;
+ }
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ }
+ case 'R':
+ {
+ if (np != 2) {
+ error("2 arguments required for rule");
+ break;
+ }
+ int dh = p[0];
+ if (dh == 0)
+ break;
+ int dv = p[1];
+ if (dv == 0)
+ break;
+ int oh = env->hpos;
+ int ov = env->vpos;
+ if (dv > 0) {
+ ov += dv;
+ dv = -dv;
+ }
+ if (dh < 0) {
+ oh += dh;
+ dh = -dh;
+ }
+ moveto(oh, ov);
+ out1(put_rule);
+ out4(-dv);
+ out4(dh);
+ break;
+ }
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+}
+
+font *dvi_printer::make_font(const char *nm)
+{
+ return dvi_font::load_dvi_font(nm);
+}
+
+printer *make_printer()
+{
+ if (draw_flag)
+ return new draw_dvi_printer;
+ else
+ return new dvi_printer;
+}
+
+static void usage(FILE *stream);
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "dF:I:lp:vw:", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'd':
+ draw_flag = 0;
+ break;
+ case 'l':
+ landscape_flag = 1;
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'I':
+ // ignore include search path
+ break;
+ case 'p':
+ if (!font::scan_papersize(optarg, 0,
+ &user_paper_length, &user_paper_width))
+ error("ignoring invalid paper format '%1'", optarg);
+ break;
+ case 'v':
+ {
+ printf("GNU grodvi (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case 'w':
+ if (sscanf(optarg, "%d", &linewidth) != 1
+ || linewidth < 0 || linewidth > 1000) {
+ error("invalid line width '%1' ignored", optarg);
+ linewidth = DEFAULT_LINEWIDTH;
+ }
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ SET_BINARY(fileno(stdout));
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-dl] [-F dir] [-p paper-format] [-w n] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grodvi/grodvi.1.man b/src/devices/grodvi/grodvi.1.man
new file mode 100644
index 0000000..05cbe0d
--- /dev/null
+++ b/src/devices/grodvi/grodvi.1.man
@@ -0,0 +1,633 @@
+.TH grodvi @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grodvi \-
+.I groff
+output driver for TeX DVI format
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2020, 2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grodvi_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.ie t .ds tx T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el .ds tx TeX
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'dvi' .ft \\$1
+..
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grodvi
+.RB [ \-dl ]
+.RB [ \-F\~\c
+.IR dir ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-w\~\c
+.IR n ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grodvi
+.B \-\-help
+.YS
+.
+.
+.SY grodvi
+.B \-v
+.
+.SY grodvi
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+DVI output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into \*[tx] DVI format.
+.
+Normally,
+.I grodvi
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~dvi \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grodvi .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grodvi
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+The DVI file generated by
+.I grodvi
+can interpreted by any correctly written DVI driver.
+.
+.I troff \" generic
+drawing primitives are implemented using
+.I tpic
+version\~2 specials.
+.
+If the driver does not support these,
+.B \[rs]D
+escape sequences will not produce any output.
+.
+.
+.P
+Encapsulated PostScript (EPS) files can be easily included;
+use the
+.B PSPIC
+macro.
+.
+.I pspic.tmac
+is loaded automatically by
+.IR dvi.tmac .
+.
+See
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.P
+The default color used by the
+.B \[rs]m
+and
+.B \[rs]M
+escape sequences is black.
+.
+Currently,
+the stroke color for
+.B \[rs]D
+drawing escape sequences is black;
+fill color values are translated to gray.
+.
+.
+.P
+In
+.IR groff ,
+as in AT&T
+.IR troff , \" AT&T
+the
+.B \[rs]N
+escape sequence can be used to access any glyph in the current font by
+its position in the corresponding TFM file.
+.
+.
+.P
+By design,
+the DVI format doesn't care about the physical dimensions of the output
+medium.
+.
+Instead,
+.I grodvi
+emits the equivalent to \*[tx]'s
+.BI \%\[rs]special{\:\%papersize= width , length }
+on the first page;
+.I dvips
+(or another DVI driver)
+then sets the page size accordingly.
+.
+If either the page width or length is not positive,
+no
+.B \%papersize
+special is output.
+.
+.
+.P
+A device control escape sequence
+.BI \[rs]X\[aq] anything \[aq]
+is translated to the same DVI file instructions as would be produced by
+.BI \%\[rs]special{ anything }
+in \*[tx];
+.I anything
+cannot contain a newline.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I grodvi
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Fonts are grouped into families
+.B T
+and
+.B H
+having members in each style.
+.
+\[lq]CM\[rq] abbreviates \[lq]Computer Modern\[rq].
+.
+.
+.RS
+.TP
+.B TR
+.FT TR
+CM Roman (cmr10)
+.FT
+.
+.TQ
+.B TI
+.FT TI
+CM Text Italic (cmti10)
+.FT
+.
+.TQ
+.B TB
+.FT TB
+CM Bold Extended Roman (cmbx10)
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+CM Bold Extended Text Italic (cmbxti10)
+.FT
+.
+.TQ
+.B HR
+.FT HR
+CM Sans Serif (cmss10)
+.FT
+.
+.TQ
+.B HI
+.FT HI
+CM Slanted Sans Serif (cmssi10)
+.FT
+.
+.TQ
+.B HB
+.FT HB
+CM Sans Serif Bold Extended (cmssbx10)
+.FT
+.
+.TQ
+.B HBI
+.FT HBI
+CM Slanted Sans Serif Bold Extended (cmssbxo10)
+.FT
+.RE
+.
+.
+.LP
+The following fonts are not members of a family.
+.
+.
+.RS
+.TP
+.B CW
+.FT CW
+CM Typewriter Text (cmtt10)
+.FT
+.
+.TQ
+.B CWI
+.FT CWI
+CM Italic Typewriter Text (cmitt10)
+.FT
+.RE
+.
+.
+.P
+Special fonts include
+.B MI
+(cmmi10),
+.B S
+(cmsy10),
+.B EX
+(cmex10),
+.B SC
+(cmtex10,
+only for
+.BR CW ),
+and,
+perhaps surprisingly,
+.BR TR ,
+.BR TI ,
+and
+.BR CW ,
+.\" See font/devdvi/generate/Makefile for details.
+because \*[tx] places some glyphs in text fonts that
+.I troff \" generic
+generally does not.
+.
+For italic fonts,
+.B CWI
+is used instead of
+.BR CW .
+.
+.
+.P
+Finally,
+the symbol fonts of the American Mathematical Society are available as
+special fonts
+.B SA
+(msam10) and
+.B SB
+(msbm10).
+.
+They are are not mounted by default.
+.
+.
+.br
+.ne 2v
+.P
+The
+.I @g@troff
+option
+.B \-mec
+loads the
+.I ec.tmac
+macro file,
+employing the EC and TC fonts instead of CM.
+.
+These are designed similarly to the Computer Modern fonts;
+further,
+they provide Euro
+.B \[rs][Eu]
+and per mille
+.B \[rs][%0]
+glyphs.
+.
+.I ec.tmac
+must be loaded before any language-specific macro files because it does
+not set up the codes necessary for automatic hyphenation.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+Use
+.MR tfmtodit @MAN1EXT@
+to create
+.I groff
+font description files from TFM
+(\*[tx] font metrics)
+files.
+.
+The font description file should contain the following additional
+directives,
+which
+.I tfmtodit
+generates automatically.
+.
+.
+.TP
+.BI internalname\~ name
+The name of the TFM file
+(without the
+.I .tfm
+extension) is
+.IR name .
+.
+.
+.TP
+.BI checksum\~ n
+The checksum in the TFM file is
+.IR n .
+.
+.
+.TP
+.BI designsize\~ n
+The design size in the TFM file is
+.IR n .
+.
+.
+.\" ====================================================================
+.SS "Drawing commands"
+.\" ====================================================================
+.
+.I grodvi
+supports an additional drawing command.
+.
+.
+.TP
+.BI \[rs]D\[aq]R\~ "dh dv" \[aq]
+Draw a rule
+(solid black rectangle)
+with one corner at the drawing position,
+and the diagonally opposite corner at the drawing position
+.RI +( dh , dv ),
+which becomes the new drawing position afterward.
+.
+This command produces a rule in the DVI file and so can be printed even
+with a driver that does not support
+.I tpic
+specials,
+unlike the other
+.B \[rs]D
+commands.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-d
+Do not use
+.I tpic
+specials to implement drawing commands.
+.
+Horizontal and vertical lines are implemented by rules.
+.
+Other drawing commands are ignored.
+.
+.
+.TP
+.BI \-F\~ dir
+Prepend directory
+.RI dir /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR dvi .
+.
+.
+.TP
+.B \-l
+Use landscape orientation rather than portrait.
+.
+.
+.TP
+.BI \-p\~ paper-format
+Set physical dimensions of output medium,
+overriding the
+.BR \%papersize ,
+.BR \%paperlength ,
+and
+.B \%paperwidth
+directives in the
+.I DESC
+file.
+.
+.I paper-format
+can be any argument accepted by the
+.B \%papersize
+directive;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI \-w\~ n
+Draw rules (lines) with a thickness of
+.IR n \~thousandths
+of an em.
+.
+The default thickness is
+.B 40
+(0.04\~em).
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to search for
+.IR devdvi ,
+.IR grodvi 's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devdvi/\:DESC
+describes the
+.B dvi
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devdvi/ F
+describes the font known
+.RI as\~ F
+on device
+.BR dvi .
+.
+.
+.TP
+.I @MACRODIR@/\:dvi\:.tmac
+defines font mappings,
+special characters,
+and colors for use with the
+.B dvi
+output device.
+.
+It is automatically loaded by
+.I \%troffrc
+when the
+.B dvi
+output device is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:ec\:.tmac
+configures the
+.B dvi
+output device to use
+the EC and TC font families instead of CM
+(Computer Modern).
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+DVI files produced by
+.I grodvi
+use a different resolution
+(57,816 units per inch)
+from those produced by \*[tx].
+.
+Incorrectly written drivers which assume the resolution used by \*[tx],
+rather than using the resolution specified in the DVI file,
+will not work with
+.IR grodvi .
+.
+.
+.LP
+When using the
+.B \-d
+option with boxed tables,
+vertical and horizontal lines can sometimes protrude by one pixel.
+.
+This is a consequence of the way \*[tx] requires that the heights
+and widths of rules be rounded.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR https://\:texfaq\:.org/\:FAQ\-\:ECfonts
+\[lq]What are the EC fonts?\[rq]
+.UE ;
+\*[tx] FAQ: Frequently Asked Question List for \*[tx]
+.
+.
+.P
+.MR tfmtodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@ ,
+.MR groff_tmac @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm FT
+.rm tx
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grodvi_1_man_C]
+.do rr *groff_grodvi_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grodvi/grodvi.am b/src/devices/grodvi/grodvi.am
new file mode 100644
index 0000000..ce93359
--- /dev/null
+++ b/src/devices/grodvi/grodvi.am
@@ -0,0 +1,32 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grodvi
+grodvi_SOURCES = src/devices/grodvi/dvi.cpp
+grodvi_LDADD = \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a $(LIBM)
+man1_MANS += src/devices/grodvi/grodvi.1
+EXTRA_DIST += src/devices/grodvi/grodvi.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grohtml/grohtml.1.man b/src/devices/grohtml/grohtml.1.man
new file mode 100644
index 0000000..2243b47
--- /dev/null
+++ b/src/devices/grohtml/grohtml.1.man
@@ -0,0 +1,731 @@
+.TH grohtml @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grohtml, post\-grohtml, pre\-grohtml \-
+.I groff
+output driver for HTML
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1999-2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grohtml_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY pre\-grohtml
+.RB [ \-epV ]
+.RB [ \-a
+.IR anti-aliasing-text-bits ]
+.RB [ \-D
+.IR image-directory ]
+.RB [ \-F
+.IR font-directory ]
+.RB [ \-g
+.IR anti-aliasing-graphic-bits ]
+.RB [ \-i
+.IR resolution ]
+.RB [ \-I
+.IR image-stem ]
+.RB [ \-o
+.IR image-vertical-offset ]
+.RB [ \-x
+.IR html-dialect ]
+.I troff-command
+.I troff-argument
+\&.\|.\|.
+.YS
+.
+.
+.SY pre\-grohtml
+.B \-\-help
+.YS
+.
+.
+.SY pre\-grohtml
+.B \-v
+.
+.SY pre\-grohtml
+.B \-\-version
+.YS
+.
+.
+.SY post\-grohtml
+.RB [ \-bCGhlnrVy ]
+.RB [ \-F
+.IR font-directory ]
+.RB [ \-j
+.IR output-stem ]
+.RB [ \-s
+.IR base-point-size ]
+.RB [ \-S
+.IR heading-level ]
+.RB [ \-x
+.IR html-dialect ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY post\-grohtml
+.B \-\-help
+.YS
+.
+.
+.SY post\-grohtml
+.B \-v
+.
+.SY post\-grohtml
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+system's HTML support consists of a preprocessor,
+.IR \%pre\-grohtml ,
+and an output driver,
+.IR \%post\-grohtml ;
+together,
+they translate
+.MR roff @MAN7EXT@
+documents to HTML.
+.
+Because a preprocessor is (uniquely) required for this output driver,
+users should invoke
+.I \%grohtml
+via the
+.MR groff @MAN1EXT@
+command with the
+.B \-Thtml
+or
+.B \-Txhtml
+options.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR \%grohtml .
+.
+If no operands are given,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I \%grohtml
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+.I \%grohtml
+invokes
+.I groff
+twice.
+.
+In the first pass,
+the preprocessor
+.I \%pre\-grohtml
+renders
+pictures,
+equations,
+and tables as images in PostScript format using the
+.B ps
+output device.
+.
+In the second pass,
+the output driver
+.I \%post\-grohtml
+translates the output of
+.MR @g@troff @MAN1EXT@
+to HTML.
+.
+.
+.P
+.I \%grohtml
+writes output encoded in \%UTF-8 and has built-in HTML entities for all
+non-composite Unicode characters.
+.
+In spite of this,
+.I groff
+may issue warnings about unknown special characters if they can't be
+found during the first pass.
+.
+Such warnings can be safely ignored unless the special characters
+appear inside a table or equation.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I \%grohtml
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Fonts are grouped into families
+.B T
+and
+.B C
+having members in each style.
+.
+.
+.RS
+.TP
+.B TR
+Times roman
+.
+.TQ
+.B TI
+Times italic
+.
+.TQ
+.B TB
+Times bold
+.
+.TQ
+.B TBI
+Times bold-italic
+.
+.TQ
+.B CR
+Courier roman
+.
+.TQ
+.B CI
+Courier italic
+.
+.TQ
+.B CB
+Courier bold
+.
+.TQ
+.B CBI
+Courier bold-italic
+.RE
+.
+.
+.P
+A special font,
+.BR S ,
+is also provided to accommodate
+.I roff
+documents that expect it to always be available.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+The font description files used with
+.I \%grohtml
+expose the same glyph repertoire in their
+.B charset
+sections.
+.
+See
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS Dependencies
+.\" ====================================================================
+.
+.I \%pre\-grohtml
+generates an image whenever an
+.I @g@eqn
+equation,
+.I @g@tbl
+table,
+or
+.I @g@pic
+picture is encountered in the input.
+.
+.I \%grohtml
+therefore may run several commands as part of its operation.
+.
+These include the \%Netpbm tools
+.IR \%pnmcrop ,
+.IR \%pnmcut ,
+and
+.IR \%pnmtopng ;
+\%Ghostscript
+.RI ( gs );
+and the \%PSUtils tool
+.IR \%psselect .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-a \~anti-aliasing-text-bits
+Number of bits of antialiasing information to be used by text when
+generating PNG images.
+.
+The default
+.RB is\~ 4
+but
+.BR 0 ,
+.BR 1 ,
+and
+.B 2
+are also valid.
+.
+Your system's version of
+.I gs
+must support the
+.B \%\-dTextAlphaBits
+option in order to exploit antialiasing.
+.\" XXX: How antiquated are the ones that don't? Get rid of this?
+.
+A value
+.RB of\~ 0
+stops
+.I \%grohtml
+from issuing antialiasing commands to
+.IR gs .
+.
+.
+.TP
+.B \-b
+Initialize the background color to white.
+.
+.
+.TP
+.B \-C
+Suppress output of \[lq]CreationDate:\[rq] HTML comment.
+.
+.
+.TP
+.BI \-D \~image-directory
+Instruct
+.I \%grohtml
+to place all image files into directory
+.IR image-directory .
+.
+.
+.TP
+.B \-e
+Direct
+.I @g@eqn
+to produce MathML.
+.
+.
+.IP
+This option should not be manually specified;
+it is synthesized by
+.I groff
+depending on whether it was given the
+.B \-Thtml
+or
+.B \-Txhtml
+option.
+.
+.
+.TP
+.BI \-F \~font-directory
+Prepend directory
+.RI font-directory /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR html .
+.
+.
+.TP
+.BI \-g \~anti-aliasing-graphic-bits
+Number of bits of antialiasing information to be used by graphics when
+generating PNG images.
+.
+The default
+.RB is\~ 4
+but
+.BR 0 ,
+.BR 1 ,
+and
+.B 2
+are also valid.
+.
+Your system's version of
+.I gs
+must support the
+.B \%\-dGraphicAlphaBits
+option in order to exploit antialiasing.
+.\" XXX: How antiquated are the ones that don't? Get rid of this?
+.
+A value
+.RB of\~ 0
+stops
+.I \%grohtml
+from issuing antialiasing commands to
+.IR gs .
+.
+.
+.TP
+.B \-G
+Suppress output of \[lq]Creator:\[rq] HTML comment.
+.
+.
+.TP
+.B \-h
+Generate section headings by using HTML
+.B B
+elements and increasing the font size,
+rather than HTML
+.B H
+elements.
+.
+.
+.TP
+.BI \-i \~resolution
+Set the image resolution in pixels per inch;
+the default
+.RB is\~ 100 .
+.
+.
+.TP
+.BI \-I \~image-stem
+Determine the image file name stem.
+.
+If omitted,
+.I \%grohtml
+uses
+.IR \%grohtml\- XXXXX
+(where
+.I XXXXX
+is the process ID).
+.
+A dash is appended to the stem to separate it from the following image
+number.
+.
+.
+.TP
+.BI \-j \~output-stem
+Instruct
+.I \%grohtml
+to split the HTML output into multiple files.
+.
+Output is written to a new file at each section heading
+(but see option
+.B \-S
+below)
+named
+.IR output-stem\- n .html .
+.
+.
+.TP
+.B \-l
+Turn off the production of automatic section links at the top of the
+document.
+.
+.
+.TP
+.B \-n
+Generate simple heading anchors whenever a section/number heading is
+found.
+.
+Without the option the anchor value is the textual heading.
+.
+This can cause problems when a heading contains a \[lq]?\[rq] on older
+versions of some browsers.
+.
+This feature is automatically enabled if a heading contains an image.
+.
+.
+.TP
+.BI \-o \~image-vertical-offset
+Specify the vertical offset of images in points.
+.
+.
+.TP
+.B \-p
+Display page rendering progress to the standard error stream.
+.
+.I \%grohtml
+displays a page number only when an image is required.
+.
+.
+.TP
+.B \-r
+Turn off the automatic header and footer line
+(HTML rule).
+.
+.
+.TP
+.BI \-s \~base-type-size
+Set the document's base type size in points.
+.
+When this size is used in the source,
+it corresponds to the HTML base type size.
+.
+Every increase of two points in the source will produce a
+.RB \[lq] big \[rq]
+element,
+and conversely when a decrease of two points is seen,
+a
+.RB \[lq] small \[rq]
+element is emitted.
+.
+.
+.TP
+.BI \-S \~heading-level
+When splitting HTML output
+(see option
+.B \-j
+above),
+split at each nested heading level defined by
+.IR heading-level ,
+or higher).
+.
+The default is
+.BR 1 .
+.
+.
+.TP
+.B \-V
+Create an XHTML or HTML validator button at the bottom of each page of
+the document.
+.
+.
+.TP
+.BI \-x \~html-dialect
+Select HTML dialect.
+.
+Currently,
+.I html-dialect
+should be either the
+.RB digit\~ 4
+or the
+.RB letter\~ x ,
+which indicates whether
+.I \%grohtml
+should generate HTML\~4 or XHTML,
+respectively.
+.
+.
+.IP
+This option should not be manually specified;
+it is synthesized by
+.I groff
+depending on whether it was given the
+.B \-Thtml
+or
+.B \-Txhtml
+option.
+.
+.
+.TP
+.B \-y
+Produce a right-aligned
+.I groff
+signature at the end of the document
+(only if
+.B \-V
+is also specified).
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to search for
+.IR devhtml ,
+.IR grohtml 's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR ctime 3
+and recorded in an HTML comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devhtml/\:DESC
+describes the
+.B html
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devhtml/ F
+describes the font known
+.RI as\~ F
+on device
+.BR html .
+.
+.
+.TP
+.I @MACRODIR@/\:html\:.tmac
+defines font mappings,
+special characters,
+and colors for use with the
+.B html
+output device.
+.
+It is automatically loaded by
+.I \%troffrc
+when either of the
+.B html
+or
+.B xhtml
+output devices is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:html\-end\:.tmac
+finalizes setup of the
+.B html
+output device.
+.
+It is automatically loaded by
+.I \%troffrc\-end
+when either of the
+.B html
+or
+.B xhtml
+output devices is selected.
+.
+.
+.P
+.I \%grohtml
+uses temporary files.
+.
+See
+.MR groff @MAN1EXT@
+for details about where such files are created.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+.I \%grohtml
+is still beta code.
+.
+.
+.PP
+.I \%grohtml
+does not truly support hyphenation,
+but you can fool it into hyphenating long input lines,
+which can appear in HTML output with a hyphenated word followed by a
+space but no line break.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.\" IR afmtodit (@MAN1EXT@),
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.\" IR psbb (1), \" XXX: what is this?
+.\" IR groff_out (@MAN5EXT@),
+.\" IR groff_char (@MAN7EXT@),
+.MR groff_font @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grohtml_1_man_C]
+.do rr *groff_grohtml_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grohtml/grohtml.am b/src/devices/grohtml/grohtml.am
new file mode 100644
index 0000000..a87cad2
--- /dev/null
+++ b/src/devices/grohtml/grohtml.am
@@ -0,0 +1,40 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += post-grohtml
+post_grohtml_SOURCES = \
+ src/devices/grohtml/post-html.cpp \
+ src/devices/grohtml/html-table.cpp \
+ src/devices/grohtml/html-text.cpp \
+ src/devices/grohtml/output.cpp \
+ src/devices/grohtml/html.h \
+ src/devices/grohtml/html-text.h \
+ src/devices/grohtml/html-table.h
+
+post_grohtml_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grohtml/grohtml.1
+EXTRA_DIST += src/devices/grohtml/grohtml.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grohtml/html-table.cpp b/src/devices/grohtml/html-table.cpp
new file mode 100644
index 0000000..3d7dfcd
--- /dev/null
+++ b/src/devices/grohtml/html-table.cpp
@@ -0,0 +1,848 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.cpp
+ *
+ * html-table.h
+ *
+ * provides the methods necessary to handle indentation and tab
+ * positions using html tables.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "html-table.h"
+#include "ctype.h"
+#include "html.h"
+#include "html-text.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+extern html_dialect dialect;
+
+
+tabs::tabs ()
+ : tab(NULL)
+{
+}
+
+tabs::~tabs ()
+{
+ delete_list();
+}
+
+/*
+ * delete_list - frees the tab list and sets tab to NULL.
+ */
+
+void tabs::delete_list (void)
+{
+ tab_position *p = tab;
+ tab_position *q;
+
+ while (p != NULL) {
+ q = p;
+ p = p->next;
+ delete q;
+ }
+ tab = NULL;
+}
+
+void tabs::clear (void)
+{
+ delete_list();
+}
+
+/*
+ * compatible - returns TRUE if the tab stops in, s, do
+ * not conflict with the current tab stops.
+ * The new tab stops are _not_ placed into
+ * this class.
+ */
+
+int tabs::compatible (const char *s)
+{
+ char align;
+ int total=0;
+ tab_position *last = tab;
+
+ if (last == NULL)
+ return FALSE; // no tab stops defined
+
+ // move over tag name
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+
+ while (*s != (char)0 && last != NULL) {
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect alignment
+ align = *s;
+ // move over alignment
+ s++;
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect tab position
+ total = atoi(s);
+ // move over tab position
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+ if (last->alignment != align || last->position != total)
+ return FALSE;
+
+ last = last->next;
+ }
+ return TRUE;
+}
+
+/*
+ * init - scans the string, s, and initializes the tab stops.
+ */
+
+void tabs::init (const char *s)
+{
+ char align;
+ int total=0;
+ tab_position *last = NULL;
+
+ clear(); // remove any tab stops
+
+ // move over tag name
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+
+ while (*s != (char)0) {
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect alignment
+ align = *s;
+ // move over alignment
+ s++;
+ // move over white space
+ while ((*s != (char)0) && isspace(*s))
+ s++;
+ // collect tab position
+ total = atoi(s);
+ // move over tab position
+ while ((*s != (char)0) && !isspace(*s))
+ s++;
+ if (last == NULL) {
+ tab = new tab_position;
+ last = tab;
+ } else {
+ last->next = new tab_position;
+ last = last->next;
+ }
+ last->alignment = align;
+ last->position = total;
+ last->next = NULL;
+ }
+}
+
+/*
+ * check_init - define tab stops using, s, providing none already exist.
+ */
+
+void tabs::check_init (const char *s)
+{
+ if (tab == NULL)
+ init(s);
+}
+
+/*
+ * find_tab - returns the tab number corresponding to the position, pos.
+ */
+
+int tabs::find_tab (int pos)
+{
+ tab_position *p;
+ int i=0;
+
+ for (p = tab; p != NULL; p = p->next) {
+ i++;
+ if (p->position == pos)
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * get_tab_pos - returns the, nth, tab position
+ */
+
+int tabs::get_tab_pos (int n)
+{
+ tab_position *p;
+
+ n--;
+ for (p = tab; (p != NULL) && (n>0); p = p->next) {
+ n--;
+ if (n == 0)
+ return p->position;
+ }
+ return 0;
+}
+
+char tabs::get_tab_align (int n)
+{
+ tab_position *p;
+
+ n--;
+ for (p = tab; (p != NULL) && (n>0); p = p->next) {
+ n--;
+ if (n == 0)
+ return p->alignment;
+ }
+ return 'L';
+}
+
+/*
+ * dump_tab - display tab positions
+ */
+
+void tabs::dump_tabs (void)
+{
+ int i=1;
+ tab_position *p;
+
+ for (p = tab; p != NULL; p = p->next) {
+ printf("tab %d is %d\n", i, p->position);
+ i++;
+ }
+}
+
+/*
+ * html_table - methods
+ */
+
+html_table::html_table (simple_output *op, int linelen)
+ : out(op), columns(NULL), linelength(linelen), last_col(NULL), start_space(FALSE)
+{
+ tab_stops = new tabs();
+}
+
+html_table::~html_table ()
+{
+ cols *c;
+ if (tab_stops != NULL)
+ delete tab_stops;
+
+ c = columns;
+ while (columns != NULL) {
+ columns = columns->next;
+ delete c;
+ c = columns;
+ }
+}
+
+/*
+ * remove_cols - remove a list of columns as defined by, c.
+ */
+
+void html_table::remove_cols (cols *c)
+{
+ cols *p;
+
+ while (c != NULL) {
+ p = c;
+ c = c->next;
+ delete p;
+ }
+}
+
+/*
+ * set_linelength - sets the line length value in this table.
+ * It also adds an extra blank column to the
+ * table should linelen exceed the last column.
+ */
+
+void html_table::set_linelength (int linelen)
+{
+ cols *p = NULL;
+ cols *c;
+ linelength = linelen;
+
+ for (c = columns; c != NULL; c = c->next) {
+ if (c->right > linelength) {
+ c->right = linelength;
+ remove_cols(c->next);
+ c->next = NULL;
+ return;
+ }
+ p = c;
+ }
+ if (p != NULL && p->right > 0 && linelength > p->right)
+ add_column(p->no+1, p->right, linelength, 'L');
+}
+
+/*
+ * get_effective_linelength -
+ */
+
+int html_table::get_effective_linelength (void)
+{
+ if (columns != NULL)
+ return linelength - columns->left;
+ else
+ return linelength;
+}
+
+/*
+ * add_indent - adds the indent to a table.
+ */
+
+void html_table::add_indent (int indent)
+{
+ if (columns != NULL && columns->left > indent)
+ add_column(0, indent, columns->left, 'L');
+}
+
+/*
+ * emit_table_header - emits the html header for this table.
+ */
+
+void html_table::emit_table_header (int space)
+{
+ if (columns == NULL)
+ return;
+
+ // dump_table();
+
+ last_col = NULL;
+ if (linelength > 0) {
+ out->nl();
+ out->nl();
+
+ out->put_string("<table width=\"100%\"")
+ .put_string(" border=\"0\" rules=\"none\" frame=\"void\"\n")
+ .put_string(" cellspacing=\"0\" cellpadding=\"0\"");
+ out->put_string(">")
+ .nl();
+ if (dialect == xhtml)
+ emit_colspan();
+ out->put_string("<tr valign=\"top\" align=\"left\"");
+ if (space) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+ out->put_string(">").nl();
+ }
+}
+
+/*
+ * get_right - returns the right most position of this column.
+ */
+
+int html_table::get_right (cols *c)
+{
+ if (c != NULL && c->right > 0)
+ return c->right;
+ if (c->next != NULL)
+ return c->left;
+ return linelength;
+}
+
+/*
+ * set_space - assigns start_space. Used to determine the
+ * vertical alignment when generating the next table row.
+ */
+
+void html_table::set_space (int space)
+{
+ start_space = space;
+}
+
+/*
+ * emit_colspan - emits a series of colspan entries defining the
+ * table columns.
+ */
+
+void html_table::emit_colspan (void)
+{
+ cols *b = columns;
+ cols *c = columns;
+ int width = 0;
+
+ out->put_string("<colgroup>");
+ while (c != NULL) {
+ if (b != NULL && b != c && is_gap(b))
+ /*
+ * blank column for gap
+ */
+ out->put_string("<col width=\"")
+ .put_number(is_gap(b))
+ .put_string("%\" class=\"center\"></col>")
+ .nl();
+
+ width = (get_right(c)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (c->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ switch (c->alignment) {
+ case 'C':
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\" class=\"center\"></col>")
+ .nl();
+ break;
+ case 'R':
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\" class=\"right\"></col>")
+ .nl();
+ break;
+ default:
+ out->put_string("<col width=\"")
+ .put_number(width)
+ .put_string("%\"></col>")
+ .nl();
+ }
+ b = c;
+ c = c->next;
+ }
+ out->put_string("</colgroup>").nl();
+}
+
+/*
+ * emit_td - writes out a <td> tag with a corresponding width
+ * if the dialect is html4.
+ */
+
+void html_table::emit_td (int percentage, const char *s)
+{
+ if (percentage) {
+ if (dialect == html4) {
+ out->put_string("<td width=\"")
+ .put_number(percentage)
+ .put_string("%\"");
+ if (s != NULL)
+ out->put_string(s);
+ out->nl();
+ }
+ else {
+ out->put_string("<td");
+ if (s != NULL)
+ out->put_string(s);
+ out->nl();
+ }
+ }
+}
+
+/*
+ * emit_col - moves onto column, n.
+ */
+
+void html_table::emit_col (int n)
+{
+ cols *c = columns;
+ cols *b = columns;
+ int width = 0;
+
+ // must be a different row
+ if (last_col != NULL && n <= last_col->no)
+ emit_new_row();
+
+ while (c != NULL && c->no < n)
+ c = c->next;
+
+ // can we find column, n?
+ if (c != NULL && c->no == n) {
+ // shutdown previous column
+ if (last_col != NULL)
+ out->put_string("</td>").nl();
+
+ // find previous column
+ if (last_col == NULL)
+ b = columns;
+ else
+ b = last_col;
+
+ // have we a gap?
+ if (last_col != NULL) {
+ emit_td(is_gap(b), "></td>");
+ b = b->next;
+ }
+
+ // move across to column n
+ while (b != c) {
+ // we compute the difference after converting positions
+ // to avoid rounding errors
+ width = (get_right(b)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (b->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ emit_td(width, "></td>");
+ // have we a gap?
+ emit_td(is_gap(b), "></td>");
+ b = b->next;
+ }
+ width = (get_right(b)*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (b->left*100 + get_effective_linelength()/2)
+ /get_effective_linelength();
+ switch (b->alignment) {
+ case 'C':
+ emit_td(width, " align=center>");
+ break;
+ case 'R':
+ emit_td(width, " align=right>");
+ break;
+ default:
+ emit_td(width);
+ }
+ // remember column, b
+ last_col = b;
+ }
+}
+
+/*
+ * finish_row -
+ */
+
+void html_table::finish_row (void)
+{
+ int n = 0;
+ cols *c;
+
+ if (last_col != NULL) {
+ for (c = last_col->next; c != NULL; c = c->next)
+ n = c->no;
+
+ if (n > 0)
+ emit_col(n);
+#if 1
+ if (last_col != NULL) {
+ out->put_string("</td>");
+ last_col = NULL;
+ }
+#endif
+ out->put_string("</tr>").nl();
+ }
+}
+
+/*
+ * emit_new_row - move to the next row.
+ */
+
+void html_table::emit_new_row (void)
+{
+ finish_row();
+
+ out->put_string("<tr valign=\"top\" align=\"left\"");
+ if (start_space) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+ out->put_string(">").nl();
+ start_space = FALSE;
+ last_col = NULL;
+}
+
+void html_table::emit_finish_table (void)
+{
+ finish_row();
+ out->put_string("</table>");
+}
+
+/*
+ * add_column - adds a column. It returns FALSE if hstart..hend
+ * crosses into a different columns.
+ */
+
+int html_table::add_column (int coln, int hstart, int hend, char align)
+{
+ cols *c = get_column(coln);
+
+ if (c == NULL)
+ return insert_column(coln, hstart, hend, align);
+ else
+ return modify_column(c, hstart, hend, align);
+}
+
+/*
+ * get_column - returns the column, coln.
+ */
+
+cols *html_table::get_column (int coln)
+{
+ cols *c = columns;
+
+ while (c != NULL && coln != c->no)
+ c = c->next;
+
+ if (c != NULL && coln == c->no)
+ return c;
+ else
+ return NULL;
+}
+
+/*
+ * insert_column - inserts a column, coln.
+ * It returns TRUE if it does not bump into
+ * another column.
+ */
+
+int html_table::insert_column (int coln, int hstart, int hend, char align)
+{
+ cols *c = columns;
+ cols *l = columns;
+ cols *n = NULL;
+
+ while (c != NULL && c->no < coln) {
+ l = c;
+ c = c->next;
+ }
+ if (l != NULL && l->no>coln && hend > l->left)
+ return FALSE; // new column bumps into previous one
+
+ l = NULL;
+ c = columns;
+ while (c != NULL && c->no < coln) {
+ l = c;
+ c = c->next;
+ }
+
+ if ((l != NULL) && (hstart < l->right))
+ return FALSE; // new column bumps into previous one
+
+ if ((l != NULL) && (l->next != NULL) &&
+ (l->next->left < hend))
+ return FALSE; // new column bumps into next one
+
+ n = new cols;
+ if (l == NULL) {
+ n->next = columns;
+ columns = n;
+ } else {
+ n->next = l->next;
+ l->next = n;
+ }
+ n->left = hstart;
+ n->right = hend;
+ n->no = coln;
+ n->alignment = align;
+ return TRUE;
+}
+
+/*
+ * modify_column - given a column, c, modify the width to
+ * contain hstart..hend.
+ * It returns TRUE if it does not clash with
+ * the next or previous column.
+ */
+
+int html_table::modify_column (cols *c, int hstart, int hend, char align)
+{
+ cols *l = columns;
+
+ while (l != NULL && l->next != c)
+ l = l->next;
+
+ if ((l != NULL) && (hstart < l->right))
+ return FALSE; // new column bumps into previous one
+
+ if ((c->next != NULL) && (c->next->left < hend))
+ return FALSE; // new column bumps into next one
+
+ if (c->left > hstart)
+ c->left = hstart;
+
+ if (c->right < hend)
+ c->right = hend;
+
+ c->alignment = align;
+
+ return TRUE;
+}
+
+/*
+ * find_tab_column - finds the column number for position, pos.
+ * It searches through the list tab stops.
+ */
+
+int html_table::find_tab_column (int pos)
+{
+ // remember the first column is reserved for untabbed glyphs
+ return tab_stops->find_tab(pos)+1;
+}
+
+/*
+ * find_column - find the column number for position, pos.
+ * It searches through the list of columns.
+ */
+
+int html_table::find_column (int pos)
+{
+ int p=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next) {
+ if (c->left > pos)
+ return p;
+ p = c->no;
+ }
+ return p;
+}
+
+/*
+ * no_columns - returns the number of table columns (rather than tabs)
+ */
+
+int html_table::no_columns (void)
+{
+ int n=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next)
+ n++;
+ return n;
+}
+
+/*
+ * is_gap - returns the gap between column, c, and the next column.
+ */
+
+int html_table::is_gap (cols *c)
+{
+ if (c == NULL || c->right <= 0 || c->next == NULL)
+ return 0;
+ else
+ // we compute the difference after converting positions
+ // to avoid rounding errors
+ return (c->next->left*100 + get_effective_linelength()/2)
+ / get_effective_linelength()
+ - (c->right*100 + get_effective_linelength()/2)
+ / get_effective_linelength();
+}
+
+/*
+ * no_gaps - returns the number of table gaps between the columns
+ */
+
+int html_table::no_gaps (void)
+{
+ int n=0;
+ cols *c;
+
+ for (c = columns; c != NULL; c = c->next)
+ if (is_gap(c))
+ n++;
+ return n;
+}
+
+/*
+ * get_tab_pos - returns the, nth, tab position
+ */
+
+int html_table::get_tab_pos (int n)
+{
+ return tab_stops->get_tab_pos(n);
+}
+
+char html_table::get_tab_align (int n)
+{
+ return tab_stops->get_tab_align(n);
+}
+
+
+void html_table::dump_table (void)
+{
+ if (columns != NULL) {
+ cols *c;
+ for (c = columns; c != NULL; c = c->next) {
+ printf("column %d %d..%d %c\n", c->no, c->left, c->right, c->alignment);
+ }
+ } else
+ tab_stops->dump_tabs();
+}
+
+/*
+ * html_indent - creates an indent with indentation, ind, given
+ * a line length of linelength.
+ */
+
+html_indent::html_indent (simple_output *op, int ind, int pageoffset, int linelength)
+{
+ table = new html_table(op, linelength);
+
+ table->add_column(1, ind+pageoffset, linelength, 'L');
+ table->add_indent(pageoffset);
+ in = ind;
+ pg = pageoffset;
+ ll = linelength;
+}
+
+html_indent::~html_indent (void)
+{
+ end();
+ delete table;
+}
+
+void html_indent::begin (int space)
+{
+ if (in + pg == 0) {
+ if (space) {
+ table->out->put_string(" style=\"margin-top: ");
+ table->out->put_string(STYLE_VERTICAL_SPACE);
+ table->out->put_string("\"");
+ }
+ }
+ else {
+ //
+ // we use exactly the same mechanism for calculating
+ // indentation as html_table::emit_col
+ //
+ table->out->put_string(" style=\"margin-left:")
+ .put_number(((in + pg) * 100 + ll/2) / ll -
+ (ll/2)/ll)
+ .put_string("%;");
+
+ if (space) {
+ table->out->put_string(" margin-top: ");
+ table->out->put_string(STYLE_VERTICAL_SPACE);
+ }
+ table->out->put_string("\"");
+ }
+}
+
+void html_indent::end (void)
+{
+}
+
+/*
+ * get_reg - collects the registers as supplied during initialization.
+ */
+
+void html_indent::get_reg (int *ind, int *pageoffset, int *linelength)
+{
+ *ind = in;
+ *pageoffset = pg;
+ *linelength = ll;
+}
diff --git a/src/devices/grohtml/html-table.h b/src/devices/grohtml/html-table.h
new file mode 100644
index 0000000..7ed27a9
--- /dev/null
+++ b/src/devices/grohtml/html-table.h
@@ -0,0 +1,133 @@
+// -*- C++ -*-
+/* Copyright (C) 2002-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-table.h
+ *
+ * html-table.h
+ *
+ * provides the methods necessary to handle indentation and tab
+ * positions using html tables.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "html.h"
+
+#if !defined(HTML_TABLE_H)
+#define HTML_TABLE_H
+
+typedef struct tab_position {
+ char alignment;
+ int position;
+ struct tab_position *next;
+} tab_position;
+
+
+class tabs {
+public:
+ tabs ();
+ ~tabs ();
+ void clear (void);
+ int compatible (const char *s);
+ void init (const char *s);
+ void check_init (const char *s);
+ int find_tab (int pos);
+ int get_tab_pos (int n);
+ char get_tab_align (int n);
+ void dump_tabs (void);
+
+private:
+ void delete_list (void);
+ tab_position *tab;
+};
+
+/*
+ * define a column
+ */
+
+typedef struct cols {
+ int left, right;
+ int no;
+ char alignment;
+ struct cols *next;
+} cols;
+
+class html_table {
+public:
+ html_table (simple_output *op, int linelen);
+ ~html_table (void);
+ int add_column (int coln, int hstart, int hend, char align);
+ cols *get_column (int coln);
+ int insert_column (int coln, int hstart, int hend, char align);
+ int modify_column (cols *c, int hstart, int hend, char align);
+ int find_tab_column (int pos);
+ int find_column (int pos);
+ int get_tab_pos (int n);
+ char get_tab_align (int n);
+ void set_linelength (int linelen);
+ int no_columns (void);
+ int no_gaps (void);
+ int is_gap (cols *c);
+ void dump_table (void);
+ void emit_table_header (int space);
+ void emit_col (int n);
+ void emit_new_row (void);
+ void emit_finish_table (void);
+ int get_right (cols *c);
+ void add_indent (int indent);
+ void finish_row (void);
+ int get_effective_linelength (void);
+ void set_space (int space);
+ void emit_colspan (void);
+ void emit_td (int percentage, const char *s = ">");
+
+ tabs *tab_stops; /* tab stop positions */
+ simple_output *out;
+private:
+ cols *columns; /* column entries */
+ int linelength;
+ cols *last_col; /* last column started */
+ int start_space; /* have we seen a '.sp' tag? */
+
+ void remove_cols (cols *c);
+};
+
+/*
+ * the indentation wrapper.
+ * Builds an indentation from a html-table.
+ * This table is only emitted if the paragraph is emitted.
+ */
+
+class html_indent {
+public:
+ html_indent (simple_output *op, int ind, int pageoffset, int linelength);
+ ~html_indent (void);
+ void begin (int space); // called if we need to use the indent
+ void get_reg (int *ind, int *pageoffset, int *linelength);
+
+ // the indent is shutdown when it is deleted
+
+private:
+ void end (void);
+ int is_used;
+ int pg; // values of the registers as passed via initialization
+ int ll;
+ int in;
+ html_table *table;
+};
+
+#endif
diff --git a/src/devices/grohtml/html-text.cpp b/src/devices/grohtml/html-text.cpp
new file mode 100644
index 0000000..b07cbe7
--- /dev/null
+++ b/src/devices/grohtml/html-text.cpp
@@ -0,0 +1,1056 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.cpp
+ *
+ * html-text.cpp
+ *
+ * provide a troff like state machine interface which
+ * generates html text.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+
+#include "html-text.h"
+
+html_text::html_text (simple_output *op, html_dialect d) :
+ stackptr(NULL), lastptr(NULL), out(op), dialect(d),
+ space_emitted(TRUE), current_indentation(-1),
+ pageoffset(-1), linelength(-1), blank_para(TRUE),
+ start_space(FALSE)
+{
+}
+
+html_text::~html_text ()
+{
+ flush_text();
+}
+
+
+#if defined(DEBUGGING)
+static int debugStack = FALSE;
+
+
+/*
+ * turnDebug - flip the debugStack boolean and return the new value.
+ */
+
+static int turnDebug (void)
+{
+ debugStack = 1-debugStack;
+ return debugStack;
+}
+
+/*
+ * dump_stack_element - display an element of the html stack, p.
+ */
+
+void html_text::dump_stack_element (tag_definition *p)
+{
+ fprintf(stderr, " | ");
+ switch (p->type) {
+
+ case P_TAG: if (p->indent == NULL) {
+ fprintf(stderr, "<P %s>", (char *)p->arg1); break;
+ } else {
+ fprintf(stderr, "<P %s [TABLE]>", (char *)p->arg1); break;
+ }
+ case I_TAG: fprintf(stderr, "<I>"); break;
+ case B_TAG: fprintf(stderr, "<B>"); break;
+ case SUB_TAG: fprintf(stderr, "<SUB>"); break;
+ case SUP_TAG: fprintf(stderr, "<SUP>"); break;
+ case TT_TAG: fprintf(stderr, "<TT>"); break;
+ case PRE_TAG: if (p->indent == NULL) {
+ fprintf(stderr, "<PRE>"); break;
+ } else {
+ fprintf(stderr, "<PRE [TABLE]>"); break;
+ }
+ case SMALL_TAG: fprintf(stderr, "<SMALL>"); break;
+ case BIG_TAG: fprintf(stderr, "<BIG>"); break;
+ case BREAK_TAG: fprintf(stderr, "<BREAK>"); break;
+ case COLOR_TAG: {
+ if (p->col.is_default())
+ fprintf(stderr, "<COLOR (default)>");
+ else {
+ unsigned int r, g, b;
+
+ p->col.get_rgb(&r, &g, &b);
+ fprintf(stderr, "<COLOR %x %x %x>", r/0x101, g/0x101, b/0x101);
+ }
+ break;
+ }
+ default: fprintf(stderr, "unknown tag");
+ }
+ if (p->text_emitted)
+ fprintf(stderr, "[t] ");
+}
+
+/*
+ * dump_stack - debugging function only.
+ */
+
+void html_text::dump_stack (void)
+{
+ if (debugStack) {
+ tag_definition *p = stackptr;
+
+ while (p != NULL) {
+ dump_stack_element(p);
+ p = p->next;
+ }
+ }
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+#else
+void html_text::dump_stack (void) {}
+#endif
+
+
+/*
+ * end_tag - shuts down the tag.
+ */
+
+void html_text::end_tag (tag_definition *t)
+{
+ switch (t->type) {
+
+ case I_TAG: out->put_string("</i>"); break;
+ case B_TAG: out->put_string("</b>"); break;
+ case P_TAG: if (t->indent == NULL) {
+ out->put_string("</p>");
+ } else {
+ delete t->indent;
+ t->indent = NULL;
+ out->put_string("</p>");
+ }
+ out->enable_newlines(FALSE);
+ blank_para = TRUE; break;
+ case SUB_TAG: out->put_string("</sub>"); break;
+ case SUP_TAG: out->put_string("</sup>"); break;
+ case TT_TAG: out->put_string("</tt>"); break;
+ case PRE_TAG: out->put_string("</pre>"); out->enable_newlines(TRUE);
+ blank_para = TRUE;
+ if (t->indent != NULL)
+ delete t->indent;
+ t->indent = NULL;
+ break;
+ case SMALL_TAG: if (! is_in_pre ())
+ out->put_string("</small>");
+ break;
+ case BIG_TAG: if (! is_in_pre ())
+ out->put_string("</big>");
+ break;
+ case COLOR_TAG: if (! is_in_pre ())
+ out->put_string("</font>");
+ break;
+
+ default:
+ error("unrecognised tag");
+ }
+}
+
+/*
+ * issue_tag - writes out an html tag with argument.
+ * space == 0 if no space is requested
+ * space == 1 if a space is requested
+ * space == 2 if tag should not have a space style
+ */
+
+void html_text::issue_tag (const char *tagname, const char *arg,
+ int space)
+{
+ if ((arg == 0) || (strlen(arg) == 0))
+ out->put_string(tagname);
+ else {
+ out->put_string(tagname);
+ out->put_string(" ");
+ out->put_string(arg);
+ }
+ if (space == TRUE) {
+ out->put_string(" style=\"margin-top: ");
+ out->put_string(STYLE_VERTICAL_SPACE);
+ out->put_string("\"");
+ }
+#if 0
+ if (space == TRUE || space == FALSE)
+ out->put_string(" valign=\"top\"");
+#endif
+ out->put_string(">");
+}
+
+/*
+ * issue_color_begin - writes out an html color tag.
+ */
+
+void html_text::issue_color_begin (color *c)
+{
+ char buf[(INT_HEXDIGITS * 3) + 1];
+ unsigned int r, g, b;
+
+ out->put_string("<font color=\"#");
+ if (c->is_default())
+ sprintf(buf, "000000");
+ else {
+ c->get_rgb(&r, &g, &b);
+ // we have to scale 0..0xFFFF to 0..0xFF
+ sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
+ }
+ out->put_string(buf);
+ out->put_string("\">");
+}
+
+/*
+ * start_tag - starts a tag.
+ */
+
+void html_text::start_tag (tag_definition *t)
+{
+ switch (t->type) {
+
+ case I_TAG: issue_tag("<i", (char *)t->arg1); break;
+ case B_TAG: issue_tag("<b", (char *)t->arg1); break;
+ case P_TAG: if (t->indent != NULL) {
+ out->nl();
+#if defined(DEBUGGING)
+ out->simple_comment("INDENTATION");
+#endif
+ out->put_string("\n<p");
+ t->indent->begin(start_space);
+ issue_tag("", (char *)t->arg1);
+ } else {
+ out->nl();
+ issue_tag("\n<p", (char *)t->arg1, start_space);
+ }
+
+ out->enable_newlines(TRUE); break;
+ case SUB_TAG: issue_tag("<sub", (char *)t->arg1); break;
+ case SUP_TAG: issue_tag("<sup", (char *)t->arg1); break;
+ case TT_TAG: issue_tag("<tt", (char *)t->arg1); break;
+ case PRE_TAG: out->enable_newlines(TRUE);
+ out->nl(); out->put_string("<pre");
+ if (t->indent == NULL)
+ issue_tag("", (char *)t->arg1, start_space);
+ else {
+ t->indent->begin(start_space);
+ issue_tag("", (char *)t->arg1);
+ }
+ out->enable_newlines(FALSE); break;
+ case SMALL_TAG: if (! is_in_pre ())
+ issue_tag("<small", (char *)t->arg1);
+ break;
+ case BIG_TAG: if (! is_in_pre ())
+ issue_tag("<big", (char *)t->arg1);
+ break;
+ case BREAK_TAG: break;
+ case COLOR_TAG: if (! is_in_pre ())
+ issue_color_begin(&t->col);
+ break;
+
+ default:
+ error("unrecognised tag");
+ }
+}
+
+/*
+ * flush_text - flushes html tags which are outstanding on the html stack.
+ */
+
+void html_text::flush_text (void)
+{
+ int notext=TRUE;
+ tag_definition *p=stackptr;
+
+ while (stackptr != 0) {
+ notext = (notext && (! stackptr->text_emitted));
+ if (! notext) {
+ end_tag(stackptr);
+ }
+ p = stackptr;
+ stackptr = stackptr->next;
+ delete p;
+ }
+ lastptr = NULL;
+}
+
+/*
+ * is_present - returns TRUE if tag is already present on the stack.
+ */
+
+int html_text::is_present (HTML_TAG t)
+{
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (t == p->type)
+ return TRUE;
+ p = p->next;
+ }
+ return FALSE;
+}
+
+/*
+ * uses_indent - returns TRUE if the current paragraph is using a
+ * html table to effect an indent.
+ */
+
+int html_text::uses_indent (void)
+{
+ tag_definition *p = stackptr;
+
+ while (p != NULL) {
+ if (p->indent != NULL)
+ return TRUE;
+ p = p->next;
+ }
+ return FALSE;
+}
+
+extern void stop();
+
+/*
+ * do_push - places, tag_definition, p, onto the stack
+ */
+
+void html_text::do_push (tag_definition *p)
+{
+ HTML_TAG t = p->type;
+
+#if defined(DEBUGGING)
+ if (t == PRE_TAG)
+ stop();
+ debugStack = TRUE;
+ fprintf(stderr, "\nentering do_push (");
+ dump_stack_element(p);
+ fprintf(stderr, ")\n");
+ dump_stack();
+ fprintf(stderr, ")\n");
+ fflush(stderr);
+#endif
+
+ /*
+ * if t is a P_TAG or PRE_TAG make sure it goes on the end of the stack.
+ */
+
+ if (((t == P_TAG) || (t == PRE_TAG)) && (lastptr != NULL)) {
+ /*
+ * store, p, at the end
+ */
+ lastptr->next = p;
+ lastptr = p;
+ p->next = NULL;
+ } else {
+ p->next = stackptr;
+ if (stackptr == NULL)
+ lastptr = p;
+ stackptr = p;
+ }
+
+#if defined(DEBUGGING)
+ dump_stack();
+ fprintf(stderr, "exiting do_push\n");
+#endif
+}
+
+/*
+ * push_para - adds a new entry onto the html paragraph stack.
+ */
+
+void html_text::push_para (HTML_TAG t, void *arg, html_indent *in)
+{
+ tag_definition *p= new tag_definition;
+
+ p->type = t;
+ p->arg1 = arg;
+ p->text_emitted = FALSE;
+ p->indent = in;
+
+ if (t == PRE_TAG && is_present(PRE_TAG))
+ fatal("cannot have multiple PRE_TAGs");
+
+ do_push(p);
+}
+
+void html_text::push_para (HTML_TAG t)
+{
+ push_para(t, (void *)"", NULL);
+}
+
+void html_text::push_para (color *c)
+{
+ tag_definition *p = new tag_definition;
+
+ p->type = COLOR_TAG;
+ p->arg1 = NULL;
+ p->col = *c;
+ p->text_emitted = FALSE;
+ p->indent = NULL;
+
+ do_push(p);
+}
+
+/*
+ * do_italic - changes to italic
+ */
+
+void html_text::do_italic (void)
+{
+ if (! is_present(I_TAG))
+ push_para(I_TAG);
+}
+
+/*
+ * do_bold - changes to bold.
+ */
+
+void html_text::do_bold (void)
+{
+ if (! is_present(B_TAG))
+ push_para(B_TAG);
+}
+
+/*
+ * do_tt - changes to teletype.
+ */
+
+void html_text::do_tt (void)
+{
+ if ((! is_present(TT_TAG)) && (! is_present(PRE_TAG)))
+ push_para(TT_TAG);
+}
+
+/*
+ * do_pre - changes to preformated text.
+ */
+
+void html_text::do_pre (void)
+{
+ done_tt();
+ if (is_present(P_TAG)) {
+ html_indent *i = remove_indent(P_TAG);
+ int space = retrieve_para_space();
+ (void)done_para();
+ if (! is_present(PRE_TAG))
+ push_para(PRE_TAG, NULL, i);
+ start_space = space;
+ } else if (! is_present(PRE_TAG))
+ push_para(PRE_TAG, NULL, NULL);
+ dump_stack();
+}
+
+/*
+ * is_in_pre - returns TRUE if we are currently within a preformatted
+ * <pre> block.
+ */
+
+int html_text::is_in_pre (void)
+{
+ return is_present(PRE_TAG);
+}
+
+/*
+ * do_color - initiates a new color tag.
+ */
+
+void html_text::do_color (color *c)
+{
+ shutdown(COLOR_TAG); // shutdown a previous color tag, if present
+ push_para(c);
+}
+
+/*
+ * done_color - shutdown an outstanding color tag, if it exists.
+ */
+
+void html_text::done_color (void)
+{
+ shutdown(COLOR_TAG);
+}
+
+/*
+ * shutdown - shuts down an html tag.
+ */
+
+char *html_text::shutdown (HTML_TAG t)
+{
+ char *arg=NULL;
+
+ if (is_present(t)) {
+ tag_definition *p =stackptr;
+ tag_definition *temp =NULL;
+ int notext =TRUE;
+
+ dump_stack();
+ while ((stackptr != NULL) && (stackptr->type != t)) {
+ notext = (notext && (! stackptr->text_emitted));
+ if (! notext) {
+ end_tag(stackptr);
+ }
+
+ /*
+ * pop tag
+ */
+ p = stackptr;
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+
+ /*
+ * push tag onto temp stack
+ */
+ p->next = temp;
+ temp = p;
+ }
+
+ /*
+ * and examine stackptr
+ */
+ if ((stackptr != NULL) && (stackptr->type == t)) {
+ if (stackptr->text_emitted) {
+ end_tag(stackptr);
+ }
+ if (t == P_TAG) {
+ arg = (char *)stackptr->arg1;
+ }
+ p = stackptr;
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ if (p->indent != NULL)
+ delete p->indent;
+ delete p;
+ }
+
+ /*
+ * and restore unaffected tags
+ */
+ while (temp != NULL) {
+ if (temp->type == COLOR_TAG)
+ push_para(&temp->col);
+ else
+ push_para(temp->type, temp->arg1, temp->indent);
+ p = temp;
+ temp = temp->next;
+ delete p;
+ }
+ }
+ return arg;
+}
+
+/*
+ * done_bold - shuts downs a bold tag.
+ */
+
+void html_text::done_bold (void)
+{
+ shutdown(B_TAG);
+}
+
+/*
+ * done_italic - shuts downs an italic tag.
+ */
+
+void html_text::done_italic (void)
+{
+ shutdown(I_TAG);
+}
+
+/*
+ * done_sup - shuts downs a sup tag.
+ */
+
+void html_text::done_sup (void)
+{
+ shutdown(SUP_TAG);
+}
+
+/*
+ * done_sub - shuts downs a sub tag.
+ */
+
+void html_text::done_sub (void)
+{
+ shutdown(SUB_TAG);
+}
+
+/*
+ * done_tt - shuts downs a tt tag.
+ */
+
+void html_text::done_tt (void)
+{
+ shutdown(TT_TAG);
+}
+
+/*
+ * done_pre - shuts downs a pre tag.
+ */
+
+void html_text::done_pre (void)
+{
+ shutdown(PRE_TAG);
+}
+
+/*
+ * done_small - shuts downs a small tag.
+ */
+
+void html_text::done_small (void)
+{
+ shutdown(SMALL_TAG);
+}
+
+/*
+ * done_big - shuts downs a big tag.
+ */
+
+void html_text::done_big (void)
+{
+ shutdown(BIG_TAG);
+}
+
+/*
+ * check_emit_text - ensures that all previous tags have been emitted (in order)
+ * before the text is written.
+ */
+
+void html_text::check_emit_text (tag_definition *t)
+{
+ if ((t != NULL) && (! t->text_emitted)) {
+ check_emit_text(t->next);
+ t->text_emitted = TRUE;
+ start_tag(t);
+ }
+}
+
+/*
+ * do_emittext - tells the class that text was written during the current tag.
+ */
+
+void html_text::do_emittext (const char *s, int length)
+{
+ if ((! is_present(P_TAG)) && (! is_present(PRE_TAG)))
+ do_para("", FALSE);
+
+ if (is_present(BREAK_TAG)) {
+ int text = remove_break();
+ check_emit_text(stackptr);
+ if (text) {
+ if (is_present(PRE_TAG))
+ out->nl();
+ else if (dialect == xhtml)
+ out->put_string("<br/>").nl();
+ else
+ out->put_string("<br>").nl();
+ }
+ } else
+ check_emit_text(stackptr);
+
+ out->put_string(s, length);
+ space_emitted = FALSE;
+ blank_para = FALSE;
+}
+
+/*
+ * do_para - starts a new paragraph
+ */
+
+void html_text::do_para (const char *arg, html_indent *in, int space)
+{
+ if (! is_present(P_TAG)) {
+ if (is_present(PRE_TAG)) {
+ html_indent *i = remove_indent(PRE_TAG);
+ done_pre();
+ if ((arg == NULL || (strcmp(arg, "") == 0)) &&
+ (i == in || in == NULL))
+ in = i;
+ else
+ delete i;
+ }
+ remove_sub_sup();
+ push_para(P_TAG, (void *)arg, in);
+ start_space = space;
+ }
+}
+
+void html_text::do_para (const char *arg, int space)
+{
+ do_para(arg, NULL, space);
+}
+
+void html_text::do_para (simple_output *op, const char *arg1,
+ int indentation_value, int page_offset,
+ int line_length, int space)
+{
+ html_indent *ind;
+
+ if (indentation_value == 0)
+ ind = NULL;
+ else
+ ind = new html_indent(op, indentation_value, page_offset, line_length);
+ do_para(arg1, ind, space);
+}
+
+/*
+ * done_para - shuts down a paragraph tag.
+ */
+
+char *html_text::done_para (void)
+{
+ char *result;
+ space_emitted = TRUE;
+ result = shutdown(P_TAG);
+ start_space = FALSE;
+ return result;
+}
+
+/*
+ * remove_indent - returns the indent associated with, tag.
+ * The indent associated with tag is set to NULL.
+ */
+
+html_indent *html_text::remove_indent (HTML_TAG tag)
+{
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (tag == p->type) {
+ html_indent *i = p->indent;
+ p->indent = NULL;
+ return i;
+ }
+ p = p->next;
+ }
+ return NULL;
+}
+
+/*
+ * remove_para_space - removes the leading space to a paragraph
+ * (effectively this trims off a leading '.sp' tag).
+ */
+
+void html_text::remove_para_space (void)
+{
+ start_space = FALSE;
+}
+
+/*
+ * do_space - issues an end of paragraph
+ */
+
+void html_text::do_space (void)
+{
+ if (is_in_pre()) {
+ do_emittext("", 0);
+ out->force_nl();
+ space_emitted = TRUE;
+ } else {
+ html_indent *i = remove_indent(P_TAG);
+
+ do_para(done_para(), i, TRUE);
+ space_emitted = TRUE;
+ }
+}
+
+/*
+ * do_break - issue a break tag.
+ */
+
+void html_text::do_break (void)
+{
+ if (! is_present(PRE_TAG))
+ if (emitted_text())
+ if (! is_present(BREAK_TAG))
+ push_para(BREAK_TAG);
+
+ space_emitted = TRUE;
+}
+
+/*
+ * do_newline - issue a newline providing that we are inside a <pre> tag.
+ */
+
+void html_text::do_newline (void)
+{
+ if (is_present(PRE_TAG)) {
+ do_emittext("\n", 1);
+ space_emitted = TRUE;
+ }
+}
+
+/*
+ * emitted_text - returns FALSE if white space has just been written.
+ */
+
+int html_text::emitted_text (void)
+{
+ return !space_emitted;
+}
+
+/*
+ * ever_emitted_text - returns TRUE if we have ever emitted text in this
+ * paragraph.
+ */
+
+int html_text::ever_emitted_text (void)
+{
+ return !blank_para;
+}
+
+/*
+ * starts_with_space - returns TRUE if we started this paragraph with a .sp
+ */
+
+int html_text::starts_with_space (void)
+{
+ return start_space;
+}
+
+/*
+ * retrieve_para_space - returns TRUE, if the paragraph starts with
+ * a space and text has not yet been emitted.
+ * If TRUE is returned, then the, start_space,
+ * variable is set to FALSE.
+ */
+
+int html_text::retrieve_para_space (void)
+{
+ if (start_space && blank_para) {
+ start_space = FALSE;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/*
+ * emit_space - writes a space providing that text was written beforehand.
+ */
+
+void html_text::emit_space (void)
+{
+ if (is_present(PRE_TAG))
+ do_emittext(" ", 1);
+ else
+ out->space_or_newline();
+
+ space_emitted = TRUE;
+}
+
+/*
+ * remove_def - removes a definition, t, from the stack.
+ */
+
+void html_text::remove_def (tag_definition *t)
+{
+ tag_definition *p = stackptr;
+ tag_definition *l = 0;
+
+ while ((p != 0) && (p != t)) {
+ l = p;
+ p = p->next;
+ }
+ if ((p != 0) && (p == t)) {
+ if (p == stackptr) {
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ } else if (l == 0) {
+ error("stack list pointers are wrong");
+ } else {
+ l->next = p->next;
+ if (l->next == NULL)
+ lastptr = l;
+ }
+ delete p;
+ }
+}
+
+/*
+ * remove_tag - removes a tag from the stack.
+ */
+
+void html_text::remove_tag (HTML_TAG tag)
+{
+ tag_definition *p = stackptr;
+
+ while ((p != 0) && (p->type != tag)) {
+ p = p->next;
+ }
+ if ((p != 0) && (p->type == tag))
+ remove_def(p);
+}
+
+/*
+ * remove_sub_sup - removes a sub or sup tag, should either exist
+ * on the stack.
+ */
+
+void html_text::remove_sub_sup (void)
+{
+ if (is_present(SUB_TAG)) {
+ remove_tag(SUB_TAG);
+ }
+ if (is_present(SUP_TAG)) {
+ remove_tag(SUP_TAG);
+ }
+ if (is_present(PRE_TAG)) {
+ remove_tag(PRE_TAG);
+ }
+}
+
+/*
+ * remove_break - break tags are not balanced thus remove it once it has been emitted.
+ * It returns TRUE if text was emitted before the <br> was issued.
+ */
+
+int html_text::remove_break (void)
+{
+ tag_definition *p = stackptr;
+ tag_definition *l = 0;
+ tag_definition *q = 0;
+
+ while ((p != 0) && (p->type != BREAK_TAG)) {
+ l = p;
+ p = p->next;
+ }
+ if ((p != 0) && (p->type == BREAK_TAG)) {
+ if (p == stackptr) {
+ stackptr = stackptr->next;
+ if (stackptr == NULL)
+ lastptr = NULL;
+ q = stackptr;
+ } else if (l == 0)
+ error("stack list pointers are wrong");
+ else {
+ l->next = p->next;
+ q = p->next;
+ if (l->next == NULL)
+ lastptr = l;
+ }
+ delete p;
+ }
+ /*
+ * now determine whether text was issued before <br>
+ */
+ while (q != 0) {
+ if (q->text_emitted)
+ return TRUE;
+ else
+ q = q->next;
+ }
+ return FALSE;
+}
+
+/*
+ * remove_para_align - removes a paragraph which has a text
+ * argument. If the paragraph has no text
+ * argument then it is left alone.
+ */
+
+void html_text::remove_para_align (void)
+{
+ if (is_present(P_TAG)) {
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (p->type == P_TAG && p->arg1 != NULL) {
+ html_indent *i = remove_indent(P_TAG);
+ int space = retrieve_para_space();
+ done_para();
+ do_para("", i, space);
+ return;
+ }
+ p = p->next;
+ }
+ }
+}
+
+/*
+ * get_alignment - returns the alignment for the paragraph.
+ * If no alignment was given then we return "".
+ */
+
+char *html_text::get_alignment (void)
+{
+ if (is_present(P_TAG)) {
+ tag_definition *p=stackptr;
+
+ while (p != NULL) {
+ if (p->type == P_TAG && p->arg1 != NULL)
+ return (char *)p->arg1;
+ p = p->next;
+ }
+ }
+ return (char *)"";
+}
+
+/*
+ * do_small - potentially inserts a <small> tag into the html stream.
+ * However we check for a <big> tag, if present then we terminate it.
+ * Otherwise a <small> tag is inserted.
+ */
+
+void html_text::do_small (void)
+{
+ if (is_present(BIG_TAG))
+ done_big();
+ else
+ push_para(SMALL_TAG);
+}
+
+/*
+ * do_big - is the mirror image of do_small.
+ */
+
+void html_text::do_big (void)
+{
+ if (is_present(SMALL_TAG))
+ done_small();
+ else
+ push_para(BIG_TAG);
+}
+
+/*
+ * do_sup - save a superscript tag on the stack of tags.
+ */
+
+void html_text::do_sup (void)
+{
+ push_para(SUP_TAG);
+}
+
+/*
+ * do_sub - save a subscript tag on the stack of tags.
+ */
+
+void html_text::do_sub (void)
+{
+ push_para(SUB_TAG);
+}
diff --git a/src/devices/grohtml/html-text.h b/src/devices/grohtml/html-text.h
new file mode 100644
index 0000000..ee58601
--- /dev/null
+++ b/src/devices/grohtml/html-text.h
@@ -0,0 +1,138 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote html-text.h
+ *
+ * html-text.h
+ *
+ * provides a state machine interface which generates html text.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "html.h"
+#include "html-table.h"
+
+#define STYLE_VERTICAL_SPACE "1em"
+
+/*
+ * supported html dialects.
+ */
+
+typedef enum {xhtml, html4} html_dialect;
+
+/*
+ * html tags
+ */
+
+typedef enum {I_TAG, B_TAG, P_TAG, SUB_TAG, SUP_TAG, TT_TAG,
+ PRE_TAG, SMALL_TAG, BIG_TAG, BREAK_TAG,
+ COLOR_TAG} HTML_TAG;
+
+typedef struct tag_definition {
+ HTML_TAG type;
+ void *arg1;
+ int text_emitted;
+ color col;
+ html_indent *indent;
+ tag_definition *next;
+} tag_definition ;
+
+/*
+ * the state of the current paragraph.
+ * It allows post-html.cpp to request font changes, paragraph start/end
+ * and emits balanced tags with a small amount of peephole optimization.
+ */
+
+class html_text {
+public:
+ html_text (simple_output *op, html_dialect d);
+ ~html_text (void);
+ void flush_text (void);
+ void do_emittext (const char *s, int length);
+ void do_italic (void);
+ void do_bold (void);
+ void do_roman (void);
+ void do_tt (void);
+ void do_pre (void);
+ void do_small (void);
+ void do_big (void);
+ void do_para (const char *arg, int space); // used for no indentation
+ void do_para (simple_output *op, const char *arg1,
+ int indentation, int pageoffset, int linelength,
+ int space);
+ void do_sup (void);
+ void do_sub (void);
+ void do_space (void);
+ void do_break (void);
+ void do_newline (void);
+ void do_table (const char *arg);
+ void done_bold (void);
+ void done_italic (void);
+ char *done_para (void);
+ void done_sup (void);
+ void done_sub (void);
+ void done_tt (void);
+ void done_pre (void);
+ void done_small (void);
+ void done_big (void);
+ void do_color (color *c);
+ void done_color (void);
+ int emitted_text (void);
+ int ever_emitted_text (void);
+ int starts_with_space (void);
+ int retrieve_para_space (void);
+ void emit_space (void);
+ int is_in_pre (void);
+ int uses_indent (void);
+ void remove_tag (HTML_TAG tag);
+ void remove_sub_sup (void);
+ void remove_para_align (void);
+ void remove_para_space (void);
+ char *get_alignment (void);
+
+private:
+ tag_definition *stackptr; /* the current paragraph state */
+ tag_definition *lastptr; /* the end of the stack */
+ simple_output *out;
+ html_dialect dialect; /* which dialect of html? */
+ int space_emitted; /* just emitted a space? */
+ int current_indentation; /* current .in value */
+ int pageoffset; /* .po value */
+ int linelength; /* current line length */
+ int blank_para; /* have we ever written text? */
+ int start_space; /* does para start with a .sp */
+ html_indent *indent; /* our indent class */
+
+ int is_present (HTML_TAG t);
+ void end_tag (tag_definition *t);
+ void start_tag (tag_definition *t);
+ void do_para (const char *arg, html_indent *in, int space);
+ void push_para (HTML_TAG t);
+ void push_para (HTML_TAG t, void *arg, html_indent *in);
+ void push_para (color *c);
+ void do_push (tag_definition *p);
+ char *shutdown (HTML_TAG t);
+ void check_emit_text (tag_definition *t);
+ int remove_break (void);
+ void issue_tag (const char *tagname, const char *arg, int space=2);
+ void issue_color_begin (color *c);
+ void remove_def (tag_definition *t);
+ html_indent *remove_indent (HTML_TAG tag);
+ void dump_stack_element (tag_definition *p);
+ void dump_stack (void);
+};
diff --git a/src/devices/grohtml/html.h b/src/devices/grohtml/html.h
new file mode 100644
index 0000000..4828646
--- /dev/null
+++ b/src/devices/grohtml/html.h
@@ -0,0 +1,97 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#if !defined(HTML_H)
+# define HTML_H
+
+const int INT_HEXDIGITS = 16; // enough for 64-bit ints
+
+/*
+ * class and structure needed to buffer words
+ */
+
+struct word {
+ char *s;
+ word *next;
+
+ word (const char *w, int n);
+ ~word ();
+};
+
+class word_list {
+public:
+ word_list ();
+ int flush (FILE *f);
+ void add_word (const char *s, int n);
+ int get_length (void);
+
+private:
+ int length;
+ word *head;
+ word *tail;
+};
+
+class simple_output {
+public:
+ simple_output(FILE *, int max_line_length);
+ simple_output &put_string(const char *, int);
+ simple_output &put_string(const char *s);
+ simple_output &put_string(const string &s);
+ simple_output &put_troffps_char (const char *s);
+ simple_output &put_translated_string(const char *s);
+ simple_output &put_number(int);
+ simple_output &put_float(double);
+ simple_output &put_symbol(const char *);
+ simple_output &put_literal_symbol(const char *);
+ simple_output &set_fixed_point(int);
+ simple_output &simple_comment(const char *);
+ simple_output &begin_comment(const char *);
+ simple_output &comment_arg(const char *);
+ simple_output &end_comment();
+ simple_output &set_file(FILE *);
+ simple_output &include_file(FILE *);
+ simple_output &copy_file(FILE *);
+ simple_output &end_line();
+ simple_output &put_raw_char(char);
+ simple_output &special(const char *);
+ simple_output &enable_newlines(int);
+ simple_output &check_newline(int n);
+ simple_output &nl(void);
+ simple_output &force_nl(void);
+ simple_output &space_or_newline (void);
+ simple_output &begin_tag (void);
+ FILE *get_file();
+private:
+ FILE *fp;
+ int max_line_length; // not including newline
+ int col;
+ int fixed_point;
+ int newlines; // can we issue newlines automatically?
+ word_list last_word;
+
+ void flush_last_word (void);
+ int check_space (const char *s, int n);
+};
+
+inline FILE *simple_output::get_file()
+{
+ return fp;
+}
+
+#endif
diff --git a/src/devices/grohtml/output.cpp b/src/devices/grohtml/output.cpp
new file mode 100644
index 0000000..0ffeb58
--- /dev/null
+++ b/src/devices/grohtml/output.cpp
@@ -0,0 +1,363 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote output.cpp
+ * but it owes a huge amount of ideas and raw code from
+ * James Clark (jjc@jclark.com) grops/ps.cpp.
+ *
+ * output.cpp
+ *
+ * provide the simple low level output routines needed by html.cpp
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#include <time.h>
+#include "html.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+
+#if defined(DEBUGGING)
+# define FPUTC(X,Y) do { fputc((X),(Y)); fputc((X), stderr); fflush(stderr); } while (0)
+# define FPUTS(X,Y) do { fputs((X),(Y)); fputs((X), stderr); fflush(stderr); } while (0)
+# define PUTC(X,Y) do { putc((X),(Y)); putc((X), stderr); fflush(stderr); } while (0)
+#else
+# define FPUTC(X,Y) do { fputc((X),(Y)); } while (0)
+# define FPUTS(X,Y) do { fputs((X),(Y)); } while (0)
+# define PUTC(X,Y) do { putc((X),(Y)); } while (0)
+#endif
+
+
+/*
+ * word - initialise a word and set next to NULL
+ */
+
+word::word (const char *w, int n)
+ : next(0)
+{
+ s = new char[n+1];
+ strncpy(s, w, n);
+ s[n] = (char)0;
+}
+
+/*
+ * destroy word and the string copy.
+ */
+
+word::~word ()
+{
+ delete[] s;
+}
+
+/*
+ * word_list - create an empty word list.
+ */
+
+word_list::word_list ()
+ : length(0), head(0), tail(0)
+{
+}
+
+/*
+ * flush - flush a word list to a FILE, f, and return the
+ * length of the buffered string.
+ */
+
+int word_list::flush (FILE *f)
+{
+ word *t;
+ int len=length;
+
+ while (head != 0) {
+ t = head;
+ head = head->next;
+ FPUTS(t->s, f);
+ delete t;
+ }
+ head = 0;
+ tail = 0;
+ length = 0;
+#if defined(DEBUGGING)
+ fflush(f); // just for testing
+#endif
+ return( len );
+}
+
+/*
+ * add_word - adds a word to the outstanding word list.
+ */
+
+void word_list::add_word (const char *s, int n)
+{
+ if (head == 0) {
+ head = new word(s, n);
+ tail = head;
+ } else {
+ tail->next = new word(s, n);
+ tail = tail->next;
+ }
+ length += n;
+}
+
+/*
+ * get_length - returns the number of characters buffered
+ */
+
+int word_list::get_length (void)
+{
+ return( length );
+}
+
+/*
+ * the classes and methods for simple_output manipulation
+ */
+
+simple_output::simple_output(FILE *f, int n)
+: fp(f), max_line_length(n), col(0), fixed_point(0), newlines(0)
+{
+}
+
+simple_output &simple_output::set_file(FILE *f)
+{
+ if (fp)
+ fflush(fp);
+ fp = f;
+ return *this;
+}
+
+simple_output &simple_output::copy_file(FILE *infp)
+{
+ int c;
+ while ((c = getc(infp)) != EOF)
+ PUTC(c, fp);
+ return *this;
+}
+
+simple_output &simple_output::end_line()
+{
+ flush_last_word();
+ if (col != 0) {
+ PUTC('\n', fp);
+ col = 0;
+ }
+ return *this;
+}
+
+simple_output &simple_output::special(const char *)
+{
+ return *this;
+}
+
+simple_output &simple_output::simple_comment(const char *s)
+{
+ flush_last_word();
+ if (col != 0)
+ PUTC('\n', fp);
+ FPUTS("<!-- ", fp);
+ FPUTS(s, fp);
+ FPUTS(" -->\n", fp);
+ col = 0;
+ return *this;
+}
+
+simple_output &simple_output::begin_comment(const char *s)
+{
+ flush_last_word();
+ if (col != 0)
+ PUTC('\n', fp);
+ col = 0;
+ put_string("<!--");
+ space_or_newline();
+ last_word.add_word(s, strlen(s));
+ return *this;
+}
+
+simple_output &simple_output::end_comment()
+{
+ flush_last_word();
+ space_or_newline();
+ put_string("-->").nl();
+ return *this;
+}
+
+/*
+ * check_newline - checks to see whether we are able to issue
+ * a newline and that one is needed.
+ */
+
+simple_output &simple_output::check_newline(int n)
+{
+ if ((col + n + last_word.get_length() + 1 > max_line_length) && (newlines)) {
+ FPUTC('\n', fp);
+ col = last_word.flush(fp);
+ }
+ return *this;
+}
+
+/*
+ * space_or_newline - will emit a newline or a space later on
+ * depending upon the current column.
+ */
+
+simple_output &simple_output::space_or_newline (void)
+{
+ if ((col + last_word.get_length() + 1 > max_line_length) && (newlines)) {
+ FPUTC('\n', fp);
+ if (last_word.get_length() > 0) {
+ col = last_word.flush(fp);
+ } else {
+ col = 0;
+ }
+ } else {
+ if (last_word.get_length() != 0) {
+ if (col > 0) {
+ FPUTC(' ', fp);
+ col++;
+ }
+ col += last_word.flush(fp);
+ }
+ }
+ return *this;
+}
+
+/*
+ * force_nl - forces a newline.
+ */
+
+simple_output &simple_output::force_nl (void)
+{
+ space_or_newline();
+ col += last_word.flush(fp);
+ FPUTC('\n', fp);
+ col = 0;
+ return *this ;
+}
+
+/*
+ * nl - writes a newline providing that we
+ * are not in the first column.
+ */
+
+simple_output &simple_output::nl (void)
+{
+ space_or_newline();
+ col += last_word.flush(fp);
+ FPUTC('\n', fp);
+ col = 0;
+ return *this ;
+}
+
+simple_output &simple_output::set_fixed_point(int n)
+{
+ assert(n >= 0 && n <= 10);
+ fixed_point = n;
+ return *this;
+}
+
+simple_output &simple_output::put_raw_char(char c)
+{
+ col += last_word.flush(fp);
+ PUTC(c, fp);
+ col++;
+ return *this;
+}
+
+simple_output &simple_output::put_string(const char *s, int n)
+{
+ last_word.add_word(s, n);
+ return *this;
+}
+
+simple_output &simple_output::put_string(const char *s)
+{
+ last_word.add_word(s, strlen(s));
+ return *this;
+}
+
+simple_output &simple_output::put_string(const string &s)
+{
+ last_word.add_word(s.contents(), s.length());
+ return *this;
+}
+
+simple_output &simple_output::put_number(int n)
+{
+ char buf[1 + INT_DIGITS + 1];
+ sprintf(buf, "%d", n);
+ put_string(buf);
+ return *this;
+}
+
+simple_output &simple_output::put_float(double d)
+{
+ char buf[128];
+
+ sprintf(buf, "%.4f", d);
+ put_string(buf);
+ return *this;
+}
+
+simple_output &simple_output::enable_newlines (int auto_newlines)
+{
+ check_newline(0);
+ newlines = auto_newlines;
+ check_newline(0);
+ return *this;
+}
+
+/*
+ * flush_last_word - flushes the last word and adjusts the
+ * col position. It will insert a newline
+ * before the last word if allowed and if
+ * necessary.
+ */
+
+void simple_output::flush_last_word (void)
+{
+ int len=last_word.get_length();
+
+ if (len > 0) {
+ if (newlines) {
+ if (col + len + 1 > max_line_length) {
+ FPUTS("\n", fp);
+ col = 0;
+ } else {
+ FPUTS(" ", fp);
+ col++;
+ }
+ len += last_word.flush(fp);
+ } else {
+ FPUTS(" ", fp);
+ col++;
+ col += last_word.flush(fp);
+ }
+ }
+}
diff --git a/src/devices/grohtml/post-html.cpp b/src/devices/grohtml/post-html.cpp
new file mode 100644
index 0000000..4e02b5c
--- /dev/null
+++ b/src/devices/grohtml/post-html.cpp
@@ -0,0 +1,5684 @@
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ *
+ * Gaius Mulley (gaius@glam.ac.uk) wrote post-html.cpp
+ * but it owes a huge amount of ideas and raw code from
+ * James Clark (jjc@jclark.com) grops/ps.cpp.
+ */
+
+/*
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "html.h"
+#include "html-text.h"
+#include "html-table.h"
+#include "curtime.h"
+
+#include <time.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+
+extern "C" const char *Version_string;
+
+#if !defined(TRUE)
+# define TRUE (1==1)
+#endif
+#if !defined(FALSE)
+# define FALSE (1==0)
+#endif
+
+#define MAX_LINE_LENGTH 60 /* maximum characters we want in a line */
+#define SIZE_INCREMENT 2 /* font size increment <big> = +2 */
+#define CENTER_TOLERANCE 2 /* how many pixels off center do we allow */
+#define ANCHOR_TEMPLATE "heading" /* if simple anchor is set we use this */
+#define UNICODE_DESC_START 0x80 /* all character entities above this are */
+ /* either encoded by their glyph names or if */
+ /* there is no name then we use &#nnn; */
+typedef enum {CENTERED, LEFT, RIGHT, INLINE} TAG_ALIGNMENT;
+typedef enum {col_tag, tab_tag, tab0_tag, none} colType;
+
+#undef DEBUG_TABLES
+// #define DEBUG_TABLES
+
+/*
+ * prototypes
+ */
+
+const char *get_html_translation (font *f, const string &name);
+static const char *get_html_entity(unsigned int code);
+int char_translate_to_html (font *f, char *buf, int buflen, unsigned char ch, int b, int and_single);
+
+
+static int auto_links = TRUE; /* by default we enable automatic links at */
+ /* top of the document. */
+static int auto_rule = TRUE; /* by default we enable an automatic rule */
+ /* at the top and bottom of the document */
+static int simple_anchors = FALSE; /* default to anchors with heading text */
+static int manufacture_headings = FALSE; /* default is to use the Hn html headings, */
+ /* rather than manufacture our own. */
+static int do_write_creator_comment = TRUE; /* write Creator HTML comment */
+static int do_write_date_comment = TRUE; /* write CreationDate HTML comment */
+static color *default_background = 0; /* has user requested initial bg color? */
+static string job_name; /* if set then the output is split into */
+ /* multiple files with 'job_name'-%d.html */
+static int multiple_files = FALSE; /* must we the output be divided into */
+ /* multiple html files, one for each */
+ /* heading? */
+static int base_point_size = 0; /* which troff font size maps onto html */
+ /* size 3? */
+static int split_level = 2; /* what heading level to split at? */
+static string head_info; /* user supplied information to be placed */
+ /* into <head> </head> */
+static int valid_flag = FALSE; /* has user requested a valid flag at the */
+ /* end of each page? */
+static int groff_sig = FALSE; /* "This document was produced using" */
+html_dialect dialect = html4; /* which html dialect should grohtml output */
+
+
+/*
+ * start with a few favorites
+ */
+
+void stop () {}
+
+static int min (int a, int b)
+{
+ if (a < b)
+ return a;
+ else
+ return b;
+}
+
+static int max (int a, int b)
+{
+ if (a > b)
+ return a;
+ else
+ return b;
+}
+
+/*
+ * is_intersection - returns TRUE if range a1..a2 intersects with
+ * b1..b2
+ */
+
+static int is_intersection (int a1, int a2, int b1, int b2)
+{
+ // easier to prove NOT outside limits
+ return ! ((a1 > b2) || (a2 < b1));
+}
+
+/*
+ * is_digit - returns TRUE if character, ch, is a digit.
+ */
+
+static int is_digit (char ch)
+{
+ return (ch >= '0') && (ch <= '9');
+}
+
+/*
+ * the classes and methods for maintaining a list of files.
+ */
+
+struct file {
+ FILE *fp;
+ file *next;
+ int new_output_file;
+ int require_links;
+ string output_file_name;
+
+ file (FILE *f);
+};
+
+/*
+ * file - initialize all fields to null pointers
+ */
+
+file::file (FILE *f)
+ : fp(f), next(0), new_output_file(FALSE),
+ require_links(FALSE), output_file_name("")
+{
+}
+
+class files {
+public:
+ files ();
+ FILE *get_file (void);
+ void start_of_list (void);
+ void move_next (void);
+ void add_new_file (FILE *f);
+ void set_file_name (string name);
+ void set_links_required (void);
+ int are_links_required (void);
+ int is_new_output_file (void);
+ string file_name (void);
+ string next_file_name (void);
+private:
+ file *head;
+ file *tail;
+ file *ptr;
+};
+
+/*
+ * files - create an empty list of files.
+ */
+
+files::files ()
+ : head(0), tail(0), ptr(0)
+{
+}
+
+/*
+ * get_file - returns the FILE associated with ptr.
+ */
+
+FILE *files::get_file (void)
+{
+ if (ptr)
+ return ptr->fp;
+ else
+ return 0;
+}
+
+/*
+ * start_of_list - reset the ptr to the start of the list.
+ */
+
+void files::start_of_list (void)
+{
+ ptr = head;
+}
+
+/*
+ * move_next - moves the ptr to the next element on the list.
+ */
+
+void files::move_next (void)
+{
+ if (ptr != 0)
+ ptr = ptr->next;
+}
+
+/*
+ * add_new_file - adds a new file, f, to the list.
+ */
+
+void files::add_new_file (FILE *f)
+{
+ if (0 /* nullptr */ == head) {
+ head = new file(f);
+ tail = head;
+ } else {
+ tail->next = new file(f);
+ tail = tail->next;
+ }
+ ptr = tail;
+}
+
+/*
+ * set_file_name - sets the final file name to contain the html
+ * data to name.
+ */
+
+void files::set_file_name (string name)
+{
+ if (ptr != 0) {
+ ptr->output_file_name = name;
+ ptr->new_output_file = TRUE;
+ }
+}
+
+/*
+ * set_links_required - issue links when processing this component
+ * of the file.
+ */
+
+void files::set_links_required (void)
+{
+ if (ptr != 0)
+ ptr->require_links = TRUE;
+}
+
+/*
+ * are_links_required - returns TRUE if this section of the file
+ * requires that links should be issued.
+ */
+
+int files::are_links_required (void)
+{
+ if (ptr != 0)
+ return ptr->require_links;
+ return FALSE;
+}
+
+/*
+ * is_new_output_file - returns TRUE if this component of the file
+ * is the start of a new output file.
+ */
+
+int files::is_new_output_file (void)
+{
+ if (ptr != 0)
+ return ptr->new_output_file;
+ return FALSE;
+}
+
+/*
+ * file_name - returns the name of the file.
+ */
+
+string files::file_name (void)
+{
+ if (ptr != 0)
+ return ptr->output_file_name;
+ return string("");
+}
+
+/*
+ * next_file_name - returns the name of the next file.
+ */
+
+string files::next_file_name (void)
+{
+ if (ptr != 0 && ptr->next != 0)
+ return ptr->next->output_file_name;
+ return string("");
+}
+
+/*
+ * the class and methods for styles
+ */
+
+struct style {
+ font *f;
+ int point_size;
+ int font_no;
+ int height;
+ int slant;
+ color col;
+ style ();
+ style (font *, int, int, int, int, color);
+ int operator == (const style &) const;
+ int operator != (const style &) const;
+};
+
+style::style()
+ : f(0), point_size(-1)
+{
+}
+
+style::style(font *p, int sz, int h, int sl, int no, color c)
+ : f(p), point_size(sz), font_no(no), height(h), slant(sl), col(c)
+{
+}
+
+int style::operator==(const style &s) const
+{
+ return (f == s.f && point_size == s.point_size
+ && height == s.height && slant == s.slant && col == s.col);
+}
+
+int style::operator!=(const style &s) const
+{
+ return !(*this == s);
+}
+
+/*
+ * the class and methods for retaining ascii text
+ */
+
+struct char_block {
+ enum { SIZE = 256 };
+ char *buffer;
+ int used;
+ char_block *next;
+
+ char_block();
+ char_block(int length);
+ ~char_block();
+};
+
+char_block::char_block()
+: buffer(0), used(0), next(0)
+{
+}
+
+char_block::char_block(int length)
+: used(0), next(0)
+{
+ buffer = new char[max(length, char_block::SIZE)];
+ if (0 /* nullptr */ == buffer)
+ fatal("out of memory error");
+}
+
+char_block::~char_block()
+{
+ if (buffer != 0)
+ delete[] buffer;
+}
+
+class char_buffer {
+public:
+ char_buffer();
+ ~char_buffer();
+ char *add_string(const char *, unsigned int);
+ char *add_string(const string &);
+private:
+ char_block *head;
+ char_block *tail;
+};
+
+char_buffer::char_buffer()
+: head(0), tail(0)
+{
+}
+
+char_buffer::~char_buffer()
+{
+ while (head != 0) {
+ char_block *temp = head;
+ head = head->next;
+ delete temp;
+ }
+}
+
+char *char_buffer::add_string (const char *s, unsigned int length)
+{
+ int i = 0;
+ unsigned int old_used;
+
+ if (0 /* nullptr */ == s|| length == 0)
+ return 0;
+
+ if (0 /* nullptr */ == tail) {
+ tail = new char_block(length+1);
+ head = tail;
+ } else {
+ if (tail->used + length+1 > char_block::SIZE) {
+ tail->next = new char_block(length+1);
+ tail = tail->next;
+ }
+ }
+
+ old_used = tail->used;
+ do {
+ tail->buffer[tail->used] = s[i];
+ tail->used++;
+ i++;
+ length--;
+ } while (length>0);
+
+ // add terminating nul character
+
+ tail->buffer[tail->used] = '\0';
+ tail->used++;
+
+ // and return start of new string
+
+ return &tail->buffer[old_used];
+}
+
+char *char_buffer::add_string (const string &s)
+{
+ return add_string(s.contents(), s.length());
+}
+
+/*
+ * the classes and methods for maintaining glyph positions.
+ */
+
+class text_glob {
+public:
+ void text_glob_html (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_special (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_line (style *s,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int thickness);
+ void text_glob_auto_image(style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void text_glob_tag (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+
+ text_glob (void);
+ ~text_glob (void);
+ int is_a_line (void);
+ int is_a_tag (void);
+ int is_eol (void);
+ int is_auto_img (void);
+ int is_br (void);
+ int is_in (void);
+ int is_po (void);
+ int is_ti (void);
+ int is_ll (void);
+ int is_ce (void);
+ int is_tl (void);
+ int is_eo_tl (void);
+ int is_eol_ce (void);
+ int is_col (void);
+ int is_tab (void);
+ int is_tab0 (void);
+ int is_ta (void);
+ int is_tab_ts (void);
+ int is_tab_te (void);
+ int is_nf (void);
+ int is_fi (void);
+ int is_eo_h (void);
+ int get_arg (void);
+ int get_tab_args (char *align);
+
+ void remember_table (html_table *t);
+ html_table *get_table (void);
+
+ style text_style;
+ const char *text_string;
+ unsigned int text_length;
+ int minv, minh, maxv, maxh;
+ int is_tag; // is this a .br, .sp, .tl etc
+ int is_img_auto; // image created by eqn delim
+ int is_special; // text has come via 'x X html:'
+ int is_line; // is the command a <line>?
+ int thickness; // the thickness of a line
+ html_table *tab; // table description
+
+private:
+ text_glob (style *s, const char *str, int length,
+ int min_vertical , int min_horizontal,
+ int max_vertical , int max_horizontal,
+ bool is_troff_command,
+ bool is_auto_image, bool is_special_command,
+ bool is_a_line , int thickness);
+};
+
+text_glob::text_glob (style *s, const char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ bool is_troff_command,
+ bool is_auto_image, bool is_special_command,
+ bool is_a_line_flag, int line_thickness)
+ : text_style(*s), text_string(str), text_length(length),
+ minv(min_vertical), minh(min_horizontal), maxv(max_vertical),
+ maxh(max_horizontal), is_tag(is_troff_command),
+ is_img_auto(is_auto_image), is_special(is_special_command),
+ is_line(is_a_line_flag), thickness(line_thickness), tab(0)
+{
+}
+
+text_glob::text_glob ()
+ : text_string(0), text_length(0), minv(-1), minh(-1), maxv(-1),
+ maxh(-1), is_tag(FALSE), is_special(FALSE), is_line(FALSE),
+ thickness(0), tab(0)
+{
+}
+
+text_glob::~text_glob ()
+{
+ if (tab != 0)
+ delete tab;
+}
+
+/*
+ * text_glob_html - used to place html text into the glob buffer.
+ */
+
+void text_glob::text_glob_html (style *s, char *str, int length,
+ int min_vertical , int min_horizontal,
+ int max_vertical , int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_html - used to place html specials into the glob buffer.
+ * This text is essentially html commands coming
+ * through from the macro sets, with special
+ * designated sequences of characters translated into
+ * html. See add_and_encode.
+ */
+
+void text_glob::text_glob_special (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, TRUE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_line - record horizontal draw line commands.
+ */
+
+void text_glob::text_glob_line (style *s,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int thickness_value)
+{
+ text_glob *g = new text_glob(s, "", 0,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ FALSE, FALSE, FALSE, TRUE,
+ thickness_value);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_auto_image - record the presence of a .auto-image tag
+ * command. Used to mark that an image has been
+ * created automatically by a preprocessor and
+ * (pre-grohtml/troff) combination. Under some
+ * circumstances images may not be created.
+ * (consider .EQ
+ * delim $$
+ * .EN
+ * .TS
+ * tab(!), center;
+ * l!l.
+ * $1 over x$!recripical of x
+ * .TE
+ * the first auto-image marker is created via
+ * .EQ/.EN pair and no image is created. The
+ * second auto-image marker occurs at $1 over
+ * x$ Currently this image will not be created
+ * as the whole of the table is created as an
+ * image. (Once html tables are handled by
+ * grohtml this will change. Shortly this will
+ * be the case).
+ */
+
+void text_glob::text_glob_auto_image(style *s, char *str, int length,
+ int min_vertical,
+ int min_horizontal,
+ int max_vertical,
+ int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ TRUE, TRUE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * text_glob_tag - records a troff tag.
+ */
+
+void text_glob::text_glob_tag (style *s, char *str, int length,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ text_glob *g = new text_glob(s, str, length,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal,
+ TRUE, FALSE, FALSE, FALSE, 0);
+ *this = *g;
+ delete g;
+}
+
+/*
+ * is_a_line - returns TRUE if glob should be converted into an <hr>
+ */
+
+int text_glob::is_a_line (void)
+{
+ return is_line;
+}
+
+/*
+ * is_a_tag - returns TRUE if glob contains a troff directive.
+ */
+
+int text_glob::is_a_tag (void)
+{
+ return is_tag;
+}
+
+/*
+ * is_eol - returns TRUE if glob contains the tag eol
+ */
+
+int text_glob::is_eol (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eol") == 0);
+}
+
+/*
+ * is_eol_ce - returns TRUE if glob contains the tag eol.ce
+ */
+
+int text_glob::is_eol_ce (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:eol.ce") == 0);
+}
+
+/*
+ * is_tl - returns TRUE if glob contains the tag .tl
+ */
+
+int text_glob::is_tl (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tl") == 0);
+}
+
+/*
+ * is_eo_tl - returns TRUE if glob contains the tag eo.tl
+ */
+
+int text_glob::is_eo_tl (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eo.tl") == 0);
+}
+
+/*
+ * is_nf - returns TRUE if glob contains the tag .fi 0
+ */
+
+int text_glob::is_nf (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.fi",
+ strlen("devtag:.fi")) == 0) &&
+ (get_arg() == 0);
+}
+
+/*
+ * is_fi - returns TRUE if glob contains the tag .fi 1
+ */
+
+int text_glob::is_fi (void)
+{
+ return (is_tag && (strncmp(text_string, "devtag:.fi",
+ strlen("devtag:.fi")) == 0) &&
+ (get_arg() == 1));
+}
+
+/*
+ * is_eo_h - returns TRUE if glob contains the tag .eo.h
+ */
+
+int text_glob::is_eo_h (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.eo.h") == 0);
+}
+
+/*
+ * is_ce - returns TRUE if glob contains the tag .ce
+ */
+
+int text_glob::is_ce (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ce",
+ strlen("devtag:.ce")) == 0);
+}
+
+/*
+ * is_in - returns TRUE if glob contains the tag .in
+ */
+
+int text_glob::is_in (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.in ",
+ strlen("devtag:.in ")) == 0);
+}
+
+/*
+ * is_po - returns TRUE if glob contains the tag .po
+ */
+
+int text_glob::is_po (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.po ",
+ strlen("devtag:.po ")) == 0);
+}
+
+/*
+ * is_ti - returns TRUE if glob contains the tag .ti
+ */
+
+int text_glob::is_ti (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ti ",
+ strlen("devtag:.ti ")) == 0);
+}
+
+/*
+ * is_ll - returns TRUE if glob contains the tag .ll
+ */
+
+int text_glob::is_ll (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ll ",
+ strlen("devtag:.ll ")) == 0);
+}
+
+/*
+ * is_col - returns TRUE if glob contains the tag .col
+ */
+
+int text_glob::is_col (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.col",
+ strlen("devtag:.col")) == 0);
+}
+
+/*
+ * is_tab_ts - returns TRUE if glob contains the tag .tab_ts
+ */
+
+int text_glob::is_tab_ts (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tab-ts") == 0);
+}
+
+/*
+ * is_tab_te - returns TRUE if glob contains the tag .tab_te
+ */
+
+int text_glob::is_tab_te (void)
+{
+ return is_tag && (strcmp(text_string, "devtag:.tab-te") == 0);
+}
+
+/*
+ * is_ta - returns TRUE if glob contains the tag .ta
+ */
+
+int text_glob::is_ta (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:.ta ",
+ strlen("devtag:.ta ")) == 0);
+}
+
+/*
+ * is_tab - returns TRUE if glob contains the tag tab
+ */
+
+int text_glob::is_tab (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:tab ",
+ strlen("devtag:tab ")) == 0);
+}
+
+/*
+ * is_tab0 - returns TRUE if glob contains the tag tab0
+ */
+
+int text_glob::is_tab0 (void)
+{
+ return is_tag && (strncmp(text_string, "devtag:tab0",
+ strlen("devtag:tab0")) == 0);
+}
+
+/*
+ * is_auto_img - returns TRUE if the glob contains an automatically
+ * generated image.
+ */
+
+int text_glob::is_auto_img (void)
+{
+ return is_img_auto;
+}
+
+/*
+ * is_br - returns TRUE if the glob is a tag containing a .br
+ * or an implied .br. Note that we do not include .nf or .fi
+ * as grohtml will place a .br after these commands if they
+ * should break the line.
+ */
+
+int text_glob::is_br (void)
+{
+ return is_a_tag() && ((strcmp ("devtag:.br", text_string) == 0) ||
+ (strncmp("devtag:.sp", text_string,
+ strlen("devtag:.sp")) == 0));
+}
+
+int text_glob::get_arg (void)
+{
+ if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
+ const char *p = text_string;
+
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ if (*p == (char)0)
+ return -1;
+ return atoi(p);
+ }
+ return -1;
+}
+
+/*
+ * get_tab_args - returns the tab position and alignment of the tab tag
+ */
+
+int text_glob::get_tab_args (char *align)
+{
+ if (strncmp("devtag:", text_string, strlen("devtag:")) == 0) {
+ const char *p = text_string;
+
+ // firstly the alignment C|R|L
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ *align = *p;
+ // now the int value
+ while ((*p != (char)0) && (!isspace(*p)))
+ p++;
+ while ((*p != (char)0) && (isspace(*p)))
+ p++;
+ if (*p == (char)0)
+ return -1;
+ return atoi(p);
+ }
+ return -1;
+}
+
+/*
+ * remember_table - saves table, t, in the text_glob.
+ */
+
+void text_glob::remember_table (html_table *t)
+{
+ if (tab != 0)
+ delete tab;
+ tab = t;
+}
+
+/*
+ * get_table - returns the stored table description.
+ */
+
+html_table *text_glob::get_table (void)
+{
+ return tab;
+}
+
+/*
+ * the class and methods used to construct ordered double linked
+ * lists. In a previous implementation we used templates via
+ * #include "ordered-list.h", but this does assume that all C++
+ * compilers can handle this feature. Pragmatically it is safer to
+ * assume this is not the case.
+ */
+
+struct element_list {
+ element_list *right;
+ element_list *left;
+ text_glob *datum;
+ int lineno;
+ int minv, minh, maxv, maxh;
+
+ element_list (text_glob *d,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ element_list ();
+ ~element_list ();
+};
+
+element_list::element_list ()
+ : right(0), left(0), datum(0), lineno(0), minv(-1), minh(-1),
+ maxv(-1), maxh(-1)
+{
+}
+
+/*
+ * element_list - create a list element assigning the datum and region
+ * parameters.
+ */
+
+element_list::element_list (text_glob *in,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+ : right(0), left(0), datum(in), lineno(line_number),
+ minv(min_vertical), minh(min_horizontal),
+ maxv(max_vertical), maxh(max_horizontal)
+{
+}
+
+element_list::~element_list ()
+{
+ if (datum != 0)
+ delete datum;
+}
+
+class list {
+public:
+ list ();
+ ~list ();
+ int is_less (element_list *a, element_list *b);
+ void add (text_glob *in,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void sub_move_right (void);
+ void move_right (void);
+ void move_left (void);
+ int is_empty (void);
+ int is_equal_to_tail (void);
+ int is_equal_to_head (void);
+ void start_from_head (void);
+ void start_from_tail (void);
+ void insert (text_glob *in);
+ void move_to (text_glob *in);
+ text_glob *move_right_get_data (void);
+ text_glob *move_left_get_data (void);
+ text_glob *get_data (void);
+private:
+ element_list *head;
+ element_list *tail;
+ element_list *ptr;
+};
+
+/*
+ * list - construct an empty list.
+ */
+
+list::list ()
+ : head(0), tail(0), ptr(0)
+{
+}
+
+/*
+ * ~list - destroy a complete list.
+ */
+
+list::~list()
+{
+ element_list *temp=head;
+
+ do {
+ temp = head;
+ if (temp != 0) {
+ head = head->right;
+ delete temp;
+ }
+ } while ((head != 0) && (head != tail));
+}
+
+/*
+ * is_less - returns TRUE if a is left of b if on the same line or
+ * if a is higher up the page than b.
+ */
+
+int list::is_less (element_list *a, element_list *b)
+{
+ // was:
+ // if (is_intersection(a->minv+1, a->maxv-1, b->minv+1, b->maxv-1)) {
+ if (a->lineno < b->lineno) {
+ return TRUE;
+ } else if (a->lineno > b->lineno) {
+ return FALSE;
+ } else if (is_intersection(a->minv, a->maxv, b->minv, b->maxv)) {
+ return (a->minh < b->minh);
+ } else {
+ return (a->maxv < b->maxv);
+ }
+}
+
+/*
+ * add - adds a datum to the list in the order specified by the
+ * region position.
+ */
+
+void list::add (text_glob *in, int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ // create a new list element with datum and position fields
+ // initialized
+ element_list *t = new element_list(in, line_number,
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ element_list *last;
+
+#if 0
+ fprintf(stderr, "[%s %d,%d,%d,%d] ",
+ in->text_string, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ fflush(stderr);
+#endif
+
+ if (0 /* nullptr */ == head) {
+ head = t;
+ tail = t;
+ ptr = t;
+ t->left = t;
+ t->right = t;
+ } else {
+ last = tail;
+
+ while ((last != head) && (is_less(t, last)))
+ last = last->left;
+
+ if (is_less(t, last)) {
+ t->right = last;
+ last->left->right = t;
+ t->left = last->left;
+ last->left = t;
+ // now check for a new head
+ if (last == head)
+ head = t;
+ } else {
+ // add t beyond last
+ t->right = last->right;
+ t->left = last;
+ last->right->left = t;
+ last->right = t;
+ // now check for a new tail
+ if (last == tail)
+ tail = t;
+ }
+ }
+}
+
+/*
+ * sub_move_right - removes the element which is currently pointed to
+ * by ptr from the list and moves ptr to the right.
+ */
+
+void list::sub_move_right (void)
+{
+ element_list *t=ptr->right;
+
+ if (head == tail) {
+ head = 0;
+ if (tail != 0)
+ delete tail;
+
+ tail = 0;
+ ptr = 0;
+ } else {
+ if (head == ptr)
+ head = head->right;
+ if (tail == ptr)
+ tail = tail->left;
+ ptr->left->right = ptr->right;
+ ptr->right->left = ptr->left;
+ ptr = t;
+ }
+}
+
+/*
+ * start_from_head - assigns ptr to the head.
+ */
+
+void list::start_from_head (void)
+{
+ ptr = head;
+}
+
+/*
+ * start_from_tail - assigns ptr to the tail.
+ */
+
+void list::start_from_tail (void)
+{
+ ptr = tail;
+}
+
+/*
+ * is_empty - returns TRUE if the list has no elements.
+ */
+
+int list::is_empty (void)
+{
+ return 0 /* nullptr */ == head;
+}
+
+/*
+ * is_equal_to_tail - returns TRUE if the ptr equals the tail.
+ */
+
+int list::is_equal_to_tail (void)
+{
+ return ptr == tail;
+}
+
+/*
+ * is_equal_to_head - returns TRUE if the ptr equals the head.
+ */
+
+int list::is_equal_to_head (void)
+{
+ return ptr == head;
+}
+
+/*
+ * move_left - moves the ptr left.
+ */
+
+void list::move_left (void)
+{
+ ptr = ptr->left;
+}
+
+/*
+ * move_right - moves the ptr right.
+ */
+
+void list::move_right (void)
+{
+ ptr = ptr->right;
+}
+
+/*
+ * get_datum - returns the datum referenced via ptr.
+ */
+
+text_glob* list::get_data (void)
+{
+ return ptr->datum;
+}
+
+/*
+ * move_right_get_data - returns the datum referenced via ptr and moves
+ * ptr right.
+ */
+
+text_glob* list::move_right_get_data (void)
+{
+ ptr = ptr->right;
+ if (ptr == head)
+ return 0;
+ else
+ return ptr->datum;
+}
+
+/*
+ * move_left_get_data - returns the datum referenced via ptr and moves
+ * ptr right.
+ */
+
+text_glob* list::move_left_get_data (void)
+{
+ ptr = ptr->left;
+ if (ptr == tail)
+ return 0;
+ else
+ return ptr->datum;
+}
+
+/*
+ * insert - inserts data after the current position.
+ */
+
+void list::insert (text_glob *in)
+{
+ if (is_empty())
+ fatal("list must not be empty if we are inserting data");
+ else {
+ if (0 /* nullptr */ == ptr)
+ ptr = head;
+
+ element_list *t = new element_list(in, ptr->lineno,
+ ptr->minv, ptr->minh,
+ ptr->maxv, ptr->maxh);
+ if (ptr == tail)
+ tail = t;
+ ptr->right->left = t;
+ t->right = ptr->right;
+ ptr->right = t;
+ t->left = ptr;
+ }
+}
+
+/*
+ * move_to - moves the current position to the point where data, in,
+ * exists. This is an expensive method and should be used
+ * sparingly.
+ */
+
+void list::move_to (text_glob *in)
+{
+ ptr = head;
+ while (ptr != tail && ptr->datum != in)
+ ptr = ptr->right;
+}
+
+/*
+ * page class and methods
+ */
+
+class page {
+public:
+ page (void);
+ void add (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void add_tag (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal);
+ void add_and_encode (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int is_tag);
+ void add_line (style *s,
+ int line_number,
+ int x1, int y1, int x2, int y2,
+ int thickness);
+ void insert_tag (const string &str);
+ void dump_page (void); // debugging method
+
+ // and the data
+
+ list glyphs; // position of glyphs and specials on page
+ char_buffer buffer; // all characters for this page
+};
+
+page::page()
+{
+}
+
+/*
+ * insert_tag - inserts a tag after the current position.
+ */
+
+void page::insert_tag (const string &str)
+{
+ if (str.length() > 0) {
+ text_glob *g=new text_glob();
+ text_glob *f=glyphs.get_data();
+ g->text_glob_tag(&f->text_style, buffer.add_string(str),
+ str.length(), f->minv, f->minh, f->maxv, f->maxh);
+ glyphs.insert(g);
+ }
+}
+
+/*
+ * add - add html text to the list of glyphs.
+ */
+
+void page::add (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ if (str.length() > 0) {
+ text_glob *g=new text_glob();
+ g->text_glob_html(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ glyphs.add(g, line_number, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * add_tag - adds a troff tag, for example: .tl .sp .br
+ */
+
+void page::add_tag (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal)
+{
+ if (str.length() > 0) {
+ text_glob *g;
+
+ if (strncmp((str+'\0').contents(), "devtag:.auto-image",
+ strlen("devtag:.auto-image")) == 0) {
+ g = new text_glob();
+ g->text_glob_auto_image(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ } else {
+ g = new text_glob();
+ g->text_glob_tag(s, buffer.add_string(str), str.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+ glyphs.add(g, line_number, min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * add_line - adds the <line> primitive providing that y1==y2
+ */
+
+void page::add_line (style *s,
+ int line_number,
+ int x_1, int y_1, int x_2, int y_2,
+ int thickness)
+{
+ if (y_1 == y_2) {
+ text_glob *g = new text_glob();
+ g->text_glob_line(s,
+ min(y_1, y_2), min(x_1, x_2),
+ max(y_1, y_2), max(x_1, x_2),
+ thickness);
+ glyphs.add(g, line_number,
+ min(y_1, y_2), min(x_1, x_2),
+ max(y_1, y_2), max(x_1, x_2));
+ }
+}
+
+/*
+ * to_unicode - returns a unicode translation of int, ch.
+ */
+
+static char *to_unicode (unsigned int ch)
+{
+ static char buf[30];
+
+ sprintf(buf, "&#%u;", ch);
+ return buf;
+}
+
+/*
+ * add_and_encode - adds a special string to the page, it translates
+ * the string into html glyphs. The special string
+ * will have come from x X html: and can contain troff
+ * character encodings which appear as \[char]. A
+ * sequence of \\ represents \.
+ * So for example we can write:
+ * "cost = \[Po]3.00 file = \\foo\\bar"
+ * which is translated into:
+ * "cost = &pound;3.00 file = \foo\bar"
+ */
+
+void page::add_and_encode (style *s, const string &str,
+ int line_number,
+ int min_vertical, int min_horizontal,
+ int max_vertical, int max_horizontal,
+ int is_tag)
+{
+ string html_string;
+ const char *html_glyph;
+ int i = 0;
+ const int len = str.length();
+
+ if (0 /* nullptr */ == s->f)
+ return;
+ while (i < len) {
+ if ((i + 1 < len) && (str.substring(i, 2) == string("\\["))) {
+ // start of escape
+ i += 2; // move over \[
+ int a = i;
+ while ((i < len) && (str[i] != ']'))
+ i++;
+ if (i > 0) {
+ string troff_charname = str.substring(a, i - a);
+ html_glyph = get_html_translation(s->f, troff_charname);
+ if (html_glyph)
+ html_string += html_glyph;
+ else {
+ glyph *g = name_to_glyph((troff_charname + '\0').contents());
+ if (s->f->contains(g))
+ html_string += s->f->get_code(g);
+ }
+ }
+ }
+ else
+ html_string += str[i];
+ i++;
+ }
+ if (html_string.length() > 0) {
+ text_glob *g=new text_glob();
+ if (is_tag)
+ g->text_glob_tag(s, buffer.add_string(html_string),
+ html_string.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ else
+ g->text_glob_special(s, buffer.add_string(html_string),
+ html_string.length(),
+ min_vertical, min_horizontal,
+ max_vertical, max_horizontal);
+ glyphs.add(g, line_number, min_vertical,
+ min_horizontal, max_vertical, max_horizontal);
+ }
+}
+
+/*
+ * dump_page - dump the page contents for debugging purposes.
+ */
+
+void page::dump_page(void)
+{
+#if defined(DEBUG_TABLES)
+ text_glob *old_pos = glyphs.get_data();
+ text_glob *g;
+
+ printf("\n<!--\n");
+ printf("\n\ndebugging start\n");
+ glyphs.start_from_head();
+ do {
+ g = glyphs.get_data();
+ if (g->is_tab_ts()) {
+ printf("\n\n");
+ if (g->get_table() != 0)
+ g->get_table()->dump_table();
+ }
+ printf("%s ", g->text_string);
+ if (g->is_tab_te())
+ printf("\n\n");
+ glyphs.move_right();
+ } while (! glyphs.is_equal_to_head());
+ glyphs.move_to(old_pos);
+ printf("\ndebugging end\n\n");
+ printf("\n-->\n");
+ fflush(stdout);
+#endif
+}
+
+/*
+ * font classes and methods
+ */
+
+class html_font : public font {
+ html_font(const char *);
+public:
+ int encoding_index;
+ char *encoding;
+ char *reencoded_name;
+ ~html_font();
+ static html_font *load_html_font(const char *);
+};
+
+html_font *html_font::load_html_font(const char *s)
+{
+ html_font *f = new html_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+html_font::html_font(const char *nm)
+: font(nm)
+{
+}
+
+html_font::~html_font()
+{
+}
+
+/*
+ * a simple class to contain the header to this document
+ */
+
+class title_desc {
+public:
+ title_desc ();
+ ~title_desc ();
+
+ int has_been_written;
+ int has_been_found;
+ int with_h1;
+ string text;
+};
+
+
+title_desc::title_desc ()
+ : has_been_written(FALSE), has_been_found(FALSE), with_h1(FALSE)
+{
+}
+
+title_desc::~title_desc ()
+{
+}
+
+class header_desc {
+public:
+ header_desc ();
+ ~header_desc ();
+
+ int no_of_level_one_headings; // how many .SH or .NH 1 have we found?
+ int no_of_headings; // how many headings have we found?
+ char_buffer headings; // all the headings used in the document
+ list headers; // list of headers built from .NH and .SH
+ list header_filename; // in which file is this header?
+ int header_level; // current header level
+ int written_header; // have we written the header yet?
+ string header_buffer; // current header text
+
+ void write_headings (FILE *f, int force);
+};
+
+header_desc::header_desc ()
+ : no_of_level_one_headings(0), no_of_headings(0), header_level(2),
+ written_header(0)
+{
+}
+
+header_desc::~header_desc ()
+{
+}
+
+/*
+ * write_headings - emits a list of links for the headings in this
+ * document
+ */
+
+void header_desc::write_headings (FILE *f, int force)
+{
+ text_glob *g;
+
+ if (auto_links || force) {
+ if (! headers.is_empty()) {
+ int h=1;
+
+ headers.start_from_head();
+ header_filename.start_from_head();
+ if (dialect == xhtml)
+ fputs("<p>", f);
+ do {
+ g = headers.get_data();
+ fputs("<a href=\"", f);
+ if (multiple_files && (! header_filename.is_empty())) {
+ text_glob *fn = header_filename.get_data();
+ fputs(fn->text_string, f);
+ }
+ fputs("#", f);
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(h);
+ buffer += '\0';
+ fprintf(f, "%s", buffer.contents());
+ } else
+ fputs(g->text_string, f);
+ h++;
+ fputs("\">", f);
+ fputs(g->text_string, f);
+ fputs("</a>", f);
+ if (dialect == xhtml)
+ fputs("<br/>\n", f);
+ else
+ fputs("<br>\n", f);
+ headers.move_right();
+ if (multiple_files && (! header_filename.is_empty()))
+ header_filename.move_right();
+ } while (! headers.is_equal_to_head());
+ fputs("\n", f);
+ if (dialect == xhtml)
+ fputs("</p>\n", f);
+ }
+ }
+}
+
+struct assert_pos {
+ assert_pos *next;
+ const char *val;
+ const char *id;
+};
+
+class assert_state {
+public:
+ assert_state ();
+ ~assert_state ();
+
+ void addx (const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void addy (const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void build(const char *c, const char *v,
+ const char *f, const char *l);
+ void check_br (int br);
+ void check_ce (int ce);
+ void check_fi (int fi);
+ void check_sp (int sp);
+ void reset (void);
+
+private:
+ int check_br_flag;
+ int check_ce_flag;
+ int check_fi_flag;
+ int check_sp_flag;
+ const char *val_br;
+ const char *val_ce;
+ const char *val_fi;
+ const char *val_sp;
+ const char *file_br;
+ const char *file_ce;
+ const char *file_fi;
+ const char *file_sp;
+ const char *line_br;
+ const char *line_ce;
+ const char *line_fi;
+ const char *line_sp;
+
+ assert_pos *xhead;
+ assert_pos *yhead;
+
+ void add (assert_pos **h,
+ const char *c, const char *i, const char *v,
+ const char *f, const char *l);
+ void compare(assert_pos *t,
+ const char *v, const char *f, const char *l);
+ void close (const char *c);
+ void set (const char *c, const char *v,
+ const char *f, const char *l);
+ void check_value (const char *s, int v, const char *name,
+ const char *f, const char *l, int *flag);
+ int check_value_error (int c, int v, const char *s,
+ const char *name,
+ const char *f, const char *l, int flag);
+};
+
+assert_state::assert_state ()
+{
+ reset();
+ val_br = 0;
+ val_ce = 0;
+ val_fi = 0;
+ val_sp = 0;
+ file_br = 0;
+ file_ce = 0;
+ file_fi = 0;
+ file_sp = 0;
+ line_br = 0;
+ line_ce = 0;
+ line_fi = 0;
+ line_sp = 0;
+ xhead = 0;
+ yhead = 0;
+}
+
+assert_state::~assert_state ()
+{
+ assert_pos *t;
+
+ while (xhead != 0) {
+ t = xhead;
+ xhead = xhead->next;
+ delete[] (char *)t->val;
+ delete[] (char *)t->id;
+ delete t;
+ }
+ while (yhead != 0) {
+ t = yhead;
+ yhead = yhead->next;
+ delete[] (char *)t->val;
+ delete[] (char *)t->id;
+ delete t;
+ }
+}
+
+void assert_state::reset (void)
+{
+ check_br_flag = 0;
+ check_ce_flag = 0;
+ check_fi_flag = 0;
+ check_sp_flag = 0;
+}
+
+void assert_state::add (assert_pos **h,
+ const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ assert_pos *t = *h;
+
+ while (t != 0) {
+ if (strcmp(t->id, i) == 0)
+ break;
+ t = t->next;
+ }
+ if (t != 0 && v != 0 && (v[0] != '='))
+ compare(t, v, f, l);
+ else {
+ if (0 /* nullptr */ == t) {
+ t = new assert_pos;
+ t->next = *h;
+ (*h) = t;
+ }
+ if (v == 0 || v[0] != '=') {
+ if (0 /* nullptr */ == f)
+ f = strsave("stdin");
+ if (0 /* nullptr */ == l)
+ l = strsave("<none>");
+ if (0 /* nullptr */ == v)
+ v = "no value at all";
+ fprintf(stderr, "%s:%s:%s: error in assertion format of id=%s;"
+ " expected value prefixed with an '=', got %s\n",
+ program_name, f, l, i, v);
+ }
+ t->id = i;
+ t->val = v;
+ delete[] (char *)c;
+ delete[] (char *)f;
+ delete[] (char *)l;
+ }
+}
+
+void assert_state::addx (const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ add(&xhead, c, i, v, f, l);
+}
+
+void assert_state::addy (const char *c, const char *i, const char *v,
+ const char *f, const char *l)
+{
+ add(&yhead, c, i, v, f, l);
+}
+
+void assert_state::compare(assert_pos *t,
+ const char *v, const char *f, const char *l)
+{
+ const char *s=t->val;
+
+ while ((*v) == '=')
+ v++;
+ while ((*s) == '=')
+ s++;
+
+ if (strcmp(v, s) != 0) {
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ fprintf(stderr, "%s:%s: grohtml assertion failed at id%s: "
+ "expected %s, got %s\n", f, l, t->id, s, v);
+ }
+}
+
+void assert_state::close (const char *c)
+{
+ if (strcmp(c, "sp") == 0)
+ check_sp_flag = 0;
+ else if (strcmp(c, "br") == 0)
+ check_br_flag = 0;
+ else if (strcmp(c, "fi") == 0)
+ check_fi_flag = 0;
+ else if (strcmp(c, "nf") == 0)
+ check_fi_flag = 0;
+ else if (strcmp(c, "ce") == 0)
+ check_ce_flag = 0;
+ else
+ fprintf(stderr, "internal error: unrecognised tag in grohtml "
+ "(%s)\n", c);
+}
+
+const char *replace_negate_str (const char *before, char *after)
+{
+ if (before != 0)
+ delete[] (char *)before;
+
+ if (strlen(after) > 0) {
+ int d = atoi(after);
+
+ if (d < 0 || d > 1) {
+ fprintf(stderr, "expected nf/fi value of 0 or 1, got %d\n", d);
+ d = 0;
+ }
+ if (d == 0)
+ after[0] = '1';
+ else
+ after[0] = '0';
+ after[1] = (char)0;
+ }
+ return after;
+}
+
+const char *replace_str (const char *before, const char *after)
+{
+ if (before != 0)
+ delete[] (char *)before;
+ return after;
+}
+
+void assert_state::set (const char *c, const char *v,
+ const char *f, const char *l)
+{
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+
+ // fprintf(stderr, "%s:%s:setting %s to %s\n", f, l, c, v);
+ if (strcmp(c, "sp") == 0) {
+ check_sp_flag = 1;
+ val_sp = replace_str(val_sp, strsave(v));
+ file_sp = replace_str(file_sp, strsave(f));
+ line_sp = replace_str(line_sp, strsave(l));
+ } else if (strcmp(c, "br") == 0) {
+ check_br_flag = 1;
+ val_br = replace_str(val_br, strsave(v));
+ file_br = replace_str(file_br, strsave(f));
+ line_br = replace_str(line_br, strsave(l));
+ } else if (strcmp(c, "fi") == 0) {
+ check_fi_flag = 1;
+ val_fi = replace_str(val_fi, strsave(v));
+ file_fi = replace_str(file_fi, strsave(f));
+ line_fi = replace_str(line_fi, strsave(l));
+ } else if (strcmp(c, "nf") == 0) {
+ check_fi_flag = 1;
+ val_fi = replace_negate_str(val_fi, strsave(v));
+ file_fi = replace_str(file_fi, strsave(f));
+ line_fi = replace_str(line_fi, strsave(l));
+ } else if (strcmp(c, "ce") == 0) {
+ check_ce_flag = 1;
+ val_ce = replace_str(val_ce, strsave(v));
+ file_ce = replace_str(file_ce, strsave(f));
+ line_ce = replace_str(line_ce, strsave(l));
+ }
+}
+
+/*
+ * build - builds the troff state assertion.
+ * see tmac/www.tmac for cmd examples.
+ */
+
+void assert_state::build (const char *c, const char *v,
+ const char *f, const char *l)
+{
+ if (c[0] == '{')
+ set(&c[1], v, f, l);
+ if (c[0] == '}')
+ close(&c[1]);
+}
+
+int assert_state::check_value_error (int c, int v, const char *s,
+ const char *name, const char *f,
+ const char *l, int flag)
+{
+ if (! c) {
+ if (0 /* nullptr */ == f)
+ f = "stdin";
+ if (0 /* nullptr */ == l)
+ l = "<none>";
+ fprintf(stderr, "%s:%s:grohtml (troff state) assertion failed; "
+ "expected %s to be %s, got %d\n", f, l, name, s, v);
+ return 0;
+ }
+ return flag;
+}
+
+void assert_state::check_value (const char *s, int v, const char *name,
+ const char *f, const char *l, int *flag)
+{
+ if (strncmp(s, "<=", 2) == 0)
+ *flag = check_value_error(v <= atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, ">=", 2) == 0)
+ *flag = check_value_error(v >= atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "==", 2) == 0)
+ *flag = check_value_error(v == atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "!=", 2) == 0)
+ *flag = check_value_error(v != atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "<", 1) == 0)
+ *flag = check_value_error(v < atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, ">", 1) == 0)
+ *flag = check_value_error(v > atoi(&s[2]), v, s, name, f, l, *flag);
+ else if (strncmp(s, "=", 1) == 0)
+ *flag = check_value_error(v == atoi(&s[1]), v, s, name, f, l, *flag);
+ else
+ *flag = check_value_error(v == atoi(s), v, s, name, f, l, *flag);
+}
+
+void assert_state::check_sp (int sp)
+{
+ if (check_sp_flag)
+ check_value(val_sp, sp, "sp", file_sp, line_sp, &check_sp_flag);
+}
+
+void assert_state::check_fi (int fi)
+{
+ if (check_fi_flag)
+ check_value(val_fi, fi, "fi", file_fi, line_fi, &check_fi_flag);
+}
+
+void assert_state::check_br (int br)
+{
+ if (check_br_flag)
+ check_value(val_br, br, "br", file_br, line_br, &check_br_flag);
+}
+
+void assert_state::check_ce (int ce)
+{
+ if (check_ce_flag)
+ check_value(val_ce, ce, "ce", file_ce, line_ce, &check_ce_flag);
+}
+
+class html_printer : public printer {
+ files file_list;
+ simple_output html;
+ int res;
+ glyph *space_glyph;
+ int space_width;
+ int no_of_printed_pages;
+ int paper_length;
+ string sbuf;
+ int sbuf_start_hpos;
+ int sbuf_vpos;
+ int sbuf_end_hpos;
+ int sbuf_prev_hpos;
+ int sbuf_kern;
+ style sbuf_style;
+ int last_sbuf_length;
+ int overstrike_detected;
+ style output_style;
+ int output_hpos;
+ int output_vpos;
+ int output_vpos_max;
+ int output_draw_point_size;
+ int line_thickness;
+ int output_line_thickness;
+ unsigned char output_space_code;
+ char *inside_font_style;
+ int page_number;
+ title_desc title;
+ header_desc header;
+ int header_indent;
+ int suppress_sub_sup;
+ int cutoff_heading;
+ page *page_contents;
+ html_text *current_paragraph;
+ html_indent *indent;
+ html_table *table;
+ int end_center;
+ int end_tempindent;
+ TAG_ALIGNMENT next_tag;
+ int fill_on;
+ int max_linelength;
+ int linelength;
+ int pageoffset;
+ int troff_indent;
+ int device_indent;
+ int temp_indent;
+ int pointsize;
+ int vertical_spacing;
+ int line_number;
+ color *background;
+ int seen_indent;
+ int next_indent;
+ int seen_pageoffset;
+ int next_pageoffset;
+ int seen_linelength;
+ int next_linelength;
+ int seen_center;
+ int next_center;
+ int seen_space;
+ int seen_break;
+ int current_column;
+ int row_space;
+ assert_state as;
+
+ void flush_sbuf ();
+ void set_style (const style &);
+ void set_space_code (unsigned char c);
+ void do_exec (char *, const environment *);
+ void do_import (char *, const environment *);
+ void do_def (char *, const environment *);
+ void do_mdef (char *, const environment *);
+ void do_file (char *, const environment *);
+ void set_line_thickness (const environment *);
+ void terminate_current_font (void);
+ void flush_font (void);
+ void add_to_sbuf (glyph *g, const string &s);
+ void write_title (int in_head);
+ int sbuf_continuation (glyph *g, const char *name,
+ const environment *env, int w);
+ void flush_page (void);
+ void troff_tag (text_glob *g);
+ void flush_globs (void);
+ void emit_line (text_glob *g);
+ void emit_raw (text_glob *g);
+ void emit_html (text_glob *g);
+ void determine_space (text_glob *g);
+ void start_font (const char *name);
+ void end_font (const char *name);
+ int is_font_courier (font *f);
+ int is_line_start (int nf);
+ int is_courier_until_eol (void);
+ void start_size (int from, int to);
+ void do_font (text_glob *g);
+ void do_center (char *arg);
+ void do_check_center (void);
+ void do_break (void);
+ void do_space (char *arg);
+ void do_eol (void);
+ void do_eol_ce (void);
+ void do_title (void);
+ void do_fill (char *arg);
+ void do_heading (char *arg);
+ void write_header (void);
+ void determine_header_level (int level);
+ void do_linelength (char *arg);
+ void do_pageoffset (char *arg);
+ void do_indentation (char *arg);
+ void do_tempindent (char *arg);
+ void do_indentedparagraph (void);
+ void do_verticalspacing (char *arg);
+ void do_pointsize (char *arg);
+ void do_centered_image (void);
+ void do_left_image (void);
+ void do_right_image (void);
+ void do_auto_image (text_glob *g,
+ const char *filename);
+ void do_links (void);
+ void do_flush (void);
+ void do_job_name (char *name);
+ void do_head (char *name);
+ void insert_split_file (void);
+ int is_in_middle (int left, int right);
+ void do_sup_or_sub (text_glob *g);
+ int start_subscript (text_glob *g);
+ int end_subscript (text_glob *g);
+ int start_superscript (text_glob *g);
+ int end_superscript (text_glob *g);
+ void outstanding_eol (int n);
+ int is_bold (font *f);
+ font *make_bold (font *f);
+ int overstrike (glyph *g, const char *name,
+ const environment *env, int w);
+ void do_body (void);
+ int next_horiz_pos (text_glob *g, int nf);
+ void lookahead_for_tables (void);
+ void insert_tab_te (void);
+ text_glob *insert_tab_ts (text_glob *where);
+ void insert_tab0_foreach_tab (void);
+ void insert_tab_0 (text_glob *where);
+ void do_indent (int in, int pageoff,
+ int linelen);
+ void shutdown_table (void);
+ void do_tab_ts (text_glob *g);
+ void do_tab_te (void);
+ void do_col (char *s);
+ void do_tab (char *s);
+ void do_tab0 (void);
+ int calc_nf (text_glob *g, int nf);
+ void calc_po_in (text_glob *g, int nf);
+ void remove_tabs (void);
+ void remove_courier_tabs (void);
+ void update_min_max (colType type_of_col,
+ int *minimum, int *maximum,
+ text_glob *g);
+ void add_table_end (const char *);
+ void do_file_components (void);
+ void write_navigation (const string &top,
+ const string &prev,
+ const string &next,
+ const string &current);
+ void emit_link (const string &to,
+ const char *name);
+ int get_troff_indent (void);
+ void restore_troff_indent (void);
+ void handle_assertion (int minv, int minh,
+ int maxv, int maxh,
+ const char *s);
+ void handle_state_assertion (text_glob *g);
+ void do_end_para (text_glob *g);
+ int round_width (int x);
+ void handle_tag_within_title (text_glob *g);
+ void writeHeadMetaStyle (void);
+ void handle_valid_flag (int needs_para);
+ void do_math (text_glob *g);
+ void write_html_anchor (text_glob *h);
+ void write_xhtml_anchor (text_glob *h);
+ // ADD HERE
+
+public:
+ html_printer ();
+ ~html_printer ();
+ void set_char (glyph *g, font *f, const environment *env,
+ int w, const char *name);
+ void set_numbered_char(int num, const environment *env, int *widthp);
+ glyph *set_char_and_width(const char *nm, const environment *env,
+ int *widthp, font **f);
+ void draw (int code, int *p, int np,
+ const environment *env);
+ void begin_page (int);
+ void end_page (int);
+ void special (char *arg, const environment *env, char type);
+ void devtag (char *arg, const environment *env, char type);
+ font *make_font (const char *);
+ void end_of_line ();
+};
+
+printer *make_printer()
+{
+ return new html_printer;
+}
+
+static void usage(FILE *stream);
+
+void html_printer::set_style(const style &sty)
+{
+ const char *fontname = sty.f->get_name();
+ if (0 /* nullptr */ == fontname)
+ fatal("no internalname specified for font");
+
+#if 0
+ change_font(fontname, (font::res / (72 * font::sizescale))
+ * sty.point_size);
+#endif
+}
+
+/*
+ * is_bold - returns TRUE if font, f, is bold.
+ */
+
+int html_printer::is_bold (font *f)
+{
+ const char *fontname = f->get_name();
+ return (strcmp(fontname, "B") == 0) || (strcmp(fontname, "BI") == 0);
+}
+
+/*
+ * make_bold - if a bold style for f exists, return it.
+ */
+
+font *html_printer::make_bold (font *f)
+{
+ const char *fontname = f->get_name();
+
+ if (strcmp(fontname, "B") == 0)
+ return f;
+ if (strcmp(fontname, "I") == 0)
+ return font::load_font("BI");
+ if (strcmp(fontname, "BI") == 0)
+ return f;
+ return 0;
+}
+
+void html_printer::end_of_line()
+{
+ flush_sbuf();
+ line_number++;
+}
+
+/*
+ * emit_line - writes out a horizontal rule.
+ */
+
+void html_printer::emit_line (text_glob *)
+{
+ // --fixme-- needs to know the length in percentage
+ if (dialect == xhtml)
+ html.put_string("<hr/>");
+ else
+ html.put_string("<hr>");
+}
+
+/*
+ * restore_troff_indent - is called when we have temporarily shutdown
+ * indentation (typically done when we have
+ * centered an image).
+ */
+
+void html_printer::restore_troff_indent (void)
+{
+ troff_indent = next_indent;
+ if (troff_indent > 0) {
+ /*
+ * force device indentation
+ */
+ device_indent = 0;
+ do_indent(get_troff_indent(), pageoffset, linelength);
+ }
+}
+
+/*
+ * emit_raw - writes the raw html information directly to the device.
+ */
+
+void html_printer::emit_raw (text_glob *g)
+{
+ do_font(g);
+ if (next_tag == INLINE) {
+ determine_space(g);
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ } else {
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+
+ current_paragraph->done_para();
+ shutdown_table();
+ switch (next_tag) {
+
+ case CENTERED:
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ break;
+ case LEFT:
+ if (dialect == html4)
+ current_paragraph->do_para(&html, "align=\"left\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ else
+ current_paragraph->do_para(&html, "class=\"left\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ break;
+ case RIGHT:
+ if (dialect == html4)
+ current_paragraph->do_para(&html, "align=\"right\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ else
+ current_paragraph->do_para(&html, "class=\"right\"",
+ get_troff_indent(), pageoffset,
+ linelength, space);
+ break;
+ default:
+ fatal("unknown enumeration");
+ }
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ current_paragraph->done_para();
+ next_tag = INLINE;
+ suppress_sub_sup = TRUE;
+ seen_space = FALSE;
+ restore_troff_indent();
+ }
+}
+
+/*
+ * handle_tag_within_title - handle a limited number of tags within
+ * the context of a table. Those tags which
+ * set values rather than generate spaces
+ * and paragraphs.
+ */
+
+void html_printer::handle_tag_within_title (text_glob *g)
+{
+ if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce() || g->is_ll()
+ || g->is_fi() || g->is_nf())
+ troff_tag(g);
+}
+
+/*
+ * do_center - handle the .ce commands from troff.
+ */
+
+void html_printer::do_center (char *arg)
+{
+ next_center = atoi(arg);
+ seen_center = TRUE;
+}
+
+/*
+ * do_centered_image - set a flag such that the next devtag is
+ * placed inside a centered paragraph.
+ */
+
+void html_printer::do_centered_image (void)
+{
+ next_tag = CENTERED;
+}
+
+/*
+ * do_right_image - set a flag such that the next devtag is
+ * placed inside a right aligned paragraph.
+ */
+
+void html_printer::do_right_image (void)
+{
+ next_tag = RIGHT;
+}
+
+/*
+ * do_left_image - set a flag such that the next devtag is
+ * placed inside a left aligned paragraph.
+ */
+
+void html_printer::do_left_image (void)
+{
+ next_tag = LEFT;
+}
+
+/*
+ * exists - returns TRUE if filename exists.
+ */
+
+static int exists (const char *filename)
+{
+ FILE *fp = fopen(filename, "r");
+
+ if (fp == 0) {
+ return FALSE;
+ } else {
+ fclose(fp);
+ return TRUE;
+ }
+}
+
+/*
+ * generate_img_src - returns a html image tag for the filename
+ * providing that the image exists.
+ */
+
+static string &generate_img_src (const char *filename)
+{
+ string *s = new string("");
+
+ while (filename && (filename[0] == ' ')) {
+ filename++;
+ }
+ if (exists(filename)) {
+ *s += string("<img src=\"") + filename + "\" "
+ + "alt=\"Image " + filename + "\">";
+ if (dialect == xhtml)
+ *s += "</img>";
+ }
+ return *s;
+}
+
+/*
+ * do_auto_image - tests whether the image, indicated by filename,
+ * is present, if so then it emits an html image tag.
+ * An image tag may be passed through from pic, eqn
+ * but the corresponding image might not be created.
+ * Consider .EQ delim $$ .EN or an empty .PS .PE.
+ */
+
+void html_printer::do_auto_image (text_glob *g, const char *filename)
+{
+ string buffer = generate_img_src(filename);
+
+ if (! buffer.empty()) {
+ /*
+ * utilize emit_raw by creating a new text_glob.
+ */
+ text_glob h = *g;
+
+ h.text_string = buffer.contents();
+ h.text_length = buffer.length();
+ emit_raw(&h);
+ } else
+ next_tag = INLINE;
+}
+
+/*
+ * outstanding_eol - call do_eol, n, times.
+ */
+
+void html_printer::outstanding_eol (int n)
+{
+ while (n > 0) {
+ do_eol();
+ n--;
+ }
+}
+
+/*
+ * do_title - handle the .tl commands from troff.
+ */
+
+void html_printer::do_title (void)
+{
+ text_glob *t;
+ int removed_from_head;
+
+ if (page_number == 1) {
+ int found_title_start = FALSE;
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ do {
+ t = page_contents->glyphs.get_data();
+ removed_from_head = FALSE;
+ if (t->is_auto_img()) {
+ string img = generate_img_src((char *)(t->text_string + 20));
+
+ if (! img.empty()) {
+ if (found_title_start)
+ title.text += " ";
+ found_title_start = TRUE;
+ title.has_been_found = TRUE;
+ title.text += img;
+ }
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else if (t->is_eo_tl()) {
+ // end of title found
+ title.has_been_found = TRUE;
+ return;
+ } else if (t->is_a_tag()) {
+ handle_tag_within_title(t);
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else if (found_title_start) {
+ title.text += " " + string(t->text_string, t->text_length);
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ } else {
+ title.text += string(t->text_string, t->text_length);
+ found_title_start = TRUE;
+ title.has_been_found = TRUE;
+ page_contents->glyphs.sub_move_right(); // move onto next word
+ removed_from_head = ((!page_contents->glyphs.is_empty()) &&
+ (page_contents->glyphs
+ .is_equal_to_head()));
+ }
+ } while ((! page_contents->glyphs.is_equal_to_head()) ||
+ (removed_from_head));
+ }
+ }
+}
+
+/*
+ * write_html_anchor - writes out an anchor. The style of the anchor
+ * dependent upon simple_anchor.
+ */
+
+void html_printer::write_html_anchor (text_glob *h)
+{
+ if (dialect == html4) {
+ if (h != 0) {
+ html.put_string("<a name=\"");
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(header.no_of_headings);
+ buffer += '\0';
+ html.put_string(buffer.contents());
+ } else
+ html.put_string(header.header_buffer);
+ html.put_string("\"></a>").nl();
+ }
+ }
+}
+
+/*
+ * write_xhtml_anchor - writes out an anchor. The style of the anchor
+ * dependent upon simple_anchor.
+ */
+
+void html_printer::write_xhtml_anchor (text_glob *h)
+{
+ if (dialect == xhtml) {
+ if (h != 0) {
+ html.put_string(" id=\"");
+ if (simple_anchors) {
+ string buffer(ANCHOR_TEMPLATE);
+
+ buffer += as_string(header.no_of_headings);
+ buffer += '\0';
+ html.put_string(buffer.contents());
+ } else
+ html.put_string(header.header_buffer);
+ html.put_string("\"");
+ }
+ }
+}
+
+void html_printer::write_header (void)
+{
+ if (! header.header_buffer.empty()) {
+ text_glob *a = 0;
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+
+ if (header.header_level > 7)
+ header.header_level = 7;
+
+ // firstly we must terminate any font and type faces
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+
+ if (cutoff_heading+2 > header.header_level) {
+ // now we save the header so we can issue a list of links
+ header.no_of_headings++;
+ style st;
+
+ a = new text_glob();
+ a->text_glob_html(&st,
+ header.headings
+ .add_string(header.header_buffer),
+ header.header_buffer.length(),
+ header.no_of_headings, header.header_level,
+ header.no_of_headings, header.header_level);
+
+ // and add this header to the header list
+ header.headers.add(a,
+ header.no_of_headings,
+ header.no_of_headings, header.no_of_headings,
+ header.no_of_headings, header.no_of_headings);
+ }
+
+ html.nl().nl();
+
+ if (manufacture_headings) {
+ // line break before a header
+ if (!current_paragraph->emitted_text())
+ current_paragraph->do_space();
+ // user wants manufactured headings which look better than
+ // <Hn></Hn>
+ if (header.header_level<4) {
+ html.put_string("<b><font size=\"+1\">");
+ html.put_string(header.header_buffer);
+ html.put_string("</font>").nl();
+ write_html_anchor(a);
+ html.put_string("</b>").nl();
+ }
+ else {
+ html.put_string("<b>");
+ html.put_string(header.header_buffer).nl();
+ write_html_anchor(a);
+ html.put_string("</b>").nl();
+ }
+ }
+ else {
+ // and now we issue the real header
+ html.put_string("<h");
+ html.put_number(header.header_level);
+ write_xhtml_anchor(a);
+ html.put_string(">");
+ html.put_string(header.header_buffer).nl();
+ write_html_anchor(a);
+ html.put_string("</h");
+ html.put_number(header.header_level);
+ html.put_string(">").nl();
+ }
+
+ /* and now we save the file name in which this header will occur */
+
+ style st; // fake style to enable us to use the list data structure
+
+ text_glob *h=new text_glob();
+ h->text_glob_html(&st,
+ header.headings.add_string(file_list.file_name()),
+ file_list.file_name().length(),
+ header.no_of_headings, header.header_level,
+ header.no_of_headings, header.header_level);
+
+ header.header_filename.add(h,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings,
+ header.no_of_headings);
+
+ current_paragraph->do_para(&html, "", get_troff_indent(),
+ pageoffset, linelength, space);
+ }
+}
+
+void html_printer::determine_header_level (int level)
+{
+ if (level == 0) {
+ int i;
+
+ for (i = 0; ((i<header.header_buffer.length())
+ && ((header.header_buffer[i] == '.')
+ || is_digit(header.header_buffer[i]))) ; i++) {
+ if (header.header_buffer[i] == '.') {
+ level++;
+ }
+ }
+ }
+ header.header_level = level+1;
+ if (header.header_level >= 2 && header.header_level <= split_level) {
+ header.no_of_level_one_headings++;
+ insert_split_file();
+ }
+}
+
+/*
+ * do_heading - handle the .SH and .NH and equivalent commands from
+ * troff.
+ */
+
+void html_printer::do_heading (char *arg)
+{
+ text_glob *g;
+ int level=atoi(arg);
+ int horiz;
+
+ header.header_buffer.clear();
+ page_contents->glyphs.move_right();
+ if (! page_contents->glyphs.is_equal_to_head()) {
+ g = page_contents->glyphs.get_data();
+ horiz = g->minh;
+ do {
+ if (g->is_auto_img()) {
+ string img=generate_img_src((char *)(g->text_string + 20));
+
+ if (! img.empty()) {
+ // we cannot use full heading anchors with images
+ simple_anchors = TRUE;
+ if (horiz < g->minh)
+ header.header_buffer += " ";
+
+ header.header_buffer += img;
+ }
+ }
+ else if (g->is_in() || g->is_ti() || g->is_po() || g->is_ce()
+ || g->is_ll())
+ troff_tag(g);
+ else if (g->is_fi())
+ fill_on = 1;
+ else if (g->is_nf())
+ fill_on = 0;
+ else if (! (g->is_a_line() || g->is_a_tag())) {
+ /*
+ * we ignore the other tag commands when constructing a heading
+ */
+ if (horiz < g->minh)
+ header.header_buffer += " ";
+
+ horiz = g->maxh;
+ header.header_buffer += string(g->text_string, g->text_length);
+ }
+ page_contents->glyphs.move_right();
+ g = page_contents->glyphs.get_data();
+ } while ((! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_eo_h()));
+ }
+
+ determine_header_level(level);
+ write_header();
+
+ /*
+ * finally set the output font to uninitialized, thus forcing
+ * the new paragraph to start a new font block.
+ */
+
+ output_style.f = 0;
+ g = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_left(); // so that next time we use old g
+}
+
+/*
+ * is_courier_until_eol - returns TRUE if we can see a whole line which
+ * is courier
+ */
+
+int html_printer::is_courier_until_eol (void)
+{
+ text_glob *orig = page_contents->glyphs.get_data();
+ int result = TRUE;
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_tail()) {
+ page_contents->glyphs.move_right();
+ do {
+ g = page_contents->glyphs.get_data();
+ if (! g->is_a_tag() && (! is_font_courier(g->text_style.f)))
+ result = FALSE;
+ page_contents->glyphs.move_right();
+ } while (result &&
+ (! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_fi()) && (! g->is_eol()));
+
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_left();
+ }
+ return result;
+}
+
+/*
+ * do_linelength - handle the .ll command from troff.
+ */
+
+void html_printer::do_linelength (char *arg)
+{
+ if (max_linelength == -1)
+ max_linelength = atoi(arg);
+
+ next_linelength = atoi(arg);
+ seen_linelength = TRUE;
+}
+
+/*
+ * do_pageoffset - handle the .po command from troff.
+ */
+
+void html_printer::do_pageoffset (char *arg)
+{
+ next_pageoffset = atoi(arg);
+ seen_pageoffset = TRUE;
+}
+
+/*
+ * get_troff_indent - returns the indent value.
+ */
+
+int html_printer::get_troff_indent (void)
+{
+ if (end_tempindent > 0)
+ return temp_indent;
+ else
+ return troff_indent;
+}
+
+/*
+ * do_indentation - handle the .in command from troff.
+ */
+
+void html_printer::do_indentation (char *arg)
+{
+ next_indent = atoi(arg);
+ seen_indent = TRUE;
+}
+
+/*
+ * do_tempindent - handle the .ti command from troff.
+ */
+
+void html_printer::do_tempindent (char *arg)
+{
+ if (fill_on) {
+ /*
+ * we set the end_tempindent to 2 as the first .br
+ * activates the .ti and the second terminates it.
+ */
+ end_tempindent = 2;
+ temp_indent = atoi(arg);
+ }
+}
+
+/*
+ * shutdown_table - shuts down the current table.
+ */
+
+void html_printer::shutdown_table (void)
+{
+ if (table != 0) {
+ current_paragraph->done_para();
+ table->emit_finish_table();
+ // don't delete this table as it will be deleted when we destroy the
+ // text_glob
+ table = 0;
+ }
+}
+
+/*
+ * do_indent - remember the indent parameters and if
+ * indent is > pageoff and indent has changed
+ * then we start a html table to implement the indentation.
+ */
+
+void html_printer::do_indent (int in, int pageoff, int linelen)
+{
+ if ((device_indent != -1) &&
+ (pageoffset+device_indent != in+pageoff)) {
+
+ int space = current_paragraph->retrieve_para_space() || seen_space;
+ current_paragraph->done_para();
+
+ device_indent = in;
+ pageoffset = pageoff;
+ if (linelen <= max_linelength)
+ linelength = linelen;
+
+ current_paragraph->do_para(&html, "", device_indent,
+ pageoffset, max_linelength, space);
+ }
+}
+
+/*
+ * do_verticalspacing - handle the .vs command from troff.
+ */
+
+void html_printer::do_verticalspacing (char *arg)
+{
+ vertical_spacing = atoi(arg);
+}
+
+/*
+ * do_pointsize - handle the .ps command from troff.
+ */
+
+void html_printer::do_pointsize (char *arg)
+{
+ /*
+ * firstly check to see whether this point size is really associated
+ * with a .tl tag
+ */
+
+ if (! page_contents->glyphs.is_empty()) {
+ text_glob *g = page_contents->glyphs.get_data();
+ text_glob *t = page_contents->glyphs.get_data();
+
+ while (t->is_a_tag() && (!page_contents->glyphs.is_equal_to_head()))
+ {
+ if (t->is_tl()) {
+ /*
+ * found title therefore ignore this .ps tag
+ */
+ while (t != g) {
+ page_contents->glyphs.move_left();
+ t = page_contents->glyphs.get_data();
+ }
+ return;
+ }
+ page_contents->glyphs.move_right();
+ t = page_contents->glyphs.get_data();
+ }
+ /*
+ * move back to original position
+ */
+ while (t != g) {
+ page_contents->glyphs.move_left();
+ t = page_contents->glyphs.get_data();
+ }
+ /*
+ * collect valid pointsize
+ */
+ pointsize = atoi(arg);
+ }
+}
+
+/*
+ * do_fill - records whether troff has requested that text be filled.
+ */
+
+void html_printer::do_fill (char *arg)
+{
+ int on = atoi(arg);
+
+ output_hpos = get_troff_indent()+pageoffset;
+ suppress_sub_sup = TRUE;
+
+ if (fill_on != on) {
+ if (on)
+ current_paragraph->do_para("", seen_space);
+ fill_on = on;
+ }
+}
+
+/*
+ * do_eol - handle the end of line
+ */
+
+void html_printer::do_eol (void)
+{
+ if (! fill_on) {
+ if (current_paragraph->ever_emitted_text()) {
+ current_paragraph->do_newline();
+ current_paragraph->do_break();
+ }
+ }
+ output_hpos = get_troff_indent()+pageoffset;
+}
+
+/*
+ * do_check_center - checks to see whether we have seen a '.ce' tag
+ * during the previous line.
+ */
+
+void html_printer::do_check_center(void)
+{
+ if (seen_center) {
+ seen_center = FALSE;
+ if (next_center > 0) {
+ if (end_center == 0) {
+ int space = current_paragraph->retrieve_para_space()
+ || seen_space;
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ } else
+ if ((strcmp("align=\"center\"",
+ current_paragraph->get_alignment()) != 0) &&
+ (strcmp("class=\"center\"",
+ current_paragraph->get_alignment()) != 0)) {
+ /*
+ * different alignment, so shutdown paragraph and open
+ * a new one.
+ */
+ int space = current_paragraph->retrieve_para_space()
+ || seen_space;
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ if (dialect == html4)
+ current_paragraph->do_para("align=\"center\"", space);
+ else
+ current_paragraph->do_para("class=\"center\"", space);
+ } else
+ // same alignment; if we have emitted text, issue a break.
+ if (current_paragraph->emitted_text())
+ current_paragraph->do_break();
+ } else
+ /*
+ * next_center == 0
+ */
+ if (end_center > 0) {
+ seen_space = seen_space
+ || current_paragraph->retrieve_para_space();
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ current_paragraph->do_para("", seen_space);
+ }
+ end_center = next_center;
+ }
+}
+
+/*
+ * do_eol_ce - handle end of line specifically for a .ce
+ */
+
+void html_printer::do_eol_ce (void)
+{
+ if (end_center > 0) {
+ if (end_center > 1)
+ if (current_paragraph->emitted_text())
+ current_paragraph->do_break();
+
+ end_center--;
+ if (end_center == 0) {
+ current_paragraph->done_para();
+ suppress_sub_sup = TRUE;
+ }
+ }
+}
+
+/*
+ * do_flush - flushes all output and tags.
+ */
+
+void html_printer::do_flush (void)
+{
+ current_paragraph->done_para();
+}
+
+/*
+ * do_links - moves onto a new temporary file and sets auto_links to
+ * false.
+ */
+
+void html_printer::do_links (void)
+{
+ html.end_line(); // flush line
+ auto_links = FALSE; // from now on only emit under user request
+ file_list.add_new_file(xtmpfile());
+ file_list.set_links_required();
+ html.set_file(file_list.get_file());
+}
+
+/*
+ * insert_split_file -
+ */
+
+void html_printer::insert_split_file (void)
+{
+ if (multiple_files) {
+ current_paragraph->done_para(); // flush paragraph
+ html.end_line(); // flush line
+ html.set_file(file_list.get_file()); // flush current file
+ file_list.add_new_file(xtmpfile());
+ string split_file = job_name;
+
+ split_file += string("-");
+ split_file += as_string(header.no_of_level_one_headings);
+ if (dialect == xhtml)
+ split_file += string(".xhtml");
+ else
+ split_file += string(".html");
+ split_file += '\0';
+
+ file_list.set_file_name(split_file);
+ html.set_file(file_list.get_file());
+ }
+}
+
+/*
+ * do_job_name - assigns the job_name to name.
+ */
+
+void html_printer::do_job_name (char *name)
+{
+ if (! multiple_files) {
+ multiple_files = TRUE;
+ while (name != 0 && (*name != (char)0) && (*name == ' '))
+ name++;
+ job_name = name;
+ }
+}
+
+/*
+ * do_head - adds a string to head_info which is to be included into
+ * the <head> </head> section of the html document.
+ */
+
+void html_printer::do_head (char *name)
+{
+ head_info += string(name);
+ head_info += '\n';
+}
+
+/*
+ * do_break - handles the ".br" request and also undoes an outstanding
+ * ".ti" command and calls indent if the indentation related
+ * registers have changed.
+ */
+
+void html_printer::do_break (void)
+{
+ int seen_temp_indent = FALSE;
+
+ current_paragraph->do_break();
+ if (end_tempindent > 0) {
+ end_tempindent--;
+ if (end_tempindent > 0)
+ seen_temp_indent = TRUE;
+ }
+ if (seen_indent || seen_pageoffset || seen_linelength
+ || seen_temp_indent) {
+ if (seen_indent && (! seen_temp_indent))
+ troff_indent = next_indent;
+ if (! seen_pageoffset)
+ next_pageoffset = pageoffset;
+ if (! seen_linelength)
+ next_linelength = linelength;
+ do_indent(get_troff_indent(), next_pageoffset, next_linelength);
+ }
+ seen_indent = seen_temp_indent;
+ seen_linelength = FALSE;
+ seen_pageoffset = FALSE;
+ do_check_center();
+ output_hpos = get_troff_indent()+pageoffset;
+ suppress_sub_sup = TRUE;
+}
+
+void html_printer::do_space (char *arg)
+{
+ int n = atoi(arg);
+
+ seen_space = atoi(arg);
+ as.check_sp(seen_space);
+#if 0
+ if (n>0 && table)
+ table->set_space(TRUE);
+#endif
+
+ while (n>0) {
+ current_paragraph->do_space();
+ n--;
+ }
+ suppress_sub_sup = TRUE;
+}
+
+/*
+ * do_tab_ts - start a table, which will have already been defined.
+ */
+
+void html_printer::do_tab_ts (text_glob *g)
+{
+ html_table *t = g->get_table();
+
+ if (t != 0) {
+ current_column = 0;
+ current_paragraph->done_pre();
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+
+#if defined(DEBUG_TABLES)
+ html.simple_comment("TABS");
+#endif
+
+ t->set_linelength(max_linelength);
+ t->add_indent(pageoffset);
+#if 0
+ t->emit_table_header(seen_space);
+#else
+ t->emit_table_header(FALSE);
+ row_space = current_paragraph->retrieve_para_space() || seen_space;
+ seen_space = FALSE;
+#endif
+ }
+
+ table = t;
+}
+
+/*
+ * do_tab_te - finish a table.
+ */
+
+void html_printer::do_tab_te (void)
+{
+ if (table) {
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+ table->emit_finish_table();
+ }
+
+ table = 0;
+ restore_troff_indent();
+}
+
+/*
+ * do_tab - handle the "devtag:tab" tag
+ */
+
+void html_printer::do_tab (char *s)
+{
+ if (table) {
+ while (isspace(*s))
+ s++;
+ s++;
+ int col = table->find_column(atoi(s) + pageoffset
+ + get_troff_indent());
+ if (col > 0) {
+ current_paragraph->done_para();
+ table->emit_col(col);
+ }
+ }
+}
+
+/*
+ * do_tab0 - handle the "devtag:tab0" tag
+ */
+
+void html_printer::do_tab0 (void)
+{
+ if (table) {
+ int col = table->find_column(pageoffset+get_troff_indent());
+ if (col > 0) {
+ current_paragraph->done_para();
+ table->emit_col(col);
+ }
+ }
+}
+
+/*
+ * do_col - start column, s.
+ */
+
+void html_printer::do_col (char *s)
+{
+ if (table) {
+ if (atoi(s) < current_column)
+ row_space = seen_space;
+
+ current_column = atoi(s);
+ current_paragraph->done_para();
+ table->emit_col(current_column);
+ current_paragraph->do_para("", row_space);
+ }
+}
+
+/*
+ * troff_tag - processes the troff tag and manipulates the troff
+ * state machine.
+ */
+
+void html_printer::troff_tag (text_glob *g)
+{
+ /*
+ * firstly skip over devtag:
+ */
+ char *t=(char *)g->text_string+strlen("devtag:");
+ if (strncmp(g->text_string, "html</p>:", strlen("html</p>:")) == 0) {
+ do_end_para(g);
+ } else if (strncmp(g->text_string, "html<?p>:", strlen("html<?p>:"))
+ == 0) {
+ if (current_paragraph->emitted_text())
+ html.put_string(g->text_string+9);
+ else
+ do_end_para(g);
+ } else if (strncmp(g->text_string, "math<?p>:", strlen("math<?p>:"))
+ == 0) {
+ do_math(g);
+ } else if (g->is_eol()) {
+ do_eol();
+ } else if (g->is_eol_ce()) {
+ do_eol_ce();
+ } else if (strncmp(t, ".sp", 3) == 0) {
+ char *a = (char *)t+3;
+ do_space(a);
+ } else if (strncmp(t, ".br", 3) == 0) {
+ seen_break = 1;
+ as.check_br(1);
+ do_break();
+ } else if (strcmp(t, ".centered-image") == 0) {
+ do_centered_image();
+ } else if (strcmp(t, ".right-image") == 0) {
+ do_right_image();
+ } else if (strcmp(t, ".left-image") == 0) {
+ do_left_image();
+ } else if (strncmp(t, ".auto-image", 11) == 0) {
+ char *a = (char *)t+11;
+ do_auto_image(g, a);
+ } else if (strncmp(t, ".ce", 3) == 0) {
+ char *a = (char *)t+3;
+ suppress_sub_sup = TRUE;
+ do_center(a);
+ } else if (g->is_tl()) {
+ suppress_sub_sup = TRUE;
+ title.with_h1 = TRUE;
+ do_title();
+ } else if (strncmp(t, ".html-tl", 8) == 0) {
+ suppress_sub_sup = TRUE;
+ title.with_h1 = FALSE;
+ do_title();
+ } else if (strncmp(t, ".fi", 3) == 0) {
+ char *a = (char *)t+3;
+ do_fill(a);
+ } else if ((strncmp(t, ".SH", 3) == 0)
+ || (strncmp(t, ".NH", 3) == 0)) {
+ char *a = (char *)t+3;
+ do_heading(a);
+ } else if (strncmp(t, ".ll", 3) == 0) {
+ char *a = (char *)t+3;
+ do_linelength(a);
+ } else if (strncmp(t, ".po", 3) == 0) {
+ char *a = (char *)t+3;
+ do_pageoffset(a);
+ } else if (strncmp(t, ".in", 3) == 0) {
+ char *a = (char *)t+3;
+ do_indentation(a);
+ } else if (strncmp(t, ".ti", 3) == 0) {
+ char *a = (char *)t+3;
+ do_tempindent(a);
+ } else if (strncmp(t, ".vs", 3) == 0) {
+ char *a = (char *)t+3;
+ do_verticalspacing(a);
+ } else if (strncmp(t, ".ps", 3) == 0) {
+ char *a = (char *)t+3;
+ do_pointsize(a);
+ } else if (strcmp(t, ".links") == 0) {
+ do_links();
+ } else if (strncmp(t, ".job-name", 9) == 0) {
+ char *a = (char *)t+9;
+ do_job_name(a);
+ } else if (strncmp(t, ".head", 5) == 0) {
+ char *a = (char *)t+5;
+ do_head(a);
+ } else if (strcmp(t, ".no-auto-rule") == 0) {
+ auto_rule = FALSE;
+ } else if (strcmp(t, ".tab-ts") == 0) {
+ do_tab_ts(g);
+ } else if (strcmp(t, ".tab-te") == 0) {
+ do_tab_te();
+ } else if (strncmp(t, ".col ", 5) == 0) {
+ char *a = (char *)t+4;
+ do_col(a);
+ } else if (strncmp(t, "tab ", 4) == 0) {
+ char *a = (char *)t+3;
+ do_tab(a);
+ } else if (strncmp(t, "tab0", 4) == 0) {
+ do_tab0();
+ }
+}
+
+/*
+ * do_math - prints out the equation
+ */
+
+void html_printer::do_math (text_glob *g)
+{
+ do_font(g);
+ if (current_paragraph->emitted_text())
+ html.put_string(g->text_string+9);
+ else
+ do_end_para(g);
+}
+
+/*
+ * is_in_middle - returns TRUE if the positions left..right are in the
+ * center of the page.
+ */
+
+int html_printer::is_in_middle (int left, int right)
+{
+ return( abs(abs(left-pageoffset) - abs(pageoffset+linelength-right))
+ <= CENTER_TOLERANCE );
+}
+
+/*
+ * flush_globs - runs through the text glob list and emits html.
+ */
+
+void html_printer::flush_globs (void)
+{
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ do {
+ g = page_contents->glyphs.get_data();
+#if 0
+ fprintf(stderr, "[%s:%d:%d:%d:%d]",
+ g->text_string, g->minv, g->minh, g->maxv, g->maxh) ;
+ fflush(stderr);
+#endif
+
+ handle_state_assertion(g);
+
+ if (strcmp(g->text_string, "XXXXXXX") == 0)
+ stop();
+
+ if (g->is_a_tag())
+ troff_tag(g);
+ else if (g->is_a_line())
+ emit_line(g);
+ else {
+ as.check_sp(seen_space);
+ as.check_br(seen_break);
+ seen_break = 0;
+ seen_space = 0;
+ emit_html(g);
+ }
+
+ as.check_fi(fill_on);
+ as.check_ce(end_center);
+ /*
+ * after processing the title (and removing it) the glyph list
+ * might be empty
+ */
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.move_right();
+ }
+ } while (! page_contents->glyphs.is_equal_to_head());
+ }
+}
+
+/*
+ * calc_nf - calculates the _no_ format flag, given the
+ * text glob, g.
+ */
+
+int html_printer::calc_nf (text_glob *g, int nf)
+{
+ if (g != 0) {
+ if (g->is_fi()) {
+ as.check_fi(TRUE);
+ return FALSE;
+ }
+ if (g->is_nf()) {
+ as.check_fi(FALSE);
+ return TRUE;
+ }
+ }
+ as.check_fi(! nf);
+ return nf;
+}
+
+/*
+ * calc_po_in - calculates the, in, po, registers
+ */
+
+void html_printer::calc_po_in (text_glob *g, int nf)
+{
+ if (g->is_in())
+ troff_indent = g->get_arg();
+ else if (g->is_po())
+ pageoffset = g->get_arg();
+ else if (g->is_ti()) {
+ temp_indent = g->get_arg();
+ end_tempindent = 2;
+ } else if (g->is_br() || (nf && g->is_eol())) {
+ if (end_tempindent > 0)
+ end_tempindent--;
+ }
+}
+
+/*
+ * next_horiz_pos - returns the next horiz position.
+ * -1 is returned if it doesn't exist.
+ */
+
+int html_printer::next_horiz_pos (text_glob *g, int nf)
+{
+ int next = -1;
+
+ if ((g != 0) && (g->is_br() || (nf && g->is_eol())))
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.move_right_get_data();
+ if (0 /* nullptr */ == g) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ }
+ else {
+ next = g->minh;
+ page_contents->glyphs.move_left();
+ }
+ }
+ return next;
+}
+
+/*
+ * insert_tab_ts - inserts a tab-ts before, where.
+ */
+
+text_glob *html_printer::insert_tab_ts (text_glob *where)
+{
+ text_glob *start_of_table;
+ text_glob *old_pos = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_to(where);
+ page_contents->glyphs.move_left();
+ // tab table start
+ page_contents->insert_tag(string("devtag:.tab-ts"));
+ page_contents->glyphs.move_right();
+ start_of_table = page_contents->glyphs.get_data();
+ page_contents->glyphs.move_to(old_pos);
+ return start_of_table;
+}
+
+/*
+ * insert_tab_te - inserts a tab-te before the current position
+ * (it skips backwards over .sp/.br)
+ */
+
+void html_printer::insert_tab_te (void)
+{
+ text_glob *g = page_contents->glyphs.get_data();
+ page_contents->dump_page();
+ while (page_contents->glyphs.get_data()->is_a_tag())
+ page_contents->glyphs.move_left();
+ // tab table end
+ page_contents->insert_tag(string("devtag:.tab-te"));
+ while (g != page_contents->glyphs.get_data())
+ page_contents->glyphs.move_right();
+ page_contents->dump_page();
+}
+
+/*
+ * insert_tab_0 - inserts a tab0 before, where.
+ */
+
+void html_printer::insert_tab_0 (text_glob *where)
+{
+ text_glob *old_pos = page_contents->glyphs.get_data();
+
+ page_contents->glyphs.move_to(where);
+ page_contents->glyphs.move_left();
+ // tab0 start of line
+ page_contents->insert_tag(string("devtag:tab0"));
+ page_contents->glyphs.move_right();
+ page_contents->glyphs.move_to(old_pos);
+}
+
+/*
+ * remove_tabs - removes the tabs tags on this line.
+ */
+
+void html_printer::remove_tabs (void)
+{
+ text_glob *orig = page_contents->glyphs.get_data();
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_tail()) {
+ do {
+ g = page_contents->glyphs.get_data();
+ if (g->is_tab()) {
+ page_contents->glyphs.sub_move_right();
+ if (g == orig)
+ orig = page_contents->glyphs.get_data();
+ } else
+ page_contents->glyphs.move_right();
+ } while ((! page_contents->glyphs.is_equal_to_head()) &&
+ (! g->is_eol()));
+
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_left();
+ }
+}
+
+void html_printer::remove_courier_tabs (void)
+{
+ text_glob *g;
+ int line_start = TRUE;
+ int nf = FALSE;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ line_start = TRUE;
+ do {
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+
+ if (line_start) {
+ if (line_start && nf && is_courier_until_eol()) {
+ remove_tabs();
+ g = page_contents->glyphs.get_data();
+ }
+ }
+
+ // line_start = g->is_br() || g->is_nf() || g->is_fi()
+ // || (nf && g->is_eol());
+ line_start = g->is_br() || (nf && g->is_eol());
+ page_contents->glyphs.move_right();
+ } while (! page_contents->glyphs.is_equal_to_head());
+ }
+}
+
+void html_printer::insert_tab0_foreach_tab (void)
+{
+ text_glob *start_of_line = 0;
+ text_glob *g = 0;
+ int seen_tab = FALSE;
+ int seen_col = FALSE;
+ int nf = FALSE;
+
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ start_of_line = page_contents->glyphs.get_data();
+ do {
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+
+ if (g->is_tab())
+ seen_tab = TRUE;
+
+ if (g->is_col())
+ seen_col = TRUE;
+
+ if (g->is_br() || (nf && g->is_eol())) {
+ do {
+ page_contents->glyphs.move_right();
+ g = page_contents->glyphs.get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+ if (page_contents->glyphs.is_equal_to_head()) {
+ if (seen_tab && !seen_col)
+ insert_tab_0(start_of_line);
+ return;
+ }
+ } while (g->is_br() || (nf && g->is_eol()) || g->is_ta());
+ // printf("\nstart_of_line is: %s\n", g->text_string);
+ if (seen_tab && !seen_col) {
+ insert_tab_0(start_of_line);
+ page_contents->glyphs.move_to(g);
+ }
+
+ seen_tab = FALSE;
+ seen_col = FALSE;
+ start_of_line = g;
+ }
+ page_contents->glyphs.move_right();
+ } while (! page_contents->glyphs.is_equal_to_head());
+ if (seen_tab && !seen_col)
+ insert_tab_0(start_of_line);
+
+ }
+}
+
+/*
+ * update_min_max - updates the extent of a column, given the left and
+ * right extents of a glyph, g.
+ */
+
+void html_printer::update_min_max (colType type_of_col,
+ int *minimum, int *maximum,
+ text_glob *g)
+{
+ switch (type_of_col) {
+
+ case tab_tag:
+ break;
+ case tab0_tag:
+ *minimum = g->minh;
+ break;
+ case col_tag:
+ *minimum = g->minh;
+ *maximum = g->maxh;
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * add_table_end - moves left one glyph, adds a table end tag and adds
+ * a debugging string.
+ */
+
+void html_printer::add_table_end (const char *
+#if defined(DEBUG_TABLES)
+ debug_string
+#endif
+)
+{
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+#if defined(DEBUG_TABLES)
+ page_contents->insert_tag(string(debug_string));
+#endif
+}
+
+/*
+ * lookahead_for_tables - checks for .col tags and inserts table
+ * start/end tags
+ */
+
+void html_printer::lookahead_for_tables (void)
+{
+ text_glob *g;
+ text_glob *start_of_line = 0;
+ text_glob *start_of_table = 0;
+ text_glob *last = 0;
+ colType type_of_col = none;
+ int found_col = FALSE;
+ int ncol = 0;
+ int colmin = 0; // pacify compiler
+ int colmax = 0; // pacify compiler
+ html_table *tbl = new html_table(&html, -1);
+ const char *tab_defs = 0;
+ char align = 'L';
+ int nf = FALSE;
+ int old_pageoffset = pageoffset;
+
+ remove_courier_tabs();
+ page_contents->dump_page();
+ insert_tab0_foreach_tab();
+ page_contents->dump_page();
+ if (! page_contents->glyphs.is_empty()) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ g = page_contents->glyphs.get_data();
+ if (g->is_br()) {
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ if (page_contents->glyphs.is_equal_to_head()) {
+ if (tbl != 0) {
+ delete tbl;
+ tbl = 0;
+ }
+ return;
+ }
+
+ start_of_line = g;
+ ncol = 0;
+ if (found_col)
+ last = g;
+ found_col = FALSE;
+ }
+
+ do {
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, " [") ;
+ fprintf(stderr, g->text_string) ;
+ fprintf(stderr, "] ") ;
+ fflush(stderr);
+ if (strcmp(g->text_string, "XXXXXXX") == 0)
+ stop();
+#endif
+
+ nf = calc_nf(g, nf);
+ calc_po_in(g, nf);
+ if (g->is_col()) {
+ if (type_of_col == tab_tag && start_of_table != 0) {
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ page_contents->insert_tag(string("*** TAB -> COL ***"));
+ if (tab_defs != 0)
+ tbl->tab_stops->init(tab_defs);
+ start_of_table = 0;
+ last = 0;
+ }
+ type_of_col = col_tag;
+ found_col = TRUE;
+ ncol = g->get_arg();
+ align = 'L';
+ colmin = 0;
+ colmax = 0;
+ } else if (g->is_tab()) {
+ type_of_col = tab_tag;
+ colmin = g->get_tab_args(&align);
+ align = 'L'; // for now as 'C' and 'R' are broken
+ ncol = tbl->find_tab_column(colmin);
+ colmin += pageoffset + get_troff_indent();
+ colmax = tbl->get_tab_pos(ncol+1);
+ if (colmax > 0)
+ colmax += pageoffset + get_troff_indent();
+ } else if (g->is_tab0()) {
+ if (type_of_col == col_tag && start_of_table != 0) {
+ page_contents->glyphs.move_left();
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ page_contents->insert_tag(string("*** COL -> TAB ***"));
+ start_of_table = 0;
+ last = 0;
+ }
+ if (tab_defs != 0)
+ tbl->tab_stops->init(tab_defs);
+ type_of_col = tab0_tag;
+ ncol = 1;
+ colmin = 0;
+ colmax = tbl->get_tab_pos(2) + pageoffset + get_troff_indent();
+ } else if (! g->is_a_tag())
+ update_min_max(type_of_col, &colmin, &colmax, g);
+ if ((g->is_col() || g->is_tab() || g->is_tab0())
+ && (start_of_line != 0)
+ && (0 /* nullptr */ == start_of_table)) {
+ start_of_table = insert_tab_ts(start_of_line);
+ start_of_line = 0;
+ } else if (g->is_ce() && (start_of_table != 0)) {
+ add_table_end("*** CE ***");
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ last = 0;
+ } else if (g->is_ta()) {
+ tab_defs = g->text_string;
+ if (type_of_col == col_tag)
+ tbl->tab_stops->check_init(tab_defs);
+ if (!tbl->tab_stops->compatible(tab_defs)) {
+ if (start_of_table != 0) {
+ add_table_end("*** TABS ***");
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ type_of_col = none;
+ last = 0;
+ }
+ tbl->tab_stops->init(tab_defs);
+ }
+ }
+ if (((! g->is_a_tag()) || g->is_tab()) && (start_of_table != 0)) {
+ // we are in a table and have a glyph
+ if ((ncol == 0)
+ || (! tbl->add_column(ncol, colmin, colmax, align))) {
+ if (ncol == 0)
+ add_table_end("*** NCOL == 0 ***");
+ else
+ add_table_end("*** CROSSED COLS ***");
+
+ start_of_table->remember_table(tbl);
+ tbl = new html_table(&html, -1);
+ start_of_table = 0;
+ type_of_col = none;
+ last = 0;
+ }
+ }
+ /*
+ * move onto next glob, check whether we are starting a new line
+ */
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ if (0 /* nullptr */ == g) {
+ if (found_col) {
+ page_contents->glyphs.start_from_head();
+ as.reset();
+ last = g;
+ found_col = FALSE;
+ }
+ } else if (g->is_br() || (nf && g->is_eol())) {
+ do {
+ g = page_contents->glyphs.move_right_get_data();
+ handle_state_assertion(g);
+ nf = calc_nf(g, nf);
+ } while ((g != 0) && (g->is_br() || (nf && g->is_eol())));
+ start_of_line = g;
+ ncol = 0;
+ if (found_col)
+ last = g;
+ found_col = FALSE;
+ }
+ } while ((g != 0) && (! page_contents->glyphs.is_equal_to_head()));
+
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, "finished scanning for tables\n");
+#endif
+
+ page_contents->glyphs.start_from_head();
+ if (start_of_table != 0) {
+ if (last != 0)
+ while (last != page_contents->glyphs.get_data())
+ page_contents->glyphs.move_left();
+
+ insert_tab_te();
+ start_of_table->remember_table(tbl);
+ tbl = 0;
+ page_contents->insert_tag(string("*** LAST ***"));
+ }
+ }
+ if (tbl != 0) {
+ delete tbl;
+ tbl = 0;
+ }
+
+ // and reset the registers
+ pageoffset = old_pageoffset;
+ troff_indent = 0;
+ temp_indent = 0;
+ end_tempindent = 0;
+}
+
+void html_printer::flush_page (void)
+{
+ suppress_sub_sup = TRUE;
+ flush_sbuf();
+ page_contents->dump_page();
+ lookahead_for_tables();
+ page_contents->dump_page();
+ flush_globs();
+ current_paragraph->done_para();
+ current_paragraph->flush_text();
+ // move onto a new page
+ delete page_contents;
+#if defined(DEBUG_TABLES)
+ fprintf(stderr, "\n\n*** flushed page ***\n\n");
+ html.simple_comment("new page called");
+#endif
+ page_contents = new page;
+}
+
+/*
+ * determine_space - works out whether we need to write a space.
+ * If last glyph is adjoining, then emit no space.
+ */
+
+void html_printer::determine_space (text_glob *g)
+{
+ if (current_paragraph->is_in_pre()) {
+ /*
+ * .nf has been specified
+ */
+ while (output_hpos < g->minh) {
+ output_hpos += space_width;
+ current_paragraph->emit_space();
+ }
+ } else {
+ if ((output_vpos != g->minv) || (output_hpos < g->minh)) {
+ current_paragraph->emit_space();
+ }
+ }
+}
+
+/*
+ * is_line_start - returns TRUE if we are at the start of a line.
+ */
+
+int html_printer::is_line_start (int nf)
+{
+ int line_start = FALSE;
+ int result = TRUE;
+ text_glob *orig = page_contents->glyphs.get_data();
+ text_glob *g;
+
+ if (! page_contents->glyphs.is_equal_to_head()) {
+ do {
+ page_contents->glyphs.move_left();
+ g = page_contents->glyphs.get_data();
+ result = g->is_a_tag();
+ if (g->is_fi())
+ nf = FALSE;
+ else if (g->is_nf())
+ nf = TRUE;
+ line_start = g->is_col() || g->is_br() || (nf && g->is_eol());
+ } while ((!line_start) && (result));
+ /*
+ * now restore our previous position.
+ */
+ while (page_contents->glyphs.get_data() != orig)
+ page_contents->glyphs.move_right();
+ }
+ return result;
+}
+
+/*
+ * is_font_courier - returns TRUE if the font, f, is courier.
+ */
+
+int html_printer::is_font_courier (font *f)
+{
+ if (f != 0) {
+ const char *fontname = f->get_name();
+
+ return( (fontname != 0) && (fontname[0] == 'C') );
+ }
+ return FALSE;
+}
+
+/*
+ * end_font - shuts down the font corresponding to fontname.
+ */
+
+void html_printer::end_font (const char *fontname)
+{
+ if (strcmp(fontname, "B") == 0) {
+ current_paragraph->done_bold();
+ } else if (strcmp(fontname, "I") == 0) {
+ current_paragraph->done_italic();
+ } else if (strcmp(fontname, "BI") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ } else if (strcmp(fontname, "CR") == 0) {
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CI") == 0) {
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CB") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "CBI") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ }
+}
+
+/*
+ * start_font - starts the font corresponding to name.
+ */
+
+void html_printer::start_font (const char *fontname)
+{
+ if (strcmp(fontname, "R") == 0) {
+ current_paragraph->done_bold();
+ current_paragraph->done_italic();
+ current_paragraph->done_tt();
+ } else if (strcmp(fontname, "B") == 0) {
+ current_paragraph->do_bold();
+ } else if (strcmp(fontname, "I") == 0) {
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "BI") == 0) {
+ current_paragraph->do_bold();
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "CR") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ } else if (strcmp(fontname, "CI") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_italic();
+ } else if (strcmp(fontname, "CB") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_bold();
+ } else if (strcmp(fontname, "CBI") == 0) {
+ if ((! fill_on) && (is_courier_until_eol()) &&
+ is_line_start(! fill_on)) {
+ current_paragraph->do_pre();
+ }
+ current_paragraph->do_tt();
+ current_paragraph->do_italic();
+ current_paragraph->do_bold();
+ }
+}
+
+/*
+ * start_size - from is old font size, to is the new font size.
+ * The HTML elements <big> and <small> respectively
+ * increase and decrease the font size by 20%. We try and
+ * map these onto glyph sizes.
+ */
+
+void html_printer::start_size (int from, int to)
+{
+ if (from < to) {
+ while (from < to) {
+ current_paragraph->do_big();
+ from += SIZE_INCREMENT;
+ }
+ } else if (from > to) {
+ while (from > to) {
+ current_paragraph->do_small();
+ from -= SIZE_INCREMENT;
+ }
+ }
+}
+
+/*
+ * do_font - checks to see whether we need to alter the html font.
+ */
+
+void html_printer::do_font (text_glob *g)
+{
+ /*
+ * check if the output_style.point_size has not been set yet
+ * this allow users to place .ps at the top of their troff files
+ * and grohtml can then treat the .ps value as the base font size (3)
+ */
+ if (output_style.point_size == -1) {
+ output_style.point_size = pointsize;
+ }
+
+ if (g->text_style.f != output_style.f) {
+ if (output_style.f != 0) {
+ end_font(output_style.f->get_name());
+ }
+ output_style.f = g->text_style.f;
+ if (output_style.f != 0) {
+ start_font(output_style.f->get_name());
+ }
+ }
+ if (output_style.point_size != g->text_style.point_size) {
+ do_sup_or_sub(g);
+ if ((output_style.point_size > 0) &&
+ (g->text_style.point_size > 0)) {
+ start_size(output_style.point_size, g->text_style.point_size);
+ }
+ if (g->text_style.point_size > 0) {
+ output_style.point_size = g->text_style.point_size;
+ }
+ }
+ if (output_style.col != g->text_style.col) {
+ current_paragraph->done_color();
+ output_style.col = g->text_style.col;
+ current_paragraph->do_color(&output_style.col);
+ }
+}
+
+/*
+ * start_subscript - returns TRUE if, g, looks like a subscript start.
+ */
+
+int html_printer::start_subscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (output_vpos < g->minv) &&
+ (output_vpos-height > g->maxv) &&
+ (output_style.point_size > g->text_style.point_size));
+}
+
+/*
+ * start_superscript - returns TRUE if, g, looks like a superscript
+ * start.
+ */
+
+int html_printer::start_superscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (output_vpos > g->minv) &&
+ (output_vpos-height < g->maxv) &&
+ (output_style.point_size > g->text_style.point_size));
+}
+
+/*
+ * end_subscript - returns TRUE if, g, looks like the end of a
+ * subscript.
+ */
+
+int html_printer::end_subscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (g->minv < output_vpos) &&
+ (output_vpos-height > g->maxv) &&
+ (output_style.point_size < g->text_style.point_size));
+}
+
+/*
+ * end_superscript - returns TRUE if, g, looks like the end of a
+ * superscript.
+ */
+
+int html_printer::end_superscript (text_glob *g)
+{
+ int r = font::res;
+ int height = output_style.point_size*r/72;
+
+ return ((output_style.point_size != 0) &&
+ (g->minv > output_vpos) &&
+ (output_vpos-height < g->maxv) &&
+ (output_style.point_size < g->text_style.point_size));
+}
+
+/*
+ * do_sup_or_sub - checks to see whether the next glyph is a
+ * subscript/superscript start/end and it calls the
+ * services of html-text to issue the appropriate tags.
+ */
+
+void html_printer::do_sup_or_sub (text_glob *g)
+{
+ if (! suppress_sub_sup) {
+ if (start_subscript(g)) {
+ current_paragraph->do_sub();
+ } else if (start_superscript(g)) {
+ current_paragraph->do_sup();
+ } else if (end_subscript(g)) {
+ current_paragraph->done_sub();
+ } else if (end_superscript(g)) {
+ current_paragraph->done_sup();
+ }
+ }
+}
+
+/*
+ * do_end_para - writes out the html text after shutting down the
+ * current paragraph.
+ */
+
+void html_printer::do_end_para (text_glob *g)
+{
+ do_font(g);
+ current_paragraph->done_para();
+ current_paragraph->remove_para_space();
+ html.put_string(g->text_string+9);
+ output_vpos = g->minv;
+ output_hpos = g->maxh;
+ output_vpos_max = g->maxv;
+ suppress_sub_sup = FALSE;
+}
+
+/*
+ * emit_html - write out the html text
+ */
+
+void html_printer::emit_html (text_glob *g)
+{
+ do_font(g);
+ determine_space(g);
+ current_paragraph->do_emittext(g->text_string, g->text_length);
+ output_vpos = g->minv;
+ output_hpos = g->maxh;
+ output_vpos_max = g->maxv;
+ suppress_sub_sup = FALSE;
+}
+
+/*
+ * flush_sbuf - flushes the current sbuf into the list of glyphs.
+ */
+
+void html_printer::flush_sbuf()
+{
+ if (sbuf.length() > 0) {
+ int r=font::res; // resolution of the device
+ set_style(sbuf_style);
+
+ if (overstrike_detected && (! is_bold(sbuf_style.f))) {
+ font *bold_font = make_bold(sbuf_style.f);
+ if (bold_font != 0)
+ sbuf_style.f = bold_font;
+ }
+
+ page_contents->add(&sbuf_style, sbuf, line_number,
+ (sbuf_vpos - (sbuf_style.point_size * r / 72)),
+ sbuf_start_hpos, sbuf_vpos, sbuf_end_hpos);
+ output_hpos = sbuf_end_hpos;
+ output_vpos = sbuf_vpos;
+ last_sbuf_length = 0;
+ sbuf_prev_hpos = sbuf_end_hpos;
+ overstrike_detected = FALSE;
+ sbuf.clear();
+ }
+}
+
+void html_printer::set_line_thickness(const environment *env)
+{
+ line_thickness = env->size;
+}
+
+void html_printer::draw(int code, int *p, int np,
+ const environment *env)
+{
+ switch (code) {
+
+ case 'l':
+# if 0
+ if (np == 2) {
+ page_contents->add_line(&sbuf_style,
+ line_number,
+ env->hpos, env->vpos,
+ (env->hpos + p[0]), (env->vpos + p[1]),
+ line_thickness);
+ } else {
+ error("2 arguments required for line");
+ }
+# endif
+ break;
+ case 't':
+ {
+ if (np == 0) {
+ line_thickness = -1;
+ } else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ }
+
+ case 'P':
+ break;
+ case 'p':
+ break;
+ case 'E':
+ break;
+ case 'e':
+ break;
+ case 'C':
+ break;
+ case 'c':
+ break;
+ case 'a':
+ break;
+ case '~':
+ break;
+ case 'f':
+ break;
+ case 'F':
+ // fill with color env->fill
+ if (background != 0)
+ delete background;
+ background = new color;
+ *background = *env->fill;
+ break;
+
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+}
+
+html_printer::html_printer()
+: html(0, MAX_LINE_LENGTH),
+ no_of_printed_pages(0),
+ last_sbuf_length(0),
+ overstrike_detected(FALSE),
+ output_hpos(-1),
+ output_vpos(-1),
+ output_vpos_max(-1),
+ line_thickness(-1),
+ inside_font_style(0),
+ page_number(0),
+ header_indent(-1),
+ suppress_sub_sup(TRUE),
+ cutoff_heading(100),
+ indent(0),
+ table(0),
+ end_center(0),
+ end_tempindent(0),
+ next_tag(INLINE),
+ fill_on(TRUE),
+ max_linelength(-1),
+ linelength(0),
+ pageoffset(0),
+ troff_indent(0),
+ device_indent(0),
+ temp_indent(0),
+ pointsize(base_point_size),
+ line_number(0),
+ background(default_background),
+ seen_indent(FALSE),
+ next_indent(0),
+ seen_pageoffset(FALSE),
+ next_pageoffset(0),
+ seen_linelength(FALSE),
+ next_linelength(0),
+ seen_center(FALSE),
+ next_center(0),
+ seen_space(0),
+ seen_break(0),
+ current_column(0),
+ row_space(FALSE)
+{
+ file_list.add_new_file(xtmpfile());
+ html.set_file(file_list.get_file());
+ if (font::hor != 24)
+ fatal("horizontal motion quantum must be 24");
+ if (font::vert != 40)
+ fatal("vertical motion quantum must be 40");
+#if 0
+ // should be sorted html..
+ if (font::res % (font::sizescale*72) != 0)
+ fatal("res must be a multiple of 72*sizescale");
+#endif
+ int r = font::res;
+ int point = 0;
+ while (r % 10 == 0) {
+ r /= 10;
+ point++;
+ }
+ res = r;
+ html.set_fixed_point(point);
+ space_glyph = name_to_glyph("space");
+ space_width = font::hor;
+ paper_length = font::paperlength;
+ linelength = font::res*13/2;
+ if (paper_length == 0)
+ paper_length = 11*font::res;
+
+ page_contents = new page();
+}
+
+/*
+ * add_to_sbuf - adds character code or name to the sbuf.
+ */
+
+void html_printer::add_to_sbuf (glyph *g, const string &s)
+{
+ if (0 /* nullptr */ == sbuf_style.f)
+ return;
+
+ const char *html_glyph = 0;
+ unsigned int code = sbuf_style.f->get_code(g);
+
+ if (s.empty()) {
+ if (sbuf_style.f->contains(g))
+ html_glyph = get_html_entity(sbuf_style.f->get_code(g));
+ else
+ html_glyph = 0;
+
+ if ((0 /* nullptr */ == html_glyph) && (code >= UNICODE_DESC_START))
+ html_glyph = to_unicode(code);
+ } else
+ html_glyph = get_html_translation(sbuf_style.f, s);
+
+ last_sbuf_length = sbuf.length();
+ if (0 /* nullptr */ == html_glyph)
+ sbuf += ((char)code);
+ else
+ sbuf += html_glyph;
+}
+
+int html_printer::sbuf_continuation (glyph *g, const char *name,
+ const environment *env, int w)
+{
+ /*
+ * lets see whether the glyph is closer to the end of sbuf
+ */
+ if ((sbuf_end_hpos == env->hpos)
+ || ((sbuf_prev_hpos < sbuf_end_hpos)
+ && (env->hpos < sbuf_end_hpos)
+ && ((sbuf_end_hpos-env->hpos < env->hpos-sbuf_prev_hpos)))) {
+ add_to_sbuf(g, name);
+ sbuf_prev_hpos = sbuf_end_hpos;
+ sbuf_end_hpos += w + sbuf_kern;
+ return TRUE;
+ } else {
+ if ((env->hpos >= sbuf_end_hpos)
+ && ((sbuf_kern == 0)
+ || (sbuf_end_hpos - sbuf_kern != env->hpos))) {
+ /*
+ * lets see whether a space is needed or not
+ */
+
+ if (env->hpos-sbuf_end_hpos < space_width) {
+ add_to_sbuf(g, name);
+ sbuf_prev_hpos = sbuf_end_hpos;
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * get_html_translation - given the position of the character and its
+ * name return the device encoding for such
+ * character.
+ */
+
+const char *get_html_translation (font *f, const string &name)
+{
+ if ((0 /* nullptr */ == f) || name.empty())
+ return 0;
+ else {
+ glyph *g = name_to_glyph((char *)(name + '\0').contents());
+ if (f->contains(g))
+ return get_html_entity(f->get_code(g));
+ else
+ return 0;
+ }
+}
+
+/*
+ * get_html_entity - given a Unicode character's code point, return an
+ * HTML entity that represents the character, if the
+ * character cannot represent itself in all contexts.
+ * the return value, if not a null pointer, is
+ * allocated in a static buffer and is only valid
+ * until the next call of this function.
+ */
+static const char *get_html_entity (unsigned int code)
+{
+ if (code < UNICODE_DESC_START) {
+ switch (code) {
+ case 0x0022: return "&quot;";
+ case 0x0026: return "&amp;";
+ case 0x003C: return "&lt;";
+ case 0x003E: return "&gt;";
+ default: return 0;
+ }
+ } else {
+ switch (code) {
+ case 0x00A0: return "&nbsp;";
+ case 0x00A1: return "&iexcl;";
+ case 0x00A2: return "&cent;";
+ case 0x00A3: return "&pound;";
+ case 0x00A4: return "&curren;";
+ case 0x00A5: return "&yen;";
+ case 0x00A6: return "&brvbar;";
+ case 0x00A7: return "&sect;";
+ case 0x00A8: return "&uml;";
+ case 0x00A9: return "&copy;";
+ case 0x00AA: return "&ordf;";
+ case 0x00AB: return "&laquo;";
+ case 0x00AC: return "&not;";
+ case 0x00AE: return "&reg;";
+ case 0x00AF: return "&macr;";
+ case 0x00B0: return "&deg;";
+ case 0x00B1: return "&plusmn;";
+ case 0x00B2: return "&sup2;";
+ case 0x00B3: return "&sup3;";
+ case 0x00B4: return "&acute;";
+ case 0x00B5: return "&micro;";
+ case 0x00B6: return "&para;";
+ case 0x00B7: return "&middot;";
+ case 0x00B8: return "&cedil;";
+ case 0x00B9: return "&sup1;";
+ case 0x00BA: return "&ordm;";
+ case 0x00BB: return "&raquo;";
+ case 0x00BC: return "&frac14;";
+ case 0x00BD: return "&frac12;";
+ case 0x00BE: return "&frac34;";
+ case 0x00BF: return "&iquest;";
+ case 0x00C0: return "&Agrave;";
+ case 0x00C1: return "&Aacute;";
+ case 0x00C2: return "&Acirc;";
+ case 0x00C3: return "&Atilde;";
+ case 0x00C4: return "&Auml;";
+ case 0x00C5: return "&Aring;";
+ case 0x00C6: return "&AElig;";
+ case 0x00C7: return "&Ccedil;";
+ case 0x00C8: return "&Egrave;";
+ case 0x00C9: return "&Eacute;";
+ case 0x00CA: return "&Ecirc;";
+ case 0x00CB: return "&Euml;";
+ case 0x00CC: return "&Igrave;";
+ case 0x00CD: return "&Iacute;";
+ case 0x00CE: return "&Icirc;";
+ case 0x00CF: return "&Iuml;";
+ case 0x00D0: return "&ETH;";
+ case 0x00D1: return "&Ntilde;";
+ case 0x00D2: return "&Ograve;";
+ case 0x00D3: return "&Oacute;";
+ case 0x00D4: return "&Ocirc;";
+ case 0x00D5: return "&Otilde;";
+ case 0x00D6: return "&Ouml;";
+ case 0x00D7: return "&times;";
+ case 0x00D8: return "&Oslash;";
+ case 0x00D9: return "&Ugrave;";
+ case 0x00DA: return "&Uacute;";
+ case 0x00DB: return "&Ucirc;";
+ case 0x00DC: return "&Uuml;";
+ case 0x00DD: return "&Yacute;";
+ case 0x00DE: return "&THORN;";
+ case 0x00DF: return "&szlig;";
+ case 0x00E0: return "&agrave;";
+ case 0x00E1: return "&aacute;";
+ case 0x00E2: return "&acirc;";
+ case 0x00E3: return "&atilde;";
+ case 0x00E4: return "&auml;";
+ case 0x00E5: return "&aring;";
+ case 0x00E6: return "&aelig;";
+ case 0x00E7: return "&ccedil;";
+ case 0x00E8: return "&egrave;";
+ case 0x00E9: return "&eacute;";
+ case 0x00EA: return "&ecirc;";
+ case 0x00EB: return "&euml;";
+ case 0x00EC: return "&igrave;";
+ case 0x00ED: return "&iacute;";
+ case 0x00EE: return "&icirc;";
+ case 0x00EF: return "&iuml;";
+ case 0x00F0: return "&eth;";
+ case 0x00F1: return "&ntilde;";
+ case 0x00F2: return "&ograve;";
+ case 0x00F3: return "&oacute;";
+ case 0x00F4: return "&ocirc;";
+ case 0x00F5: return "&otilde;";
+ case 0x00F6: return "&ouml;";
+ case 0x00F7: return "&divide;";
+ case 0x00F8: return "&oslash;";
+ case 0x00F9: return "&ugrave;";
+ case 0x00FA: return "&uacute;";
+ case 0x00FB: return "&ucirc;";
+ case 0x00FC: return "&uuml;";
+ case 0x00FD: return "&yacute;";
+ case 0x00FE: return "&thorn;";
+ case 0x00FF: return "&yuml;";
+ case 0x0152: return "&OElig;";
+ case 0x0153: return "&oelig;";
+ case 0x0160: return "&Scaron;";
+ case 0x0161: return "&scaron;";
+ case 0x0178: return "&Yuml;";
+ case 0x0192: return "&fnof;";
+ case 0x0391: return "&Alpha;";
+ case 0x0392: return "&Beta;";
+ case 0x0393: return "&Gamma;";
+ case 0x0394: return "&Delta;";
+ case 0x0395: return "&Epsilon;";
+ case 0x0396: return "&Zeta;";
+ case 0x0397: return "&Eta;";
+ case 0x0398: return "&Theta;";
+ case 0x0399: return "&Iota;";
+ case 0x039A: return "&Kappa;";
+ case 0x039B: return "&Lambda;";
+ case 0x039C: return "&Mu;";
+ case 0x039D: return "&Nu;";
+ case 0x039E: return "&Xi;";
+ case 0x039F: return "&Omicron;";
+ case 0x03A0: return "&Pi;";
+ case 0x03A1: return "&Rho;";
+ case 0x03A3: return "&Sigma;";
+ case 0x03A4: return "&Tau;";
+ case 0x03A5: return "&Upsilon;";
+ case 0x03A6: return "&Phi;";
+ case 0x03A7: return "&Chi;";
+ case 0x03A8: return "&Psi;";
+ case 0x03A9: return "&Omega;";
+ case 0x03B1: return "&alpha;";
+ case 0x03B2: return "&beta;";
+ case 0x03B3: return "&gamma;";
+ case 0x03B4: return "&delta;";
+ case 0x03B5: return "&epsilon;";
+ case 0x03B6: return "&zeta;";
+ case 0x03B7: return "&eta;";
+ case 0x03B8: return "&theta;";
+ case 0x03B9: return "&iota;";
+ case 0x03BA: return "&kappa;";
+ case 0x03BB: return "&lambda;";
+ case 0x03BC: return "&mu;";
+ case 0x03BD: return "&nu;";
+ case 0x03BE: return "&xi;";
+ case 0x03BF: return "&omicron;";
+ case 0x03C0: return "&pi;";
+ case 0x03C1: return "&rho;";
+ case 0x03C2: return "&sigmaf;";
+ case 0x03C3: return "&sigma;";
+ case 0x03C4: return "&tau;";
+ case 0x03C5: return "&upsilon;";
+ case 0x03C6: return "&phi;";
+ case 0x03C7: return "&chi;";
+ case 0x03C8: return "&psi;";
+ case 0x03C9: return "&omega;";
+ case 0x03D1: return "&thetasym;";
+ case 0x03D6: return "&piv;";
+ case 0x2013: return "&ndash;";
+ case 0x2014: return "&mdash;";
+ case 0x2018: return "&lsquo;";
+ case 0x2019: return "&rsquo;";
+ case 0x201A: return "&sbquo;";
+ case 0x201C: return "&ldquo;";
+ case 0x201D: return "&rdquo;";
+ case 0x201E: return "&bdquo;";
+ case 0x2020: return "&dagger;";
+ case 0x2021: return "&Dagger;";
+ case 0x2022: return "&bull;";
+ case 0x2030: return "&permil;";
+ case 0x2032: return "&prime;";
+ case 0x2033: return "&Prime;";
+ case 0x2039: return "&lsaquo;";
+ case 0x203A: return "&rsaquo;";
+ case 0x203E: return "&oline;";
+ case 0x2044: return "&frasl;";
+ case 0x20AC: return "&euro;";
+ case 0x2111: return "&image;";
+ case 0x2118: return "&weierp;";
+ case 0x211C: return "&real;";
+ case 0x2122: return "&trade;";
+ case 0x2135: return "&alefsym;";
+ case 0x2190: return "&larr;";
+ case 0x2191: return "&uarr;";
+ case 0x2192: return "&rarr;";
+ case 0x2193: return "&darr;";
+ case 0x2194: return "&harr;";
+ case 0x21D0: return "&lArr;";
+ case 0x21D1: return "&uArr;";
+ case 0x21D2: return "&rArr;";
+ case 0x21D3: return "&dArr;";
+ case 0x21D4: return "&hArr;";
+ case 0x2200: return "&forall;";
+ case 0x2202: return "&part;";
+ case 0x2203: return "&exist;";
+ case 0x2205: return "&empty;";
+ case 0x2207: return "&nabla;";
+ case 0x2208: return "&isin;";
+ case 0x2209: return "&notin;";
+ case 0x220B: return "&ni;";
+ case 0x220F: return "&prod;";
+ case 0x2211: return "&sum;";
+ case 0x2212: return "&minus;";
+ case 0x2217: return "&lowast;";
+ case 0x221A: return "&radic;";
+ case 0x221D: return "&prop;";
+ case 0x221E: return "&infin;";
+ case 0x2220: return "&ang;";
+ case 0x2227: return "&and;";
+ case 0x2228: return "&or;";
+ case 0x2229: return "&cap;";
+ case 0x222A: return "&cup;";
+ case 0x222B: return "&int;";
+ case 0x2234: return "&there4;";
+ case 0x223C: return "&sim;";
+ case 0x2245: return "&cong;";
+ case 0x2248: return "&asymp;";
+ case 0x2260: return "&ne;";
+ case 0x2261: return "&equiv;";
+ case 0x2264: return "&le;";
+ case 0x2265: return "&ge;";
+ case 0x2282: return "&sub;";
+ case 0x2283: return "&sup;";
+ case 0x2284: return "&nsub;";
+ case 0x2286: return "&sube;";
+ case 0x2287: return "&supe;";
+ case 0x2295: return "&oplus;";
+ case 0x2297: return "&otimes;";
+ case 0x22A5: return "&perp;";
+ case 0x22C5: return "&sdot;";
+ case 0x2308: return "&lceil;";
+ case 0x2309: return "&rceil;";
+ case 0x230A: return "&lfloor;";
+ case 0x230B: return "&rfloor;";
+ case 0x2329: return "&lang;";
+ case 0x232A: return "&rang;";
+ case 0x25CA: return "&loz;";
+ case 0x2660: return "&spades;";
+ case 0x2663: return "&clubs;";
+ case 0x2665: return "&hearts;";
+ case 0x2666: return "&diams;";
+ case 0x27E8: return "&lang;";
+ case 0x27E9: return "&rang;";
+ default: return to_unicode(code);
+ }
+ }
+}
+
+/*
+ * overstrike - returns TRUE if the glyph (i, name) is going to
+ * overstrike a previous glyph in sbuf. If TRUE the font
+ * is changed to bold and the previous sbuf is flushed.
+ */
+
+int html_printer::overstrike(glyph *g, const char *name,
+ const environment *env, int w)
+{
+ if ((env->hpos < sbuf_end_hpos)
+ || ((sbuf_kern != 0) && (sbuf_end_hpos - sbuf_kern < env->hpos)))
+ {
+ /*
+ * at this point we have detected an overlap
+ */
+ if (overstrike_detected) {
+ /* already detected, remove previous glyph and use this glyph */
+ sbuf.set_length(last_sbuf_length);
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ } else {
+ /* first time we have detected an overstrike in the sbuf */
+ sbuf.set_length(last_sbuf_length); /* remove previous glyph */
+ if (! is_bold(sbuf_style.f))
+ flush_sbuf();
+ overstrike_detected = TRUE;
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * set_char - adds a character into the sbuf if it is a continuation
+ * with the previous word otherwise flush the current sbuf
+ * and add character anew.
+ */
+
+void html_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *name)
+{
+ style sty(f, env->size, env->height, env->slant, env->fontno,
+ *env->col);
+ if (sty.slant != 0) {
+ if (sty.slant > 80 || sty.slant < -80) {
+ error("slant of %1 degrees out of range", sty.slant);
+ sty.slant = 0;
+ }
+ }
+ if (((!sbuf.empty())
+ && (sty == sbuf_style)
+ && (sbuf_vpos == env->vpos))
+ && (sbuf_continuation(g, name, env, w)
+ || overstrike(g, name, env, w)))
+ return;
+
+ flush_sbuf();
+ if (0 /* nullptr */ == sbuf_style.f)
+ sbuf_style = sty;
+ add_to_sbuf(g, name);
+ sbuf_end_hpos = env->hpos + w;
+ sbuf_start_hpos = env->hpos;
+ sbuf_prev_hpos = env->hpos;
+ sbuf_vpos = env->vpos;
+ sbuf_style = sty;
+ sbuf_kern = 0;
+}
+
+/*
+ * set_numbered_char - handle numbered characters. Negative values are
+ * interpreted as unbreakable spaces; the value
+ * (taken positive) gives the width.
+ */
+
+void html_printer::set_numbered_char(int num, const environment *env,
+ int *widthp)
+{
+ int nbsp_width = 0;
+ if (num < 0) {
+ nbsp_width = -num;
+ num = 160; // &nbsp;
+ }
+ glyph *g = number_to_glyph(num);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return;
+ }
+ font *f = font_table[fn];
+ if (f == 0) {
+ error("no font mounted at position %1", fn);
+ return;
+ }
+ if (!f->contains(g)) {
+ error("font '%1' does not contain numbered character %2",
+ f->get_name(),
+ num);
+ return;
+ }
+ int w;
+ if (nbsp_width)
+ w = nbsp_width;
+ else
+ w = f->get_width(g, env->size);
+ w = round_width(w);
+ if (widthp)
+ *widthp = w;
+ set_char(g, f, env, w, 0);
+}
+
+glyph *html_printer::set_char_and_width(const char *nm,
+ const environment *env,
+ int *widthp, font **f)
+{
+ glyph *g = name_to_glyph(nm);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return UNDEFINED_GLYPH;
+ }
+ *f = font_table[fn];
+ if (*f == 0) {
+ error("no font mounted at position %1", fn);
+ return UNDEFINED_GLYPH;
+ }
+ if (!(*f)->contains(g)) {
+ if (nm[0] != '\0' && nm[1] == '\0')
+ error("font '%1' does not contain ordinary character '%2'",
+ (*f)->get_name(), nm[0]);
+ else
+ error("font '%1' does not contain special character '%2'",
+ (*f)->get_name(), nm);
+ return UNDEFINED_GLYPH;
+ }
+ int w = (*f)->get_width(g, env->size);
+ w = round_width(w);
+ if (widthp)
+ *widthp = w;
+ return g;
+}
+
+/*
+ * write_title - writes the title to this document
+ */
+
+void html_printer::write_title (int in_head)
+{
+ if (title.has_been_found) {
+ if (in_head) {
+ html.put_string("<title>");
+ html.put_string(title.text);
+ html.put_string("</title>").nl().nl();
+ } else {
+ title.has_been_written = TRUE;
+ if (title.with_h1) {
+ if (dialect == xhtml)
+ html.put_string("<h1>");
+ else
+ html.put_string("<h1 align=\"center\">");
+ html.put_string(title.text);
+ html.put_string("</h1>").nl().nl();
+ }
+ }
+ } else if (in_head) {
+ // place empty title tags to help conform to 'tidy'
+ html.put_string("<title></title>").nl();
+ }
+}
+
+/*
+ * write_rule - emits HTML rule element if the auto_rule is TRUE.
+ */
+
+static void write_rule (void)
+{
+ if (auto_rule) {
+ if (dialect == xhtml)
+ fputs("<hr/>\n", stdout);
+ else
+ fputs("<hr>\n", stdout);
+ }
+}
+
+void html_printer::begin_page(int n)
+{
+ page_number = n;
+#if defined(DEBUGGING)
+ html.begin_comment("Page: ")
+ .put_string(i_to_a(page_number)).end_comment();;
+#endif
+ no_of_printed_pages++;
+
+ output_style.f = 0;
+ output_style.point_size= -1;
+ output_space_code = 32;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ output_hpos = -1;
+ output_vpos = -1;
+ output_vpos_max = -1;
+ current_paragraph = new html_text(&html, dialect);
+ do_indent(get_troff_indent(), pageoffset, linelength);
+ current_paragraph->do_para("", FALSE);
+}
+
+void html_printer::end_page(int)
+{
+ flush_sbuf();
+ flush_page();
+}
+
+font *html_printer::make_font(const char *nm)
+{
+ return html_font::load_html_font(nm);
+}
+
+void html_printer::do_body (void)
+{
+ if (0 /* nullptr */ == background)
+ fputs("<body>\n\n", stdout);
+ else {
+ char buf[(INT_HEXDIGITS * 3) + 1];
+ unsigned int r, g, b;
+
+ background->get_rgb(&r, &g, &b);
+ // we have to scale 0..0xFFFF to 0..0xFF
+ sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
+
+ fputs("<body bgcolor=\"#", stdout);
+ fputs(buf, stdout);
+ fputs("\">\n\n", stdout);
+ }
+}
+
+/*
+ * emit_link - generates: <a href="to">name</a>
+ */
+
+void html_printer::emit_link (const string &to, const char *name)
+{
+ fputs("<a href=\"", stdout);
+ fputs(to.contents(), stdout);
+ fputs("\">", stdout);
+ fputs(name, stdout);
+ fputs("</a>", stdout);
+}
+
+/*
+ * write_navigation - writes out the links which navigate between
+ * file fragments.
+ */
+
+void html_printer::write_navigation (const string &top,
+ const string &prev,
+ const string &next,
+ const string &current)
+{
+ int need_bar = FALSE;
+
+ if (multiple_files) {
+ current_paragraph->done_para();
+ write_rule();
+ if (groff_sig)
+ fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n"
+ "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n"
+ "<colgroup><col class=\"left\"></col>"
+ "<col class=\"right\"></col></colgroup>\n"
+ "<tr><td class=\"left\">", stdout);
+ handle_valid_flag(FALSE);
+ fputs("[ ", stdout);
+ if ((strcmp(prev.contents(), "") != 0)
+ && prev != top
+ && prev != current) {
+ emit_link(prev, "prev");
+ need_bar = TRUE;
+ }
+ if ((strcmp(next.contents(), "") != 0)
+ && next != top
+ && next != current) {
+ if (need_bar)
+ fputs(" | ", stdout);
+ emit_link(next, "next");
+ need_bar = TRUE;
+ }
+ if (top != "<standard input>"
+ && (strcmp(top.contents(), "") != 0)
+ && top != current) {
+ if (need_bar)
+ fputs(" | ", stdout);
+ emit_link(top, "top");
+ }
+ fputs(" ]\n", stdout);
+ if (groff_sig) {
+ fputs("</td><td class=\"right\"><i><small>"
+ "This document was produced using "
+ "<a href=\"http://www.gnu.org/software/groff/\">"
+ "groff-", stdout);
+ fputs(Version_string, stdout);
+ fputs("</a>.</small></i></td></tr></table>\n", stdout);
+ }
+ write_rule();
+ }
+}
+
+/*
+ * do_file_components - scan the file list copying each temporary file
+ * in turn. This has twofold use: firstly to emit
+ * section heading links, between file fragments
+ * if required and secondly to generate jobname
+ * file fragments if required.
+ */
+
+void html_printer::do_file_components (void)
+{
+ int fragment_no = 1;
+ string top;
+ string prev;
+ string next;
+ string current;
+
+ file_list.start_of_list();
+ top = string(job_name);
+ if (dialect == xhtml)
+ top += string(".xhtml");
+ else
+ top += string(".html");
+ top += '\0';
+ next = file_list.next_file_name();
+ next += '\0';
+ current = next;
+ while (file_list.get_file() != 0) {
+ if (fseek(file_list.get_file(), 0L, 0) < 0)
+ fatal("fseek on temporary file failed");
+ html.copy_file(file_list.get_file());
+ fclose(file_list.get_file());
+ file_list.move_next();
+ if (file_list.is_new_output_file()) {
+#ifdef LONG_FOR_TIME_T
+ long t;
+#else
+ time_t t;
+#endif
+
+ if (fragment_no > 1)
+ write_navigation(top, prev, next, current);
+ prev = current;
+ current = next;
+ next = file_list.next_file_name();
+ next += '\0';
+ string split_file = file_list.file_name();
+ split_file += '\0';
+ fflush(stdout);
+ if (!freopen(split_file.contents(), "w", stdout)) {
+ fatal("unable to reopen standard output stream: %1",
+ strerror(errno));
+ }
+ fragment_no++;
+ if (dialect == xhtml)
+ writeHeadMetaStyle();
+
+ if (do_write_creator_comment) {
+ html.begin_comment("Creator : ")
+ .put_string("groff ")
+ .put_string("version ")
+ .put_string(Version_string)
+ .end_comment();
+ }
+
+ if (do_write_date_comment) {
+ t = current_time();
+ html.begin_comment("CreationDate: ")
+ .put_string(ctime(&t), strlen(ctime(&t))-1)
+ .end_comment();
+ }
+
+ if (dialect == html4)
+ writeHeadMetaStyle();
+
+ html.put_string("<title>");
+ html.put_string(split_file.contents());
+ html.put_string("</title>").nl().nl();
+
+ fputs(head_info.contents(), stdout);
+ fputs("</head>\n", stdout);
+ write_navigation(top, prev, next, current);
+ }
+ if (file_list.are_links_required())
+ header.write_headings(stdout, TRUE);
+ }
+ if (fragment_no > 1)
+ write_navigation(top, prev, next, current);
+ else {
+ assert(current_paragraph != 0);
+ current_paragraph->done_para();
+ write_rule();
+ if (valid_flag) {
+ if (groff_sig)
+ fputs("\n\n<table width=\"100%\" border=\"0\" rules=\"none\"\n"
+ "frame=\"void\" cellspacing=\"1\" cellpadding=\"0\">\n"
+ "<colgroup><col class=\"left\"></col>"
+ "<col class=\"right\"></col></colgroup>\n"
+ "<tr><td class=\"left\">", stdout);
+ handle_valid_flag(TRUE);
+ if (groff_sig) {
+ fputs("</td><td class=\"right\"><i><small>"
+ "This document was produced using "
+ "<a href=\"http://www.gnu.org/software/groff/\">"
+ "groff-", stdout);
+ fputs(Version_string, stdout);
+ fputs("</a>.</small></i></td></tr></table>\n", stdout);
+ }
+ write_rule();
+ }
+ }
+}
+
+/*
+ * writeHeadMetaStyle - emits the <head> <meta> and <style> tags and
+ * related information.
+ */
+
+void html_printer::writeHeadMetaStyle (void)
+{
+ if (dialect == html4) {
+ fputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional"
+ "//EN\"\n", stdout);
+ fputs("\"http://www.w3.org/TR/html4/loose.dtd\">\n", stdout);
+ fputs("<html>\n", stdout);
+ fputs("<head>\n", stdout);
+ fputs("<meta name=\"generator\" "
+ "content=\"groff -Thtml, see www.gnu.org\">\n", stdout);
+ fputs("<meta http-equiv=\"Content-Type\" "
+ "content=\"text/html; charset=US-ASCII\">\n", stdout);
+ fputs("<meta name=\"Content-Style\" content=\"text/css\">\n",
+ stdout);
+ fputs("<style type=\"text/css\">\n", stdout);
+ }
+ else {
+ fputs("<?xml version=\"1.0\" encoding=\"us-ascii\"?>\n", stdout);
+ fputs("<!DOCTYPE html PUBLIC \"-//W3C//"
+ "DTD XHTML 1.1 plus MathML 2.0//EN\"\n", stdout);
+ fputs(" \"http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd\"\n",
+ stdout);
+ fputs(" [<!ENTITY mathml \"http://www.w3.org/1998/Math/"
+ "MathML\">]>\n", stdout);
+
+ fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\" "
+ "xml:lang=\"en\">\n", stdout);
+ fputs("<head>\n", stdout);
+ fputs("<meta name=\"generator\" "
+ "content=\"groff -Txhtml, see www.gnu.org\"/>\n", stdout);
+ fputs("<meta http-equiv=\"Content-Type\" "
+ "content=\"text/html; charset=US-ASCII\"/>\n", stdout);
+ fputs("<meta name=\"Content-Style\" content=\"text/css\"/>\n",
+ stdout);
+ fputs("<style type=\"text/css\">\n", stdout);
+ fputs(" .center { text-align: center }\n", stdout);
+ fputs(" .right { text-align: right }\n", stdout);
+ }
+ fputs(" p { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" pre { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" table { margin-top: 0; margin-bottom: 0; "
+ "vertical-align: top }\n", stdout);
+ fputs(" h1 { text-align: center }\n", stdout);
+ fputs("</style>\n", stdout);
+}
+
+html_printer::~html_printer()
+{
+#ifdef LONG_FOR_TIME_T
+ long t;
+#else
+ time_t t;
+#endif
+
+ if (current_paragraph)
+ current_paragraph->flush_text();
+ html.end_line();
+ html.set_file(stdout);
+
+ if (dialect == xhtml)
+ writeHeadMetaStyle();
+
+ if (do_write_creator_comment) {
+ html.begin_comment("Creator : ")
+ .put_string("groff ")
+ .put_string("version ")
+ .put_string(Version_string)
+ .end_comment();
+ }
+
+ if (do_write_date_comment) {
+ t = current_time();
+ html.begin_comment("CreationDate: ")
+ .put_string(ctime(&t), strlen(ctime(&t))-1)
+ .end_comment();
+ }
+
+ if (dialect == html4)
+ writeHeadMetaStyle();
+
+ write_title(TRUE);
+ head_info += '\0';
+ fputs(head_info.contents(), stdout);
+ fputs("</head>\n", stdout);
+ do_body();
+
+ write_title(FALSE);
+ header.write_headings(stdout, FALSE);
+ write_rule();
+#if defined(DEBUGGING)
+ html.begin_comment("Total number of pages: ")
+ .put_string(i_to_a(no_of_printed_pages)).end_comment();
+#endif
+ html.end_line();
+ html.end_line();
+
+ if (multiple_files) {
+ fputs("</body>\n", stdout);
+ fputs("</html>\n", stdout);
+ do_file_components();
+ } else {
+ do_file_components();
+ fputs("</body>\n", stdout);
+ fputs("</html>\n", stdout);
+ }
+}
+
+/*
+ * get_str - returns a duplicate of string, s. The duplicate
+ * string is terminated at the next ',' or ']'.
+ */
+
+static char *get_str (const char *s, char **n)
+{
+ int i = 0;
+ char *v;
+
+ while ((s[i] != (char)0) && (s[i] != ',') && (s[i] != ']'))
+ i++;
+ if (i>0) {
+ v = new char[i+1];
+ memcpy(v, s, i+1);
+ v[i] = (char)0;
+ if (s[i] == ',')
+ (*n) = (char *)&s[i+1];
+ else
+ (*n) = (char *)&s[i];
+ return v;
+ }
+ if (s[i] == ',')
+ (*n) = (char *)&s[1];
+ else
+ (*n) = (char *)s;
+ return 0;
+}
+
+/*
+ * make_val - creates a string from if s is a null pointer.
+ */
+
+char *make_val (char *s, int v, char *id, char *f, char *l)
+{
+ if (0 /* nullptr */ == s) {
+ char buf[30];
+
+ sprintf(buf, "%d", v);
+ return strsave(buf);
+ }
+ else {
+ /*
+ * check that value, s, is the same as, v.
+ */
+ char *t = s;
+
+ while (*t == '=')
+ t++;
+ if (atoi(t) != v) {
+ if (0 /* nullptr */ == f)
+ f = (char *)"stdin";
+ if (0 /* nullptr */ == l)
+ l = (char *)"<none>";
+ fprintf(stderr, "%s:%s: grohtml assertion failed at id%s; "
+ "expected %d, got %s\n", f, l, id, v, s);
+ }
+ return s;
+ }
+}
+
+/*
+ * handle_assertion - handles the assertions created via .www:ASSERT
+ * in www.tmac. See www.tmac for examples. This
+ * method should be called as we are parsing the
+ * ditroff input. It checks the x, y position
+ * assertions. It does _not_ check the troff state
+ * assertions as these are unknown at this point.
+ */
+
+void html_printer::handle_assertion (int minv, int minh,
+ int maxv, int maxh, const char *s)
+{
+ char *n;
+ char *cmd = get_str(s, &n);
+ char *id = get_str(n, &n);
+ char *val = get_str(n, &n);
+ char *file= get_str(n, &n);
+ char *line= get_str(n, &n);
+
+ if (strcmp(cmd, "assertion:[x") == 0)
+ as.addx(cmd, id, make_val(val, minh, id, file, line), file, line);
+ else if (strcmp(cmd, "assertion:[y") == 0)
+ as.addy(cmd, id, make_val(val, minv, id, file, line), file, line);
+ else
+ if (strncmp(cmd, "assertion:[", strlen("assertion:[")) == 0)
+ page_contents->add_tag(&sbuf_style, string(s),
+ line_number, minv, minh, maxv, maxh);
+}
+
+/*
+ * build_state_assertion - builds the troff state assertions.
+ */
+
+void html_printer::handle_state_assertion (text_glob *g)
+{
+ if (g != 0 && g->is_a_tag()
+ && (strncmp(g->text_string, "assertion:[", 11) == 0)) {
+ char *n = (char *)&g->text_string[11];
+ char *cmd = get_str(n, &n);
+ char *val = get_str(n, &n);
+ (void)get_str(n, &n); // unused
+ char *file= get_str(n, &n);
+ char *line= get_str(n, &n);
+
+ as.build(cmd, val, file, line);
+ }
+}
+
+/*
+ * special - handle all x X requests from troff. For post-html they
+ * allow users to pass raw HTML commands, turn auto linked
+ * headings off/on, and so forth.
+ */
+
+void html_printer::special(char *s, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+ if (s != 0) {
+ flush_sbuf();
+ if (env->fontno >= 0) {
+ style sty(get_font_from_index(env->fontno), env->size,
+ env->height, env->slant, env->fontno, *env->col);
+ sbuf_style = sty;
+ }
+
+ if (strncmp(s, "html:", 5) == 0) {
+ int r=font::res; /* resolution of the device */
+ font *f=sbuf_style.f;
+
+ if (0 /* nullptr */ == f)
+ f = font::load_font("TR");
+
+ /*
+ * pass rest of string through to html output during flush
+ */
+ page_contents->add_and_encode(&sbuf_style, string(&s[5]),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos,
+ FALSE);
+
+ /*
+ * assume that the html command has no width, if it does then
+ * hopefully troff will have fudged this in a macro by requesting
+ * that the formatting move right by the appropriate amount.
+ */
+ } else if ((strncmp(s, "html</p>:", 9) == 0) ||
+ (strncmp(s, "html<?p>:", 9) == 0) ||
+ (strncmp(s, "math<?p>:", 9) == 0)) {
+ int r=font::res; /* resolution of the device */
+ font *f=sbuf_style.f;
+ string t;
+
+ if (0 /* nullptr */ == f)
+ f = font::load_font("TR");
+
+ if (strncmp(s, "math<?p>:", 9) == 0) {
+ if (strncmp((char *)&s[9], "<math>", 6) == 0) {
+ s[9] = '\0';
+ t = s;
+ t += "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">";
+ t += (char *)&s[15];
+ t += '\0';
+ s = (char *)&t[0];
+ }
+ }
+
+ /*
+ * need to pass all of string through to html output during flush
+ */
+ page_contents->add_and_encode(&sbuf_style, string(s),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos,
+ TRUE);
+
+ /*
+ * assume that the html command has no width, if it does then
+ * hopefully troff will have fudged this in a macro by
+ * requesting that the formatting move right by the appropriate
+ * amount.
+ */
+
+ } else if (strncmp(s, "index:", 6) == 0) {
+ cutoff_heading = atoi(&s[6]);
+ } else if (strncmp(s, "assertion:[", 11) == 0) {
+ int r=font::res; /* resolution of the device */
+
+ handle_assertion(env->vpos-env->size*r/72, env->hpos,
+ env->vpos, env->hpos, s);
+ }
+ }
+}
+
+/*
+ * devtag - handles device troff tags sent from the 'troff'.
+ * These include the troff state machine tags:
+ * .br, .sp, .in, .tl, .ll etc
+ *
+ * (see man 5 grohtml_tags).
+ */
+
+void html_printer::devtag (char *s, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+
+ if (s != 0) {
+ flush_sbuf();
+ if (env->fontno >= 0) {
+ style sty(get_font_from_index(env->fontno), env->size,
+ env->height, env->slant, env->fontno, *env->col);
+ sbuf_style = sty;
+ }
+
+ if (strncmp(s, "devtag:", strlen("devtag:")) == 0) {
+ int r=font::res; /* resolution of the device */
+
+ page_contents->add_tag(&sbuf_style, string(s),
+ line_number,
+ env->vpos-env->size*r/72, env->hpos,
+ env->vpos , env->hpos);
+ }
+ }
+}
+
+
+/*
+ * taken from number.cpp in src/roff/troff, [hunits::hunits(units x)]
+ */
+
+int html_printer::round_width(int x)
+{
+ int r = font::hor;
+ int n;
+
+ // don't depend on rounding direction for division of negative ints
+ if (r == 1)
+ n = x;
+ else
+ n = (x < 0
+ ? -((-x + r/2 - 1)/r)
+ : (x + r/2 - 1)/r);
+ return n * r;
+}
+
+/*
+ * handle_valid_flag - emits a valid XHTML 1.1 or HTML 4.01 button,
+ * provided -V was supplied on the command line.
+ */
+
+void html_printer::handle_valid_flag (int needs_para)
+{
+ if (valid_flag) {
+ if (needs_para)
+ fputs("<p>", stdout);
+ if (dialect == xhtml)
+ fputs("<a href=\"http://validator.w3.org/check?uri=referer\">"
+ "<img src=\"http://www.w3.org/Icons/valid-xhtml11-blue\" "
+ "alt=\"Valid XHTML 1.1 Transitional\" "
+ "height=\"31\" width=\"88\" /></a>\n", stdout);
+ else
+ fputs("<a href=\"http://validator.w3.org/check?uri=referer\">"
+ "<img src=\"http://www.w3.org/Icons/valid-html401-blue\" "
+ "alt=\"Valid HTML 4.01 Transitional\" "
+ "height=\"31\" width=\"88\"></a>\n", stdout);
+ if (needs_para)
+ fputs("</p>", stdout);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv,
+ "a:bCdD:eF:g:Ghi:I:j:lno:prs:S:vVx:y", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'a':
+ /* text antialiasing bits - handled by pre-html */
+ break;
+ case 'b':
+ // set background color to white
+ default_background = new color;
+ default_background->set_gray(color::MAX_COLOR_VAL);
+ break;
+ case 'C':
+ // Don't write CreationDate HTML comments.
+ do_write_date_comment = FALSE;
+ break;
+ case 'd':
+ /* handled by pre-html */
+ break;
+ case 'D':
+ /* handled by pre-html */
+ break;
+ case 'e':
+ /* handled by pre-html */
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'g':
+ /* graphic antialiasing bits - handled by pre-html */
+ break;
+ case 'G':
+ // Don't write Creator HTML comments.
+ do_write_creator_comment = FALSE;
+ break;
+ case 'h':
+ /* do not use the Hn headings of html, but manufacture our own */
+ manufacture_headings = TRUE;
+ break;
+ case 'i':
+ /* handled by pre-html */
+ break;
+ case 'I':
+ /* handled by pre-html */
+ break;
+ case 'j':
+ multiple_files = TRUE;
+ job_name = optarg;
+ break;
+ case 'l':
+ auto_links = FALSE;
+ break;
+ case 'n':
+ simple_anchors = TRUE;
+ break;
+ case 'o':
+ /* handled by pre-html */
+ break;
+ case 'p':
+ /* handled by pre-html */
+ break;
+ case 'r':
+ auto_rule = FALSE;
+ break;
+ case 's':
+ base_point_size = atoi(optarg);
+ break;
+ case 'S':
+ split_level = atoi(optarg) + 1;
+ break;
+ case 'v':
+ printf("GNU post-grohtml (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'V':
+ valid_flag = TRUE;
+ break;
+ case 'x':
+ if (strcmp(optarg, "x") == 0) {
+ dialect = xhtml;
+ simple_anchors = TRUE;
+ } else if (strcmp(optarg, "4") == 0)
+ dialect = html4;
+ else
+ warning("unsupported HTML dialect: '%1'", optarg);
+ break;
+ case 'y':
+ groff_sig = TRUE;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ if (optind >= argc) {
+ do_file("-");
+ } else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-bCGhlnrVy] [-F font-directory] [-j output-stem]"
+" [-s base-type-size] [-S heading-level] [-x html-dialect] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grolbp/charset.h b/src/devices/grolbp/charset.h
new file mode 100644
index 0000000..c4e43a0
--- /dev/null
+++ b/src/devices/grolbp/charset.h
@@ -0,0 +1,89 @@
+// -*- C++ -*-
+/* Copyright (C) 2000-2020 Free Software Foundation, Inc.
+ Written by Francisco Andrés Verdú <pandres@dragonet.es> with many ideas
+ taken from the other groff drivers.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// Definition of the WP54 character set
+
+unsigned char symset[] = {
+0x57,0x50,0x35,0x34,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61,
+0x72,0x64,0x65,0x2d,0x42,0x6f,0x6f,0x6b,0x00,0x41,0x76,
+0x61,0x6e,0x74,0x47,0x61,0x72,0x64,0x65,0x2d,0x44,0x65,
+0x6d,0x69,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61,0x72,
+0x64,0x65,0x2d,0x42,0x6f,0x6f,0x6b,0x4f,0x62,0x6c,0x69,
+0x71,0x75,0x65,0x00,0x41,0x76,0x61,0x6e,0x74,0x47,0x61,
+0x72,0x64,0x65,0x2d,0x44,0x65,0x6d,0x69,0x4f,0x62,0x6c,
+0x69,0x71,0x75,0x65,0x00,0x42,0x6f,0x6f,0x6b,0x6d,0x61,
+0x6e,0x2d,0x4c,0x69,0x67,0x68,0x74,0x00,0x42,0x6f,0x6f,
+0x6b,0x6d,0x61,0x6e,0x2d,0x44,0x65,0x6d,0x69,0x00,0x42,
+0x6f,0x6f,0x6b,0x6d,0x61,0x6e,0x2d,0x4c,0x69,0x67,0x68,
+0x74,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x42,0x6f,0x6f,
+0x6b,0x6d,0x61,0x6e,0x2d,0x44,0x65,0x6d,0x69,0x49,0x74,
+0x61,0x6c,0x69,0x63,0x00,0x43,0x65,0x6e,0x74,0x75,0x72,
+0x79,0x53,0x63,0x68,0x6c,0x62,0x6b,0x2d,0x52,0x6f,0x6d,
+0x61,0x6e,0x00,0x43,0x65,0x6e,0x74,0x75,0x72,0x79,0x53,
+0x63,0x68,0x6c,0x62,0x6b,0x2d,0x42,0x6f,0x6c,0x64,0x00,
+0x43,0x65,0x6e,0x74,0x75,0x72,0x79,0x53,0x63,0x68,0x6c,
+0x62,0x6b,0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x43,
+0x65,0x6e,0x74,0x75,0x72,0x79,0x53,0x63,0x68,0x6c,0x62,
+0x6b,0x2d,0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,0x69,
+0x63,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x52,0x6f,0x6d,
+0x61,0x6e,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x42,0x6f,
+0x6c,0x64,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,0x49,0x74,
+0x61,0x6c,0x69,0x63,0x00,0x44,0x75,0x74,0x63,0x68,0x2d,
+0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,
+0x53,0x77,0x69,0x73,0x73,0x00,0x53,0x77,0x69,0x73,0x73,
+0x2d,0x42,0x6f,0x6c,0x64,0x00,0x53,0x77,0x69,0x73,0x73,
+0x2d,0x4f,0x62,0x6c,0x69,0x71,0x75,0x65,0x00,0x53,0x77,
+0x69,0x73,0x73,0x2d,0x42,0x6f,0x6c,0x64,0x4f,0x62,0x6c,
+0x69,0x71,0x75,0x65,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,
+0x4e,0x61,0x72,0x72,0x6f,0x77,0x00,0x53,0x77,0x69,0x73,
+0x73,0x2d,0x4e,0x61,0x72,0x72,0x6f,0x77,0x2d,0x42,0x6f,
+0x6c,0x64,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,0x4e,0x61,
+0x72,0x72,0x6f,0x77,0x2d,0x4f,0x62,0x6c,0x69,0x71,0x75,
+0x65,0x00,0x53,0x77,0x69,0x73,0x73,0x2d,0x4e,0x61,0x72,
+0x72,0x6f,0x77,0x2d,0x42,0x6f,0x6c,0x64,0x4f,0x62,0x6c,
+0x69,0x71,0x75,0x65,0x00,0x5a,0x61,0x70,0x66,0x43,0x61,
+0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63,0x2d,
+0x52,0x6f,0x6d,0x61,0x6e,0x00,0x5a,0x61,0x70,0x66,0x43,
+0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63,
+0x2d,0x42,0x6f,0x6c,0x64,0x00,0x5a,0x61,0x70,0x66,0x43,
+0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,0x69,0x63,
+0x2d,0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x5a,0x61,0x70,
+0x66,0x43,0x61,0x6c,0x6c,0x69,0x67,0x72,0x61,0x70,0x68,
+0x69,0x63,0x2d,0x42,0x6f,0x6c,0x64,0x49,0x74,0x61,0x6c,
+0x69,0x63,0x00,0x5a,0x61,0x70,0x66,0x43,0x68,0x61,0x6e,
+0x63,0x65,0x72,0x79,0x2d,0x4d,0x65,0x64,0x69,0x75,0x6d,
+0x49,0x74,0x61,0x6c,0x69,0x63,0x00,0x00,0x09,0x00,0x0A,
+0x00,0x0B,0x00,0x0E,0x00,0x14,0x00,0x17,0x00,0x18,0x00,
+0x1F,0x00,0x20,0x00,0x36,0x00,0x37,0x00,0x38,0x00,0x45,0x00,
+0x47,0x00,0x48,0x00,0x80,0x00,0x82,0x00,0x83,0x00,0x84,
+0x00,0x85,0x00,0x87,0x00,0x8B,0x00,0x8C,0x00,0x8D,0x00,0x8E,
+0x00,0x8F,0x00,0x90,0x00,0x91,0x00,0x92,0x00,0x95,0x00,0x96,
+0x00,0x97,0x00,0x98,0x00,0x99,0x00,0x9C,0x00,0x9E,0x00,
+0x9F,0x00,0xA0,0x00,0xA1,0x00,0xA2,0x00,0xA3,0x00,0xCB,0x00,
+0xCC,0x00,0xCD,0x00,0xCE,0x00,0xD1,0x00,0xD3,0x00,0xD4,
+0x00,0xD5,0x00,0xD6,0x00,0xFA,0x00,0xFB,0x00,0xFC,0x00,0xFD,
+0x00,0xCF,0x00,0x26,0x00,0x7E,0x03,0x05,0x00,0xA5,0x00,
+0xA6,0x00,0xA8,0x00,0xAA,0x00,0xAD,0x00,0xAE,0x00,0xAF,0x00,
+0xB0,0x00,0xB1,0x00,0xB2,0x00,0xB3,0x00,0xB5,0x00,0xB6,0x00,
+0xB8,0x00,0xB9,0x00,0xBA,0x00,0xBB,0x00,0xBC,0x00,0xBE,
+0x00,0xBF,0x00,0xC0,0x00,0xC1,0x00,0xC6,0x00,0xDC,0x00,0xEB,
+0x00,0xEC,0x00,0xF2,0x00,0xF3,0x00,0x15,0x00,0x16,0x00,
+0x86
+};
diff --git a/src/devices/grolbp/grolbp.1.man b/src/devices/grolbp/grolbp.1.man
new file mode 100644
index 0000000..c8cda76
--- /dev/null
+++ b/src/devices/grolbp/grolbp.1.man
@@ -0,0 +1,504 @@
+'\" t
+.TH grolbp @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grolbp \-
+.I groff
+output driver for Canon CaPSL printers
+.
+.
+.\" Modified from grolj4 man page by Francisco Andrés Verdú
+.\" <pandres@dragonet.es> for the grolbp program.
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1994-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grolbp_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grolbp
+.RB [ \-l ]
+.RB [ \-c\~\c
+.IR num-copies ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-o\~\c
+.IR orientation ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-w\~\c
+.IR width ]
+.RI [ file\~ .\|.\|.]
+.
+.SY grolbp
+[\c
+.BI \-\-copies= num-copies\c
+] [\c
+.BI \-\-fontdir= font-directory\c
+] [\c
+.B \-\-landscape\c
+] [\c
+.BI \-\-linewidth= width\c
+] [\c
+.BI \-\-orientation= orientation\c
+] [\c
+.BI \-\-papersize= paper-format\c
+]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grolbp
+.B \-h
+.
+.SY grolbp
+.B \-\-help
+.YS
+.
+.
+.SY grolbp
+.B \-v
+.
+.SY grolbp
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+This GNU
+.I roff
+output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into a CaPSL and VDM format suitable for Canon LBP-4 and LBP-8 printers.
+.
+Normally,
+.I grolbp
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~lbp \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grolbp .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grolbp
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+The driver supports the Dutch,
+Swiss,
+and Swiss-Narrow scalable typefaces,
+each in the regular,
+bold,
+italic,
+and bold-italic styles.
+.
+Additionally,
+the bitmapped,
+monospaced Courier and Elite typefaces are available in regular,
+bold,
+and
+italic styles;
+Courier at 8 and 12 points,
+Elite at 8 and 10 points.
+.
+The following chart summarizes the
+.I groff
+font names used to access them.
+.
+.
+.P
+.TS
+tab(|) allbox center;
+Cb Cb Cb Cb Cb
+L L L L L
+.
+Typeface | Roman | Bold | Italic | Bold-Italic
+Dutch | TR | TB | TI | TBI
+Swiss | HR | HB | HI | HBI
+Swiss Narrow | HNR | HNB | HNI | HNBI
+Courier | CR | CB | CI |
+Elite | ER | EB | EI |
+.TE
+.
+.
+.\" ====================================================================
+.SS "Paper format, orientation, and device description file"
+.\" ====================================================================
+.
+.I grolbp
+supports paper formats
+.RB \[lq] A4 \[rq],
+.RB \[lq] letter \[rq],
+.RB \[lq] legal \[rq],
+and
+.RB \[lq] executive \[rq].
+.
+These are matched case-insensitively.
+.
+The
+.BR \-p ,
+.B \-\-papersize
+option overrides any setting in the device description file
+.IR DESC .
+.
+If neither specifies a paper format,
+A4 is assumed.
+.
+.
+.P
+In its
+.I DESC
+file,
+.I grolbp
+(case-insensitively) recognizes an
+.B orientation
+directive accepting one mandatory argument,
+.B portrait
+or
+.BR landscape .
+.
+The first valid orientation directive encountered controls.
+.\" XXX: This is inconsistent with other description file processing.
+.
+The
+.BR \-l ,
+.BR \-o ,
+and
+.B \-\-orientation
+command-line options
+override any setting in
+.IR DESC .
+.
+If none of the foregoing specify the orientation,
+portrait is assumed.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+In addition to the font description file directives documented in
+.MR groff_font @MAN5EXT@ ,
+.I grolbp
+recognizes
+.BR lbpname ,
+which maps the
+.I groff
+font name to the font name used internally by the printer.
+.
+Its syntax is as follows.
+.RS
+.EX
+.RI lbpname\~ printer-font-name
+.EE
+.RE
+.
+.
+.BR lbpname 's
+argument is case-sensitive.
+.
+The printer's font names are encoded as follows.
+.
+.
+.P
+For bitmapped fonts,
+.I printer-font_name
+has the form
+.RS
+.EX
+.RI N\[la] base-font-name \[ra]\[la] font-style \[ra]
+.EE
+.RE
+.I base-font-name
+is the font name as it appears in the printer's font listings without
+the first letter,
+up to
+(but not including)
+the font size.
+.
+.I font-style
+can be one of the letters
+.BR R ,
+.BR I ,
+or
+.BR B ,
+.\" The bold-italic style apparently was not supported for bitmap fonts.
+indicating the roman,
+italic,
+and bold styles,
+respectively.
+.
+For instance,
+if the printer's \[lq]font listing A\[rq]
+shows \[lq]Nelite12I.ISO_USA\[rq],
+the corresponding entry in the
+.I groff
+font description file is
+.RS
+.EX
+lbpname NeliteI
+.EE
+.RE
+.
+You may need to modify
+.I grolbp
+to add support for new bitmapped fonts,
+since the available font names and font sizes of bitmapped fonts
+(as documented above)
+are hard-coded into the program.
+.
+.
+.P
+For scalable fonts,
+.I printer-font-name
+is identical to the font name as it appears in the printer's \[lq]font
+listing A\[rq].
+.
+For instance,
+to select the \[lq]Swiss\[rq] font in bold-italic style,
+which appears in the font listing
+as \%\[lq]Swiss\-BoldOblique\[rq],
+.RS
+.EX
+lbpname Swiss\-BoldOblique
+.EE
+.RE
+is the required directive,
+and this is what we find in the
+.I groff
+font description file
+.I HBI
+for the
+.B lbp
+device.
+.
+.
+.\" ====================================================================
+.SS "Drawing commands"
+.\" ====================================================================
+.
+For compatibility with
+.MR grolj4 @MAN1EXT@ ,
+an additional drawing command is available.
+.
+.
+.TP
+.BI \[rs]D\[aq]R\~ "dh dv" \[aq]
+Draw a rule
+(solid black rectangle)
+with one corner at the drawing position,
+and the diagonally opposite corner at the drawing position
+.RI +( dh , dv ).
+.\" XXX , at which the drawing position will be afterward. ?
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-c " num-copies"
+.TQ
+.BI \-\-copies= num-copies
+Produce
+.I num-copies
+copies of each page.
+.
+.
+.TP
+.BI \-F " font-directory"
+.TQ
+.BI \-\-fontdir= font-directory
+Prepend directory
+.RI font-directory /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR lbp .
+.
+.
+.TP
+.B \-l
+.TQ
+.B \-\-landscape
+Format the document in landscape orientation.
+.
+.
+.TP
+.BI \-o " orientation"
+.TQ
+.BI \-\-orientation= orientation
+Format the document in the given
+.IR orientation ,
+which must be
+.RB \%\[lq] portrait \[rq]
+or
+.RB \%\[lq] landscape \[rq].
+.
+.
+.TP
+.BI \-p " paper-format"
+.TQ
+.BI \-\-papersize= paper-format
+Set the paper format to
+.IR paper-format ,
+which must be a valid paper format as described above.
+.
+.
+.TP
+.BI \-w " width"
+.TQ
+.BI \-\-linewidth= width
+Set the default line thickness to
+.I width
+thousandths of an em;
+the default is
+.B 40
+(0.04\~em).
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devlbp/\:DESC
+describes the
+.B lbp
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devlbp/ F
+describes the font known
+.RI as\~ F
+on device
+.BR lbp .
+.
+.
+.TP
+.I @MACRODIR@/\:lbp\:.tmac
+defines macros for use with the
+.B lbp
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B lbp
+output device is selected.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grolbp_1_man_C]
+.do rr *groff_grolbp_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grolbp/grolbp.am b/src/devices/grolbp/grolbp.am
new file mode 100644
index 0000000..3ee3a0a
--- /dev/null
+++ b/src/devices/grolbp/grolbp.am
@@ -0,0 +1,36 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grolbp
+grolbp_SOURCES = \
+ src/devices/grolbp/lbp.cpp \
+ src/devices/grolbp/lbp.h \
+ src/devices/grolbp/charset.h
+
+grolbp_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grolbp/grolbp.1
+EXTRA_DIST += src/devices/grolbp/grolbp.1.man
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grolbp/lbp.cpp b/src/devices/grolbp/lbp.cpp
new file mode 100644
index 0000000..35cd54e
--- /dev/null
+++ b/src/devices/grolbp/lbp.cpp
@@ -0,0 +1,738 @@
+/* Copyright (C) 1994-2020 Free Software Foundation, Inc.
+ Written by Francisco Andrés Verdú <pandres@dragonet.es> with many
+ ideas taken from the other groff drivers.
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+TODO
+
+ - Add X command to include bitmaps
+*/
+
+#include <assert.h>
+
+#include "driver.h"
+#include "lbp.h"
+#include "charset.h"
+#include "paper.h"
+
+#include "nonposix.h"
+
+extern "C" const char *Version_string;
+
+static int user_papersize = -1;
+static int orientation = -1;
+
+// custom paper format
+static double user_paperlength = 0;
+static double user_paperwidth = 0;
+
+static int ncopies = 1;
+
+#define DEFAULT_LINEWIDTH_FACTOR 40 // 0.04em
+static int linewidth_factor = DEFAULT_LINEWIDTH_FACTOR;
+
+static int set_papersize(const char *paperformat);
+
+class lbp_font : public font {
+public:
+ ~lbp_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static lbp_font *load_lbp_font(const char *);
+ char *lbpname;
+ char is_scalable;
+private:
+ lbp_font(const char *);
+};
+
+class lbp_printer : public printer {
+public:
+ lbp_printer(int, double, double);
+ ~lbp_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *name);
+ void draw(int code, int *p, int np, const environment *env);
+ void begin_page(int);
+ void end_page(int page_length);
+ font *make_font(const char *);
+ void end_of_line();
+private:
+ void set_line_thickness(int size,const environment *env);
+ void vdmstart();
+ void vdmflush(); // the name vdmend was already used in lbp.h
+ void setfillmode(int mode);
+ void polygon( int hpos,int vpos,int np,int *p);
+ char *font_name(const lbp_font *f, const int siz);
+
+ int fill_pattern;
+ int fill_mode;
+ int cur_hpos;
+ int cur_vpos;
+ lbp_font *cur_font;
+ int cur_size;
+ unsigned short cur_symbol_set;
+ int line_thickness;
+ int req_linethickness; // requested line thickness
+ // custom paper format
+ int papersize;
+ int paperlength;
+ int paperwidth;
+};
+
+lbp_font::lbp_font(const char *nm)
+: font(nm)
+{
+}
+
+lbp_font::~lbp_font()
+{
+}
+
+lbp_font *lbp_font::load_lbp_font(const char *s)
+{
+ lbp_font *f = new lbp_font(s);
+ f->lbpname = NULL;
+ f->is_scalable = 1; // Default is that fonts are scalable
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+
+void lbp_font::handle_unknown_font_command(const char *command,
+ const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "lbpname") == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'%1' command requires an argument",
+ command);
+ this->lbpname = new char[strlen(arg) + 1];
+ strcpy(this->lbpname, arg);
+ // we recognize bitmapped fonts by the first character of its name
+ if (arg[0] == 'N')
+ this->is_scalable = 0;
+ // fprintf(stderr, "Loading font \"%s\" \n", arg);
+ }
+ // fprintf(stderr, "Loading font %s \"%s\" in %s at %d\n",
+ // command, arg, filename, lineno);
+}
+
+static void wp54charset()
+{
+ unsigned int i;
+ lbpputs("\033[714;100;29;0;32;120.}");
+ for (i = 0; i < sizeof(symset); i++)
+ lbpputc(symset[i]);
+ lbpputs("\033[100;0 D");
+ return;
+}
+
+lbp_printer::lbp_printer(int ps, double pw, double pl)
+: fill_pattern(1),
+ fill_mode(0),
+ cur_hpos(-1),
+ cur_font(0),
+ cur_size(0),
+ cur_symbol_set(0),
+ req_linethickness(-1)
+{
+ SET_BINARY(fileno(stdout));
+ lbpinit(stdout);
+ lbpputs("\033c\033;\033[2&z\033[7 I\033[?32h\033[?33h\033[11h");
+ wp54charset(); // Define the new symbol set
+ lbpputs("\033[7 I\033[?32h\033[?33h\033[11h");
+ // Paper format handling
+ if (orientation < 0)
+ orientation = 0; // Default orientation is portrait
+ papersize = 14; // Default paper format is A4
+ if (font::papersize) {
+ papersize = set_papersize(font::papersize);
+ paperlength = font::paperlength;
+ paperwidth = font::paperwidth;
+ }
+ if (ps >= 0) {
+ papersize = ps;
+ paperlength = int(pl * font::res + 0.5);
+ paperwidth = int(pw * font::res + 0.5);
+ }
+ if (papersize < 80) // standard paper
+ lbpprintf("\033[%dp", (papersize | orientation));
+ else // Custom paper
+ lbpprintf("\033[%d;%d;%dp", (papersize | orientation),
+ paperlength, paperwidth);
+ // Number of copies
+ lbpprintf("\033[%dv\n", ncopies);
+ lbpputs("\033[0u\033[1u\033P1y Grolbp\033\\");
+ lbpmoveabs(0, 0);
+ lbpputs("\033[0t\033[2t");
+ lbpputs("\033('$2\033)' 1"); // Primary symbol set IBML
+ // Secondary symbol set IBMR1
+ cur_symbol_set = 0;
+}
+
+lbp_printer::~lbp_printer()
+{
+ lbpputs("\033P1y\033\\");
+ lbpputs("\033c\033<");
+}
+
+inline void lbp_printer::set_line_thickness(int size,const environment *env)
+{
+ if (size == 0)
+ line_thickness = 1;
+ else {
+ if (size < 0)
+ // line_thickness =
+ // (env->size * (font::res/72)) * (linewidth_factor/1000)
+ // we ought to check for overflow
+ line_thickness =
+ env->size * linewidth_factor * font::res / 72000;
+ else // size > 0
+ line_thickness = size;
+ } // else from if (size == 0)
+ if (line_thickness < 1)
+ line_thickness = 1;
+ if (vdminited())
+ vdmlinewidth(line_thickness);
+ req_linethickness = size; // an size requested
+ /* fprintf(stderr, "thickness: %d == %d, size %d, %d \n",
+ size, line_thickness, env->size,req_linethickness); */
+ return;
+} // lbp_printer::set_line_thickness
+
+void lbp_printer::begin_page(int)
+{
+}
+
+void lbp_printer::end_page(int)
+{
+ if (vdminited())
+ vdmflush();
+ lbpputc('\f');
+ cur_hpos = -1;
+}
+
+void lbp_printer::end_of_line()
+{
+ cur_hpos = -1; // force absolute motion
+}
+
+char *lbp_printer::font_name(const lbp_font *f, const int siz)
+{
+ static char bfont_name[255]; // The resulting font name
+ char type, // Italic, Roman, Bold
+ ori, // Normal or Rotated
+ *nam; // The font name without other data.
+ int cpi; // The font size in characters per inch
+ // (bitmapped fonts are monospaced).
+ /* Bitmap font selection is ugly in this printer, so don't expect
+ this function to be elegant. */
+ bfont_name[0] = 0x00;
+ if (orientation) // Landscape
+ ori = 'R';
+ else // Portrait
+ ori = 'N';
+ type = f->lbpname[strlen(f->lbpname) - 1];
+ nam = new char[strlen(f->lbpname) - 2];
+ strncpy(nam, &(f->lbpname[1]), strlen(f->lbpname) - 2);
+ nam[strlen(f->lbpname) - 2] = 0x00;
+ // fprintf(stderr, "Bitmap font '%s' %d %c %c \n", nam, siz, type, ori);
+ /* Since these fonts are available only at certain sizes,
+ 10 and 17 cpi for courier, 12 and 17 cpi for elite,
+ we adjust the resulting size. */
+ cpi = 17;
+ // Fortunately there are only two bitmapped fonts shipped with the printer.
+ if (!strcasecmp(nam, "courier")) {
+ // Courier font
+ if (siz >= 12)
+ cpi = 10;
+ else cpi = 17;
+ }
+ if (!strcasecmp(nam, "elite")) {
+ if (siz >= 10)
+ cpi = 12;
+ else cpi = 17;
+ }
+ // Now that we have all the data, let's generate the font name.
+ if ((type != 'B') && (type != 'I')) // Roman font
+ sprintf(bfont_name, "%c%s%d", ori, nam, cpi);
+ else
+ sprintf(bfont_name, "%c%s%d%c", ori, nam, cpi, type);
+ return bfont_name;
+}
+
+void lbp_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ int code = f->get_code(g);
+ unsigned char ch = code & 0xff;
+ unsigned short symbol_set = code >> 8;
+ if (f != cur_font) {
+ lbp_font *psf = (lbp_font *)f;
+ // fprintf(stderr, "Loading font %s \"%d\" \n", psf->lbpname, env->size);
+ if (psf->is_scalable) {
+ // Scalable font selection is different from bitmaped
+ lbpprintf("\033Pz%s.IBML\033\\\033[%d C", psf->lbpname,
+ (int)((env->size * font::res) / 72));
+ }
+ else
+ // bitmapped font
+ lbpprintf("\033Pz%s.IBML\033\\\n", font_name(psf, env->size));
+ lbpputs("\033)' 1"); // Select IBML and IBMR1 symbol set
+ cur_font = psf;
+ cur_symbol_set = 0;
+ // Update the line thickness if needed
+ if ((req_linethickness < 0 ) && (env->size != cur_size))
+ set_line_thickness(req_linethickness,env);
+ cur_size = env->size;
+ }
+ if (symbol_set != cur_symbol_set) {
+ if (cur_symbol_set == 3)
+ // if current symbol set is Symbol we must restore the font
+ lbpprintf("\033Pz%s.IBML\033\\\033[%d C", cur_font->lbpname,
+ (int)((env->size * font::res) / 72));
+ switch (symbol_set) {
+ case 0:
+ lbpputs("\033('$2\033)' 1"); // Select IBML and IBMR1 symbol sets
+ break;
+ case 1:
+ lbpputs("\033(d\033)' 1"); // Select wp54 symbol set
+ break;
+ case 2:
+ lbpputs("\033('$2\033)'!0"); // Select IBMP symbol set
+ break;
+ case 3:
+ lbpprintf("\033PzSymbol.SYML\033\\\033[%d C",
+ (int)((env->size * font::res) / 72));
+ lbpputs("\033(\"!!0\033)\"!!1"); // Select symbol font
+ break;
+ case 4:
+ lbpputs("\033)\"! 1\033(\"!$2"); // Select PS symbol set
+ break;
+ }
+ cur_symbol_set = symbol_set;
+ }
+ if (env->size != cur_size) {
+ if (!cur_font->is_scalable)
+ lbpprintf("\033Pz%s.IBML\033\\\n", font_name(cur_font, env->size));
+ else
+ lbpprintf("\033[%d C", (int)((env->size * font::res) / 72));
+ cur_size = env->size;
+ // Update the line thickness if needed
+ if (req_linethickness < 0 )
+ set_line_thickness(req_linethickness,env);
+ }
+ if ((env->hpos != cur_hpos) || (env->vpos != cur_vpos)) {
+ // lbpmoveabs(env->hpos - ((5 * 300) / 16), env->vpos);
+ lbpmoveabs(env->hpos - 64, env->vpos - 64);
+ cur_vpos = env->vpos;
+ cur_hpos = env->hpos;
+ }
+ if ((ch & 0x7F) < 32)
+ lbpputs("\033[1.v");
+ lbpputc(ch);
+ cur_hpos += w;
+}
+
+void lbp_printer::vdmstart()
+{
+ FILE *f;
+ static int changed_origin = 0;
+ errno = 0;
+ f = tmpfile();
+ // f = fopen("/tmp/gtmp","w+");
+ if (f == NULL)
+ perror("Opening temporary file");
+ vdminit(f);
+ if (!changed_origin) { // we should change the origin only one time
+ changed_origin = 1;
+ vdmorigin(-63, 0);
+ }
+ vdmlinewidth(line_thickness);
+}
+
+void
+lbp_printer::vdmflush()
+{
+ char buffer[1024];
+ int bytes_read = 1;
+ vdmend();
+ fflush(lbpoutput);
+ /* let's copy the vdm code to the output */
+ rewind(vdmoutput);
+ do {
+ bytes_read = fread(buffer, 1, sizeof(buffer), vdmoutput);
+ bytes_read = fwrite(buffer, 1, bytes_read, lbpoutput);
+ } while (bytes_read == sizeof(buffer));
+ fclose(vdmoutput); // This will also delete the file,
+ // since it is created by tmpfile()
+ vdmoutput = NULL;
+}
+
+inline void lbp_printer::setfillmode(int mode)
+{
+ if (mode != fill_mode) {
+ if (mode != 1)
+ vdmsetfillmode(mode, 1, 0);
+ else
+ // To get black, we must use white inverted.
+ vdmsetfillmode(mode, 1, 1);
+
+ fill_mode = mode;
+ }
+}
+
+inline void lbp_printer::polygon(int hpos, int vpos, int np, int *p)
+{
+ int *points, i;
+ points = new int[np + 2];
+ points[0] = hpos;
+ points[1] = vpos;
+ // fprintf(stderr, "Polygon (%d,%d) ", points[0], points[1]);
+ for (i = 0; i < np; i++)
+ points[i + 2] = p[i];
+ // for (i = 0; i < np; i++) fprintf(stderr, " %d ", p[i]);
+ // fprintf(stderr, "\n");
+ vdmpolygon((np /2) + 1, points);
+}
+
+void lbp_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if ((req_linethickness < 0 ) && (env->size != cur_size))
+ set_line_thickness(req_linethickness,env);
+
+ switch (code) {
+ case 't':
+ if (np == 0)
+ line_thickness = 1;
+ else { // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ set_line_thickness(p[0],env);
+ }
+ break;
+ case 'l': // Line
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ if (!vdminited())
+ vdmstart();
+ vdmline(env->hpos, env->vpos, p[0], p[1]);
+/* fprintf(stderr, "\nline: %d,%d - %d,%d thickness %d == %d\n",
+ env->hpos - 64,env->vpos -64, env->hpos - 64 + p[0],
+ env->vpos -64 + p[1], env->size, line_thickness);*/
+ break;
+ case 'R': // Rule
+ if (np != 2) {
+ error("2 arguments required for Rule");
+ break;
+ }
+ if (vdminited()) {
+ setfillmode(fill_pattern); // Solid Rule
+ vdmrectangle(env->hpos, env->vpos, p[0], p[1]);
+ }
+ else {
+ lbpruleabs(env->hpos - 64, env->vpos -64, p[0], p[1]);
+ cur_vpos = p[1];
+ cur_hpos = p[0];
+ }
+ // fprintf(stderr, "\nrule: thickness %d == %d\n",
+ // env->size, line_thickness);
+ break;
+ case 'P': // Filled Polygon
+ if (!vdminited())
+ vdmstart();
+ setfillmode(fill_pattern);
+ polygon(env->hpos, env->vpos, np, p);
+ break;
+ case 'p': // Empty Polygon
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ polygon(env->hpos, env->vpos, np, p);
+ break;
+ case 'C': // Filled Circle
+ if (!vdminited())
+ vdmstart();
+ // fprintf(stderr, "Circle (%d,%d) Fill %d\n",
+ // env->hpos, env->vpos, fill_pattern);
+ setfillmode(fill_pattern);
+ vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2);
+ break;
+ case 'c': // Empty Circle
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ vdmcircle(env->hpos + (p[0]/2), env->vpos, p[0]/2);
+ break;
+ case 'E': // Filled Ellipse
+ if (!vdminited())
+ vdmstart();
+ setfillmode(fill_pattern);
+ vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0);
+ break;
+ case 'e': // Empty Ellipse
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ vdmellipse(env->hpos + (p[0]/2), env->vpos, p[0]/2, p[1]/2, 0);
+ break;
+ case 'a': // Arc
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ // VDM draws arcs clockwise and pic counterclockwise
+ // We must compensate for that, exchanging the starting and
+ // ending points
+ vdmvarc(env->hpos + p[0], env->vpos+p[1],
+ int(sqrt(double((p[0]*p[0]) + (p[1]*p[1])))),
+ p[2], p[3],
+ (-p[0]), (-p[1]), 1, 2);
+ break;
+ case '~': // Spline
+ if (!vdminited())
+ vdmstart();
+ setfillmode(0);
+ vdmspline(np/2, env->hpos, env->vpos, p);
+ break;
+ case 'f':
+ if (np != 1 && np != 2) {
+ error("1 argument required for fill");
+ break;
+ }
+ // fprintf(stderr, "Fill %d\n", p[0]);
+ if ((p[0] == 1) || (p[0] >= 1000)) { // Black
+ fill_pattern = 1;
+ break;
+ }
+ if (p[0] == 0) { // White
+ fill_pattern = 0;
+ break;
+ }
+ if ((p[0] > 1) && (p[0] < 1000))
+ {
+ if (p[0] >= 990) fill_pattern = -23;
+ else if (p[0] >= 700) fill_pattern = -28;
+ else if (p[0] >= 500) fill_pattern = -27;
+ else if (p[0] >= 400) fill_pattern = -26;
+ else if (p[0] >= 300) fill_pattern = -25;
+ else if (p[0] >= 200) fill_pattern = -22;
+ else if (p[0] >= 100) fill_pattern = -24;
+ else fill_pattern = -21;
+ }
+ break;
+ case 'F':
+ // not implemented yet
+ break;
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+ return;
+}
+
+font *lbp_printer::make_font(const char *nm)
+{
+ return lbp_font::load_lbp_font(nm);
+}
+
+printer *make_printer()
+{
+ return new lbp_printer(user_papersize, user_paperwidth, user_paperlength);
+}
+
+static struct {
+ const char *name;
+ int code;
+} lbp_papersizes[] =
+ {{ "A4", 14 },
+ { "letter", 30 },
+ { "legal", 32 },
+ { "executive", 40 },
+ };
+
+static int set_papersize(const char *paperformat)
+{
+ unsigned int i;
+ // First, test for a standard (i.e. supported directly by the printer)
+ // paper format.
+ for (i = 0 ; i < sizeof(lbp_papersizes) / sizeof(lbp_papersizes[0]); i++)
+ {
+ if (strcasecmp(lbp_papersizes[i].name,paperformat) == 0)
+ return lbp_papersizes[i].code;
+ }
+ // Otherwise, we assume a custom paper format.
+ return 82; // XXX: magic number
+}
+
+static void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ // orientation command
+ if (strcasecmp(command, "orientation") == 0) {
+ // We give priority to command-line options
+ if (orientation > 0)
+ return;
+ if (arg == 0)
+ error_with_file_and_line(filename, lineno,
+ "'orientation' command requires an argument");
+ else {
+ if (strcasecmp(arg, "portrait") == 0)
+ orientation = 0;
+ else {
+ if (strcasecmp(arg, "landscape") == 0)
+ orientation = 1;
+ else
+ error_with_file_and_line(filename, lineno,
+ "invalid argument to 'orientation' command");
+ }
+ }
+ }
+}
+
+static struct option long_options[] = {
+ { "orientation", required_argument, NULL, 'o' },
+ { "version", no_argument, NULL, 'v' },
+ { "copies", required_argument, NULL, 'c' },
+ { "landscape", no_argument, NULL, 'l' },
+ { "papersize", required_argument, NULL, 'p' },
+ { "linewidth", required_argument, NULL, 'w' },
+ { "fontdir", required_argument, NULL, 'F' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, 0, 0 }
+};
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-l] [-c num-copies] [-F font-directory] [-o orientation]"
+" [-p paper-format] [-w width] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"Translate the output of troff(1) into a CaPSL and VDM format suitable"
+"\n"
+"for Canon LBP-4 and LBP-8 printers. See the grolbp(1) manual page.\n",
+ stream);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ if (program_name == NULL)
+ program_name = strsave(argv[0]);
+ font::set_unknown_desc_command_handler(handle_unknown_desc_command);
+ // command line parsing
+ int c;
+ int option_index = 0;
+ while ((c = getopt_long(argc, argv, "c:F:hI:lo:p:vw:", long_options,
+ &option_index))
+ != EOF) {
+ switch (c) {
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'I':
+ // ignore include path arguments
+ break;
+ case 'p':
+ {
+ const char *s;
+ if (!font::scan_papersize(optarg, &s,
+ &user_paperlength, &user_paperwidth))
+ error("ignoring invalid paper format '%1'", optarg);
+ else
+ user_papersize = set_papersize(s);
+ break;
+ }
+ case 'l':
+ orientation = 1;
+ break;
+ case 'v':
+ printf("GNU grolbp (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'o':
+ if (strcasecmp(optarg, "portrait") == 0)
+ orientation = 0;
+ else {
+ if (strcasecmp(optarg, "landscape") == 0)
+ orientation = 1;
+ else
+ error("unknown orientation '%1'", optarg);
+ }
+ break;
+ case 'c':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if ((n <= 0) && (ptr == optarg))
+ error("argument for -c must be a positive integer");
+ else if (n <= 0 || n > 32767)
+ error("out of range argument for -c");
+ else
+ ncopies = unsigned(n);
+ break;
+ }
+ case 'w':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg)
+ error("argument for -w must be a non-negative integer");
+ else if (n < 0 || n > INT_MAX)
+ error("out of range argument for -w");
+ else
+ linewidth_factor = int(n);
+ break;
+ }
+ case 'h':
+ usage(stdout);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ }
+ if (optind >= argc)
+ do_file("-");
+ while (optind < argc)
+ do_file(argv[optind++]);
+ if (lbpoutput)
+ lbpputs("\033c\033<");
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grolbp/lbp.h b/src/devices/grolbp/lbp.h
new file mode 100644
index 0000000..ee1c7b9
--- /dev/null
+++ b/src/devices/grolbp/lbp.h
@@ -0,0 +1,544 @@
+// -*- C -*-
+/* Copyright (C) 1994-2020 Free Software Foundation, Inc.
+ Written by Francisco Andrés Verdú <pandres@dragonet.es>
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This file contains a set of utility functions to use canon CaPSL printers
+ * (lbp-4 and lbp-8 series printers) */
+
+#ifndef LBP_H
+#define LBP_H
+
+#include <stdio.h>
+#include <stdarg.h>
+
+static FILE *lbpoutput = NULL;
+static FILE *vdmoutput = NULL;
+
+
+static inline void
+lbpinit(FILE *outfile)
+{
+ lbpoutput = outfile;
+}
+
+
+static void
+lbpprintf(const char *format, ... )
+{ /* Taken from cjet */
+ va_list stuff;
+
+ va_start(stuff, format);
+ vfprintf(lbpoutput, format, stuff);
+ va_end(stuff);
+}
+
+
+static inline void
+lbpputs(const char *data)
+{
+ fputs(data,lbpoutput);
+}
+
+
+static inline void
+lbpputc(unsigned char c)
+{
+ fputc(c,lbpoutput);
+}
+
+
+static inline void
+lbpsavestatus(int idx )
+{
+ fprintf(lbpoutput,"\033[%d%%y",idx);
+}
+
+
+static inline void
+lbprestorestatus(int idx )
+{
+ fprintf(lbpoutput,"\033[%d%cz",idx ,'%');
+}
+
+
+static inline void
+lbpsavepos(int idx)
+{
+ fprintf(lbpoutput,"\033[1;%d;0x",idx);
+}
+
+
+static inline void
+lbprestorepos(int idx)
+{
+ fprintf(lbpoutput,"\033[0;%d;0x",idx);
+}
+
+
+static inline void
+lbprestoreposx(int idx)
+{
+ fprintf(lbpoutput,"\033[0;%d;1x",idx);
+}
+
+
+static inline void
+lbpmoverel(int despl, char direction)
+{
+ fprintf(lbpoutput,"\033[%d%c",despl,direction);
+}
+
+
+static inline void
+lbplinerel(int width,int despl,char direction )
+{
+ fprintf(lbpoutput,"\033[%d;0;9{\033[%d%c\033[9}",width,despl,direction);
+}
+
+
+static inline void
+lbpmoveabs(int x, int y)
+{
+ fprintf(lbpoutput,"\033[%d;%df",y,x);
+}
+
+
+static inline void
+lbplineto(int x,int y, int width )
+{
+ fprintf(lbpoutput,"\033[%d;0;9{",width);
+ lbpmoveabs(x,y);
+ fprintf(lbpoutput,"\033[9}\n");
+}
+
+
+static inline void
+lbpruleabs(int x, int y, int hsize, int vsize)
+{
+ lbpmoveabs(x,y);
+ fprintf(lbpoutput,"\033[0;9;000s");
+ lbpmoveabs(x+hsize,y+vsize);
+ fprintf(lbpoutput,"\033[9r");
+}
+
+
+static void vdmprintf(const char *format, ... );
+
+
+static inline char *
+vdmnum(int num,char *result)
+{
+ char b1,b2,b3;
+ char *p = result;
+ int nm;
+
+ nm = abs(num);
+ /* First byte 1024 - 32768 */
+ b1 = ((nm >> 10) & 0x3F);
+ if (b1) *p++ = b1 | 0x40;
+
+ /* Second Byte 16 - 1024 */
+ b2 = ((nm >> 4) & 0x3F);
+ if ( b1 || b2) *p++= b2 | 0x40;
+
+ /* Third byte 0 - 15 */
+ b3 = ((nm & 0x0F) | 32);
+ if (num >= 0) b3 |= 16;
+ *p++ = b3;
+ *p = 0x00; /* End of the resulting string */
+ return result;
+}
+
+
+static inline void
+vdmorigin(int newx, int newy)
+{
+ char nx[4],ny[4];
+
+ vdmprintf("}\"%s%s\x1e",vdmnum(newx,nx),vdmnum(newy,ny));
+}
+
+
+static inline FILE *
+vdminit(FILE *vdmfile)
+{
+ char scale[4],size[4],lineend[4];
+
+/* vdmoutput = tmpfile();*/
+ vdmoutput = vdmfile;
+ /* Initialize the VDM mode */
+ vdmprintf("\033[0&}#GROLBP\x1e!0%s%s\x1e$\x1e}F%s\x1e",\
+ vdmnum(-3,scale),vdmnum(1,size),vdmnum(1,lineend));
+ return vdmoutput;
+
+}
+
+
+static inline void
+vdmend()
+{
+ vdmprintf("}p\x1e");
+}
+
+
+static void
+vdmprintf(const char *format, ... )
+{ /* Taken from cjet */
+ va_list stuff;
+
+ if (vdmoutput == NULL) vdminit(tmpfile());
+ va_start(stuff, format);
+ vfprintf(vdmoutput, format, stuff);
+ va_end(stuff);
+}
+
+
+static inline void
+vdmsetfillmode(int pattern,int perimeter, int inverted)
+{
+ char patt[4],perim[4],
+ rot[4], /* rotation */
+ espejo[4], /* espejo */
+ inv[4]; /* Inverted */
+
+ vdmprintf("I%s%s%s%s%s\x1e",vdmnum(pattern,patt),\
+ vdmnum(perimeter,perim),vdmnum(0,rot),
+ vdmnum(0,espejo),vdmnum(inverted,inv));
+}
+
+
+static inline void
+vdmcircle(int centerx, int centery, int radius)
+{
+ char x[4],y[4],rad[4];
+
+ vdmprintf("5%s%s%s\x1e",vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radius,rad));
+}
+
+
+static inline void
+vdmaarc(int centerx, int centery, int radius,int startangle,int angle,int style,int arcopen)
+{
+ char x[4],y[4],rad[4],stx[4],sty[4],styl[4],op[4];
+
+ vdmprintf("}6%s%s%s%s%s%s%s\x1e",vdmnum(arcopen,op),\
+ vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radius,rad),vdmnum(startangle,stx),vdmnum(angle,sty),\
+ vdmnum(style,styl));
+}
+
+
+static inline void
+vdmvarc(int centerx, int centery,int radius, int startx, int starty, int endx, int endy,\
+ int style,int arcopen)
+{
+ char x[4],y[4],rad[4],stx[4],sty[4],enx[4],eny[4],styl[4],op[4];
+
+ vdmprintf("}6%s%s%s%s%s%s%s%s%s\x1e",vdmnum(arcopen,op),\
+ vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radius,rad),vdmnum(startx,stx),vdmnum(starty,sty),\
+ vdmnum(endx,enx),vdmnum(endy,eny),vdmnum(style,styl));
+}
+
+
+static inline void
+vdmellipse(int centerx, int centery, int radiusx, int radiusy,int rotation)
+{
+ char x[4],y[4],radx[4],rady[4],rotat[4];
+
+ vdmprintf("}7%s%s%s%s%s\x1e\n",vdmnum(centerx,x),vdmnum(centery,y),\
+ vdmnum(radiusx,radx),vdmnum(radiusy,rady),\
+ vdmnum(rotation,rotat));
+}
+
+
+static inline void
+vdmsetlinetype(int lintype)
+{
+ char ltyp[4], expfact[4];
+
+ vdmprintf("E1%s%s\x1e",vdmnum(lintype,ltyp),vdmnum(1,expfact));
+
+}
+
+
+static inline void
+vdmsetlinestyle(int lintype, int pattern,int unionstyle)
+{
+ char patt[4],ltip[4],
+ rot[4], /* rotation */
+ espejo[4], /* espejo */
+ in[4]; /* Inverted */
+
+ vdmprintf("}G%s%s%s%s%s\x1e",vdmnum(lintype,ltip),\
+ vdmnum(pattern,patt),vdmnum(0,rot),
+ vdmnum(0,espejo),vdmnum(0,in));
+ vdmprintf("}F%s",vdmnum(unionstyle,rot));
+}
+
+
+static inline void
+vdmlinewidth(int width)
+{
+ char wh[4];
+
+ vdmprintf("F1%s\x1e",vdmnum(width,wh));
+}
+
+
+static inline void
+vdmrectangle(int origx, int origy,int dstx, int dsty)
+{
+ char xcoord[4],ycoord[4],sdstx[4],sdsty[4];
+
+ vdmprintf("}:%s%s%s%s\x1e\n",vdmnum(origx,xcoord),vdmnum(dstx,sdstx),\
+ vdmnum(origy,ycoord),vdmnum(dsty,sdsty));
+}
+
+
+static inline void
+vdmpolyline(int numpoints, int *points)
+{
+ int i,*p = points;
+ char xcoord[4],ycoord[4];
+
+ if (numpoints < 2) return;
+ vdmprintf("1%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ for (i = 1; i < numpoints ; i++) {
+ vdmprintf("%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ } /* for */
+ vdmprintf("\x1e\n");
+}
+
+
+static inline void
+vdmpolygon(int numpoints, int *points)
+{
+ int i,*p = points;
+ char xcoord[4],ycoord[4];
+
+ if (numpoints < 2) return;
+ vdmprintf("2%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ for (i = 1; i < numpoints ; i++) {
+ vdmprintf("%s%s",vdmnum(*p,xcoord),vdmnum(*(p+1),ycoord));
+ p += 2;
+ } /* for */
+ vdmprintf("\x1e\n");
+
+}
+
+
+/************************************************************************
+ * Higher level auxiliary functions *
+ ************************************************************************/
+static inline int
+vdminited()
+{
+ return (vdmoutput != NULL);
+}
+
+
+static inline void
+vdmline(int startx, int starty, int sizex, int sizey)
+{
+ int points[4];
+
+ points[0] = startx;
+ points[1] = starty;
+ points[2] = sizex;
+ points[3] = sizey;
+
+ vdmpolyline(2,points);
+
+}
+
+
+/*#define THRESHOLD .05 */ /* inch */
+#define THRESHOLD 1 /* points (1/300 inch) */
+static inline void
+splinerel(double px,double py,int flush)
+{
+ static int lx = 0 ,ly = 0;
+ static double pend = 0.0;
+ static int dy = 0, despx = 0, despy = 0, sigpend = 0;
+ int dxnew = 0, dynew = 0, sg;
+ char xcoord[4],ycoord[4];
+ double npend ;
+
+ if (flush == -1) {lx = (int)px; ly = (int)py; return;}
+
+ if (flush == 0) {
+ dxnew = (int)px -lx;
+ dynew = (int)py -ly;
+ if ((dxnew == 0) && (dynew == 0)) return;
+ sg = (dxnew < 0)? -1 : 0;
+/* fprintf(stderr,"s (%d,%d) (%d,%d)\n",dxnew,dynew,despx,despy);*/
+ if (dynew == 0) {
+ despx = dxnew;
+ if ((sg == sigpend) && (dy == 0)){
+ return;
+ }
+ dy = 0;
+ }
+ else {
+ dy = 1;
+ npend = (1.0*dxnew)/dynew;
+ if (( npend == pend) && (sigpend == sg))
+ { despy = dynew; despx = dxnew; return; }
+ else
+ { sigpend = sg;
+ pend = npend;
+ } /* else (( npend == pend) && ... */
+ } /* else (if (dynew == 0)) */
+ } /* if (!flush ) */
+
+ /* if we've changed direction we must draw the line */
+/* fprintf(stderr," (%d) %.2f,%.2f\n",flush,(float)px,(float)py);*/
+ if ((despx != 0) || (despy != 0)) vdmprintf("%s%s",vdmnum(despx,xcoord),\
+ vdmnum(despy,ycoord));
+ /*if ((despx != 0) || (despy != 0)) fprintf(stderr,"2
+ *%d,%d\n",despx,despy);*/
+ if (flush) {
+ dxnew = dy = despx = despy = 0;
+ return;
+ } /* if (flush) */
+ dxnew -= despx;
+ dynew -= despy;
+ if ((dxnew != 0) || (dynew != 0)) vdmprintf("%s%s",vdmnum(dxnew,xcoord),\
+ vdmnum(dynew,ycoord));
+
+/* if ((dxnew != 0) || (dynew != 0)) fprintf(stderr,"3
+ * %d,%d\n",dxnew,dynew);*/
+ lx = (int)px; ly = (int)py;
+ dxnew = dy = despx = despy = 0;
+
+}
+
+
+/**********************************************************************
+ * The following code to draw splines is adapted from the transfig package
+ */
+static void
+quadratic_spline(double a_1, double b_1, double a_2, double b_2, \
+ double a_3, double b_3, double a_4, double b_4)
+{
+ double x_1, y_1, x_4, y_4;
+ double x_mid, y_mid;
+
+ x_1 = a_1; y_1 = b_1;
+ x_4 = a_4; y_4 = b_4;
+ x_mid = (a_2 + a_3)/2.0;
+ y_mid = (b_2 + b_3)/2.0;
+ if ((fabs(x_1 - x_mid) < THRESHOLD)
+ && (fabs(y_1 - y_mid) < THRESHOLD)) {
+ splinerel(x_mid, y_mid, 0);
+/* fprintf(tfp, "PA%.4f,%.4f;\n", x_mid, y_mid);*/
+ }
+ else {
+ quadratic_spline(x_1, y_1, ((x_1+a_2)/2.0), ((y_1+b_2)/2.0),
+ ((3.0*a_2+a_3)/4.0), ((3.0*b_2+b_3)/4.0), x_mid, y_mid);
+ }
+
+ if ((fabs(x_mid - x_4) < THRESHOLD)
+ && (fabs(y_mid - y_4) < THRESHOLD)) {
+ splinerel(x_4, y_4, 0);
+/* fprintf(tfp, "PA%.4f,%.4f;\n", x_4, y_4);*/
+ }
+ else {
+ quadratic_spline(x_mid, y_mid,
+ ((a_2+3.0*a_3)/4.0), ((b_2+3.0*b_3)/4.0),
+ ((a_3+x_4)/2.0), ((b_3+y_4)/2.0), x_4, y_4);
+ }
+}
+
+
+#define XCOORD(i) numbers[(2*i)]
+#define YCOORD(i) numbers[(2*i)+1]
+static void
+vdmspline(int numpoints, int o_x, int o_y, int *numbers)
+{
+ double cx_1, cy_1, cx_2, cy_2, cx_3, cy_3, cx_4, cy_4;
+ double x_1, y_1, x_2, y_2;
+ char xcoord[4],ycoord[4];
+ int i;
+
+ /*p = s->points;
+ x_1 = p->x/ppi;*/
+ x_1 = o_x;
+ y_1 = o_y;
+/* p = p->next;
+ x_2 = p->x/ppi;
+ y_2 = p->y/ppi;*/
+ x_2 = o_x + XCOORD(0);
+ y_2 = o_y + YCOORD(0);
+ cx_1 = (x_1 + x_2)/2.0;
+ cy_1 = (y_1 + y_2)/2.0;
+ cx_2 = (x_1 + 3.0*x_2)/4.0;
+ cy_2 = (y_1 + 3.0*y_2)/4.0;
+
+/* fprintf(stderr,"Spline %d (%d,%d)\n",numpoints,(int)x_1,(int)y_1);*/
+ vdmprintf("1%s%s",vdmnum((int)x_1,xcoord),vdmnum((int)y_1,ycoord));
+ splinerel(x_1,y_1,-1);
+ splinerel(cx_1,cy_1,0);
+/* fprintf(tfp, "PA%.4f,%.4f;PD%.4f,%.4f;\n",
+ x_1, y_1, cx_1, cy_1);*/
+
+ /*for (p = p->next; p != NULL; p = p->next) {*/
+ for (i = 1; i < (numpoints); i++) {
+ x_1 = x_2;
+ y_1 = y_2;
+/* x_2 = p->x/ppi;
+ y_2 = p->y/ppi;*/
+ x_2 = x_1 + XCOORD(i);
+ y_2 = y_1 + YCOORD(i);
+ cx_3 = (3.0*x_1 + x_2)/4.0;
+ cy_3 = (3.0*y_1 + y_2)/4.0;
+ cx_4 = (x_1 + x_2)/2.0;
+ cy_4 = (y_1 + y_2)/2.0;
+ /* fprintf(stderr,"Point (%d,%d) - (%d,%d)\n",(int)x_1,(int)(y_1),(int)x_2,(int)y_2);*/
+ quadratic_spline(cx_1, cy_1, cx_2, cy_2, cx_3, cy_3, cx_4, cy_4);
+ cx_1 = cx_4;
+ cy_1 = cy_4;
+ cx_2 = (x_1 + 3.0*x_2)/4.0;
+ cy_2 = (y_1 + 3.0*y_2)/4.0;
+ }
+ x_1 = x_2;
+ y_1 = y_2;
+/* p = s->points->next;
+ x_2 = p->x/ppi;
+ y_2 = p->y/ppi;*/
+ x_2 = o_x + XCOORD(0);
+ y_2 = o_y + YCOORD(0);
+ cx_3 = (3.0*x_1 + x_2)/4.0;
+ cy_3 = (3.0*y_1 + y_2)/4.0;
+ cx_4 = (x_1 + x_2)/2.0;
+ cy_4 = (y_1 + y_2)/2.0;
+ splinerel(x_1, y_1, 0);
+ splinerel(x_1, y_1, 1);
+ /*vdmprintf("%s%s",vdmnum((int)(x_1-lx),xcoord),\
+ vdmnum((int)(y_1-ly),ycoord));*/
+ vdmprintf("\x1e\n");
+/* fprintf(tfp, "PA%.4f,%.4f;PU;\n", x_1, y_1);*/
+
+
+}
+
+
+#endif
diff --git a/src/devices/grolj4/grolj4.1.man b/src/devices/grolj4/grolj4.1.man
new file mode 100644
index 0000000..6227779
--- /dev/null
+++ b/src/devices/grolj4/grolj4.1.man
@@ -0,0 +1,896 @@
+.TH grolj4 @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grolj4 \-
+.I groff
+output driver for HP LaserJet 4 and compatible printers
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1994-2020, 2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grolj4_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'lj4' .ft \\$1
+..
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grolj4
+.RB [ \-l ]
+.RB [ \-c\~\c
+.IR num-copies ]
+.RB [ \-d
+.RI [ n ]]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-w\~\c
+.IR line-width ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grolj4
+.B \-\-help
+.YS
+.
+.
+.SY grolj4
+.B \-v
+.
+.SY grolj4
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+This GNU
+.I roff
+output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into a PCL5 format suitable for an HP LaserJet 4 printer.
+.
+Normally,
+.I grolj4
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~lj4 \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grolj4 .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grolj4
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I grolj4
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Fonts are grouped into families
+.BR A ,
+.BR C ,
+.BR G ,
+.BR O ,
+.BR T ,
+.BR TN ,
+.BR U ,
+and
+.B UC
+having members in each style.
+.
+.
+.RS
+.TP 14n
+.B AB
+.FT AB
+Arial Bold
+.FT
+.
+.TQ
+.B ABI
+.FT ABI
+Arial Bold Italic
+.FT
+.
+.TQ
+.B AI
+.FT AI
+Arial Italic
+.FT
+.
+.TQ
+.B AR
+.FT AR
+Arial Roman
+.FT
+.
+.TQ
+.B CB
+.FT CB
+Courier Bold
+.FT
+.
+.TQ
+.B CBI
+.FT CBI
+Courier Bold Italic
+.FT
+.
+.TQ
+.B CI
+.FT CI
+Courier Italic
+.FT
+.
+.TQ
+.B CR
+.FT CR
+Courier Roman
+.FT
+.
+.TQ
+.B GB
+.FT GB
+Garamond Halbfett
+.FT
+.
+.TQ
+.B GBI
+.FT GBI
+Garamond Kursiv Halbfett
+.FT
+.
+.TQ
+.B GI
+.FT GI
+Garamond Kursiv
+.FT
+.
+.TQ
+.B GR
+.FT GR
+Garamond Antiqua
+.FT
+.
+.TQ
+.B OB
+.FT OB
+CG Omega Bold
+.FT
+.
+.TQ
+.B OBI
+.FT OBI
+CG Omega Bold Italic
+.FT
+.
+.TQ
+.B OI
+.FT OI
+CG Omega Italic
+.FT
+.
+.TQ
+.B OR
+.FT OR
+CG Omega Roman
+.
+.TQ
+.B OB
+.FT OB
+CG Omega Bold
+.FT
+.
+.TQ
+.B OBI
+.FT OBI
+CG Omega Bold Italic
+.FT
+.
+.TQ
+.B OI
+.FT OI
+CG Omega Italic
+.FT
+.
+.TQ
+.B OR
+.FT OR
+CG Omega Roman
+.FT
+.
+.TQ
+.B TB
+.FT TB
+CG Times Bold
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+CG Times Bold Italic
+.FT
+.
+.TQ
+.B TI
+.FT TI
+CG Times Italic
+.FT
+.
+.TQ
+.B TR
+.FT TR
+CG Times Roman
+.FT
+.
+.TQ
+.B TNRB
+.FT TNRB
+M Times Bold
+.FT
+.
+.TQ
+.B TNRBI
+.FT TNRBI
+M Times Bold Italic
+.FT
+.
+.TQ
+.B TNRI
+.FT TNRI
+M Times Italic
+.FT
+.
+.TQ
+.B TNRR
+.FT TNRR
+M Times Roman
+.FT
+.
+.TQ
+.B UB
+.FT UB
+Univers Bold
+.FT
+.
+.TQ
+.B UBI
+.FT UBI
+Univers Bold Italic
+.FT
+.
+.TQ
+.B UI
+.FT UI
+Univers Medium Italic
+.FT
+.
+.TQ
+.B UR
+.FT UR
+Univers Medium
+.FT
+.
+.TQ
+.B UCB
+.FT UCB
+Univers Condensed Bold
+.FT
+.
+.TQ
+.B UCBI
+.FT UCBI
+Univers Condensed Bold Italic
+.FT
+.
+.TQ
+.B UCI
+.FT UCI
+Univers Condensed Medium Italic
+.FT
+.
+.TQ
+.B UCR
+.FT UCR
+Univers Condensed Medium
+.FT
+.RE
+.
+.
+.P
+The following fonts are not members of a family.
+.
+.
+.RS
+.TP 14n
+.B ALBB
+.FT ALBB
+Albertus Extra Bold
+.FT
+.
+.TQ
+.B ALBR
+.FT ALBR
+Albertus Medium
+.FT
+.
+.TQ
+.B AOB
+.FT AOB
+Antique Olive Bold
+.
+.TQ
+.B AOI
+.FT AOI
+Antique Olive Italic
+.
+.TQ
+.B AOR
+.FT AOR
+Antique Olive Roman
+.
+.TQ
+.B CLARENDON
+.FT CLARENDON
+Clarendon
+.
+.TQ
+.B CORONET
+.FT CORONET
+Coronet
+.
+.TQ
+.B LGB
+.FT LGB
+Letter Gothic Bold
+.
+.TQ
+.B LGI
+.FT LGI
+Letter Gothic Italic
+.
+.TQ
+.B LGR
+.FT LGR
+Letter Gothic Roman
+.
+.TQ
+.B MARIGOLD
+.FT MARIGOLD
+Marigold
+.RE
+.
+.
+.P
+The special font is
+.B S
+(PostScript Symbol);
+.B SYMBOL
+(M Symbol),
+and
+.B WINGDINGS
+(Wingdings)
+are also available but not mounted by default.
+.
+.
+.\" ====================================================================
+.SS "Paper format and device description file"
+.\" ====================================================================
+.
+.I grolj4
+supports paper formats
+.RB \[lq] A4 \[rq],
+.RB \[lq] B5 \[rq],
+.RB \[lq] C5 \[rq],
+.RB \[lq] com10 \[rq],
+.RB \[lq] DL \[rq],
+.RB \%\[lq] executive \[rq],
+.RB \%\[lq] legal \[rq],
+.RB \%\[lq] letter \[rq],
+and
+.RB \[lq] monarch \[rq].
+.
+These are matched case-insensitively.
+.
+The
+.B \-p
+option overrides any setting in the device description file
+.IR DESC .
+.
+If neither specifies a paper format,
+\[lq]letter\[rq] is assumed.
+.
+.
+.\" ====================================================================
+.SS "Font description files"
+.\" ====================================================================
+.
+.I grolj4
+recognizes four font description file directives in addition to those
+documented in
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI pclweight\~ n
+Set the stroke weight to
+.IR n ,
+an integer in the range \-7 to +7;
+the default is\~0.
+.
+.
+.TP
+.BI pclstyle\~ n
+Set the style to
+.IR n ,
+an integer in the range 0 to 32767;
+the default is\~0.
+.
+.
+.TP
+.BI pclproportional\~ n
+Set the proportional spacing Boolean flag to
+.IR n ,
+which can be either 0 or\~1;
+the default is\~0.
+.
+.
+.TP
+.BI pcltypeface\~ n
+Set the typeface family to
+.IR n ,
+an integer in the range 0 to 65535;
+the default is\~0.
+.
+.
+.\" ====================================================================
+.SS "Drawing commands"
+.\" ====================================================================
+.
+An additional drawing command is recognized as an extension to those
+documented in
+.MR groff @MAN7EXT@ .
+.
+.
+.TP
+.BI \[rs]D\[aq]R\~ "dh dv" \[aq]
+Draw a rule
+(solid black rectangle)
+with one corner at the drawing position,
+and the diagonally opposite corner at the drawing position
+.RI +( dh , dv ),
+at which the drawing position will be afterward.
+.
+This generates a PCL fill rectangle command,
+and so will work on printers that do not support HP-GL/2,
+unlike the other
+.B \[rs]D
+commands.
+.
+.
+.\" ====================================================================
+.SS Fonts
+.\" ====================================================================
+.
+Nominally,
+all Hewlett-Packard LaserJet\~\%4-series and newer printers have the
+same internal fonts:
+45 scalable fonts and one bitmapped Lineprinter font.
+.
+The scalable fonts are available in sizes between 0.25 points and 999.75
+points,
+in 0.25-point increments;
+the Lineprinter font is available only in 8.5-point size.
+.
+.
+.P
+The LaserJet font files included with
+.I groff
+assume that all printers since the LaserJet\~4 are identical.
+.
+There are some differences between fonts in the earlier and more recent
+printers,
+however.
+.
+The LaserJet\~4 printer used Agfa Intellifont technology for 35 of the
+internal scalable fonts;
+the remaining 10 scalable fonts were TrueType.
+.
+Beginning with the LaserJet\~\%4000-series printers introduced in 1997,
+all scalable internal fonts have been TrueType.
+.
+The number of printable glyphs differs slightly between Intellifont and
+TrueType fonts
+(generally,
+the TrueType fonts include more glyphs),
+and
+there are some minor differences in glyph metrics.
+.
+Differences among printer models are described in the
+.I "PCL\~5 Comparison Guide"
+and the
+.I "PCL\~5 Comparison Guide Addendum"
+(for printers introduced since approximately 2001).
+.
+.
+.P
+LaserJet printers reference a glyph by a combination of a 256-glyph
+symbol set and an index within that symbol set.
+.
+Many glyphs appear in more than one symbol set;
+all combinations of symbol set and index that reference the same glyph
+are equivalent.
+.
+For each glyph,
+.MR hpftodit @MAN1EXT@
+searches a list of symbol sets,
+and selects the first set that contains the glyph.
+.
+The printing code generated by
+.I hpftodit
+is an integer that encodes a numerical value for the symbol set in the
+high byte(s),
+and the index in the low byte.
+.
+See
+.MR groff_font @MAN5EXT@
+for a complete description of the font file format;
+symbol sets are described in greater detail in the
+.IR "PCL\~5 Printer Language Technical Reference Manual" .
+.
+.
+.P
+Two of the scalable fonts,
+Symbol and Wingdings,
+are bound to 256-glyph symbol sets;
+the remaining scalable fonts,
+as well as the Lineprinter font,
+support numerous symbol sets,
+sufficient to enable printing of more than 600 glyphs.
+.
+.
+.P
+The metrics generated by
+.I hpftodit
+assume that the
+.I DESC
+file contains values of 1200 for
+.I res
+and 6350 for
+.IR unitwidth ,
+or any combination
+(e.g.,
+2400 and 3175)
+for which
+.IR res \~\[tmu]\~ unitwidth \~=\~7\|620\|000.
+.
+Although HP PCL\~5 LaserJet printers support an internal resolution of
+7200 units per inch,
+they use a 16-bit signed integer for positioning;
+if
+.B devlj4
+is to support U.S.\& ledger paper (11\~in\~\[mu]\~17\~in;
+in = inch),
+the maximum usable resolution is 32\|767\~\[di]\~17,
+or 1927 units per inch,
+which rounds down to 1200 units per inch.
+.
+If the largest required paper dimension is less
+(e.g.,
+8.5\~in\~\[mu]\~11\~in,
+or A5),
+a greater
+.I res
+(and lesser
+.IR unitwidth )
+can be specified.
+.
+.
+.P
+Font metrics for Intellifont fonts were provided by Tagged Font Metric
+(TFM) files originally developed by Agfa/Compugraphic.
+.
+The TFM files provided for these fonts supported 600+ glyphs and
+contained extensive lists of kerning pairs.
+.
+.
+.P
+To accommodate developers who had become accustomed to TFM files,
+HP also provided TFM files for the 10 TrueType fonts included in the
+LaserJet\~4.
+.
+The TFM files for TrueType fonts generally included less information
+than the Intellifont TFMs,
+supporting fewer glyphs,
+and in most cases,
+providing no kerning information.
+.
+By the time the LaserJet\~4000 printer was introduced,
+most developers had migrated to other means of obtaining font metrics,
+and support for new TFM files was very limited.
+.
+The TFM files provided for the TrueType fonts in the LaserJet\~4000
+support only the Latin 2 (ISO 8859-2) symbol set,
+and include no kerning information;
+consequently,
+they are of little value for any but the most rudimentary documents.
+.
+.
+.P
+Because the Intellifont TFM files contain considerably more information,
+they generally are preferable to the TrueType TFM files even for use
+with the TrueType fonts in the newer printers.
+.
+The metrics for the TrueType fonts are very close,
+though not identical,
+to those for the earlier Intellifont fonts of the same names.
+.
+Although most output using the Intellifont metrics with the newer
+printers is quite acceptable,
+a few glyphs may fail to print as expected.
+.
+The differences in glyph metrics may be particularly noticeable with
+composite parentheses,
+brackets,
+and braces used by
+.MR eqn @MAN1EXT@ .
+.
+A script,
+located in
+.IR @FONTDIR@/\:\%devlj4/\:generate ,
+can be used to adjust the metrics for these glyphs in the special font
+\[lq]S\[rq] for use with printers that have all TrueType fonts.
+.
+.
+.P
+At the time HP last supported TFM files,
+only version 1.0 of the Unicode standard was available.
+.
+Consequently,
+many glyphs lacking assigned code points were assigned by HP to the
+Private Use Area (PUA).
+.
+Later versions of the Unicode standard included code points outside the
+PUA for many of these glyphs.
+.
+The HP-supplied TrueType TFM files use the PUA assignments;
+TFM files generated from more recent TrueType font files require the
+later Unicode values to access the same glyphs.
+.
+Consequently,
+two different mapping files may be required:
+one for the HP-supplied TFM files,
+and one for more recent TFM files.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-c\~ num-copies
+Format
+.I num-copies
+copies of each page.
+.
+.
+.TP
+.BR \-d \~[\c
+.IR n ]
+Use duplex mode
+.IR n :
+1\~is long-side binding (default),
+and 2\~is short-side binding.
+.
+.
+.TP
+.BI \-F " font-directory"
+Prepend directory
+.IR font-directory /dev name
+to the search path for font and device description files;
+.I name
+is the name of the device,
+usually
+.BR lj4 .
+.
+.
+.TP
+.B \-l
+Format the document in landscape orientation.
+.
+.
+.TP
+.BI \-p " paper-format"
+Set the paper format to
+.IR paper-format ,
+which must be a valid paper format as described above.
+.
+.
+.TP
+.BI \-w " line-width"
+Set the default line thickness to
+.I line-width
+thousandths of an em;
+the default is
+.B 40
+(0.04\~em).
+.
+.
+.br
+.ne 4v \" Keep section heading and paragraph together.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+lists directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devlj4/\:DESC
+describes the
+.B lj4
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devlj4/ F
+describes the font known
+.RI as\~ F
+on device
+.BR lj4 .
+.
+.
+.TP
+.I @MACRODIR@/\:lj4\:.tmac
+defines macros for use with the
+.B lj4
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B lj4
+output device is selected.
+.
+.
+.\" ====================================================================
+.SH Bugs
+.\" ====================================================================
+.
+.\" XXX: What does this mean? The period/full stop glyph? Flyspecks?
+Small dots.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR http://\:www\:.hp\:.com/\:ctg/\:Manual/\:bpl13210\:.pdf
+.I HP PCL/PJL Reference:
+.I PCL\~5 Printer Language Technical Reference Manual,
+.I Part I
+.UE
+.
+.
+.P
+.MR hpftodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@
+.
+.
+.\" Clean up.
+.rm FT
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grolj4_1_man_C]
+.do rr *groff_grolj4_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grolj4/grolj4.am b/src/devices/grolj4/grolj4.am
new file mode 100644
index 0000000..37138b2
--- /dev/null
+++ b/src/devices/grolj4/grolj4.am
@@ -0,0 +1,32 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grolj4
+grolj4_SOURCES = src/devices/grolj4/lj4.cpp
+grolj4_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grolj4/grolj4.1
+EXTRA_DIST += \
+ src/devices/grolj4/grolj4.1.man
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grolj4/lj4.cpp b/src/devices/grolj4/lj4.cpp
new file mode 100644
index 0000000..a429a7d
--- /dev/null
+++ b/src/devices/grolj4/lj4.cpp
@@ -0,0 +1,715 @@
+/* Copyright (C) 1994-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+TODO
+
+option to use beziers for circle/ellipse/arc
+option to use lines for spline (for LJ3)
+left/top offset registration
+output bin selection option
+paper source option
+output non-integer parameters using fixed point numbers
+X command to insert contents of file
+X command to specify inline escape sequence (how to specify unprintable chars?)
+X command to include bitmap graphics
+*/
+
+#include <assert.h>
+
+#include "driver.h"
+#include "nonposix.h"
+
+extern "C" const char *Version_string;
+
+static struct {
+ const char *name;
+ int code;
+ // at 300dpi
+ int x_offset_portrait;
+ int x_offset_landscape;
+} paper_table[] = {
+ { "letter", 2, 75, 60 },
+ { "legal", 3, 75, 60 },
+ { "executive", 1, 75, 60 },
+ { "a4", 26, 71, 59 },
+ { "com10", 81, 75, 60 },
+ { "monarch", 80, 75, 60 },
+ { "c5", 91, 71, 59 },
+ { "b5", 100, 71, 59 },
+ { "dl", 90, 71, 59 },
+};
+
+static int user_paper_size = -1;
+static int landscape_flag = 0;
+static int duplex_flag = 0;
+
+// An upper limit on the paper dimensions in centipoints,
+// used for setting HPGL picture frame.
+#define MAX_PAPER_WIDTH (12*720)
+#define MAX_PAPER_HEIGHT (17*720)
+
+// Dotted lines that are thinner than this don't work right.
+#define MIN_DOT_PEN_WIDTH .351
+
+#ifndef DEFAULT_LINE_WIDTH_FACTOR
+// in ems/1000
+#define DEFAULT_LINE_WIDTH_FACTOR 40
+#endif
+
+const int DEFAULT_HPGL_UNITS = 1016;
+int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR;
+unsigned ncopies = 0; // 0 means don't send ncopies command
+
+static int lookup_paper_size(const char *);
+
+class lj4_font : public font {
+public:
+ ~lj4_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static lj4_font *load_lj4_font(const char *);
+ int weight;
+ int style;
+ int proportional;
+ int typeface;
+private:
+ lj4_font(const char *);
+};
+
+lj4_font::lj4_font(const char *nm)
+: font(nm), weight(0), style(0), proportional(0), typeface(0)
+{
+}
+
+lj4_font::~lj4_font()
+{
+}
+
+lj4_font *lj4_font::load_lj4_font(const char *s)
+{
+ lj4_font *f = new lj4_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+static struct {
+ const char *s;
+ int lj4_font::*ptr;
+ int min;
+ int max;
+} command_table[] = {
+ { "pclweight", &lj4_font::weight, -7, 7 },
+ { "pclstyle", &lj4_font::style, 0, 32767 },
+ { "pclproportional", &lj4_font::proportional, 0, 1 },
+ { "pcltypeface", &lj4_font::typeface, 0, 65535 },
+};
+
+void lj4_font::handle_unknown_font_command(const char *command,
+ const char *arg,
+ const char *filename, int lineno)
+{
+ for (unsigned int i = 0;
+ i < sizeof(command_table)/sizeof(command_table[0]); i++) {
+ if (strcmp(command, command_table[i].s) == 0) {
+ if (arg == 0)
+ fatal_with_file_and_line(filename, lineno,
+ "'%1' command requires an argument",
+ command);
+ char *ptr;
+ long n = strtol(arg, &ptr, 10);
+ if (n == 0 && ptr == arg)
+ fatal_with_file_and_line(filename, lineno,
+ "'%1' command requires numeric argument",
+ command);
+ if (n < command_table[i].min) {
+ error_with_file_and_line(filename, lineno,
+ "argument for '%1' command must not be less than %2",
+ command, command_table[i].min);
+ n = command_table[i].min;
+ }
+ else if (n > command_table[i].max) {
+ error_with_file_and_line(filename, lineno,
+ "argument for '%1' command must not be greater than %2",
+ command, command_table[i].max);
+ n = command_table[i].max;
+ }
+ this->*command_table[i].ptr = int(n);
+ break;
+ }
+ }
+}
+
+class lj4_printer : public printer {
+public:
+ lj4_printer(int);
+ ~lj4_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *name);
+ void draw(int code, int *p, int np, const environment *env);
+ void begin_page(int);
+ void end_page(int page_length);
+ font *make_font(const char *);
+ void end_of_line();
+private:
+ void set_line_thickness(int size, int dot = 0);
+ void hpgl_init();
+ void hpgl_start();
+ void hpgl_end();
+ int moveto(int hpos, int vpos);
+ int moveto1(int hpos, int vpos);
+
+ int cur_hpos;
+ int cur_vpos;
+ lj4_font *cur_font;
+ int cur_size;
+ unsigned short cur_symbol_set;
+ int x_offset;
+ int line_thickness;
+ double pen_width;
+ double hpgl_scale;
+ int hpgl_inited;
+ int paper_size;
+};
+
+inline
+int lj4_printer::moveto(int hpos, int vpos)
+{
+ if (cur_hpos != hpos || cur_vpos != vpos || cur_hpos < 0)
+ return moveto1(hpos, vpos);
+ else
+ return 1;
+}
+
+inline
+void lj4_printer::hpgl_start()
+{
+ fputs("\033%1B", stdout);
+}
+
+inline
+void lj4_printer::hpgl_end()
+{
+ fputs(";\033%0A", stdout);
+}
+
+lj4_printer::lj4_printer(int ps)
+: cur_hpos(-1),
+ cur_font(0),
+ cur_size(0),
+ cur_symbol_set(0),
+ line_thickness(-1),
+ pen_width(-1.0),
+ hpgl_inited(0)
+{
+ if (7200 % font::res != 0)
+ fatal("invalid resolution %1: resolution must be a factor of 7200",
+ font::res);
+ fputs("\033E", stdout); // reset
+ if (font::res != 300)
+ printf("\033&u%dD", font::res); // unit of measure
+ if (ncopies > 0)
+ printf("\033&l%uX", ncopies);
+ paper_size = 0; // default to letter
+ if (font::papersize) {
+ int n = lookup_paper_size(font::papersize);
+ if (n < 0)
+ error("ignoring invalid paper format '%1'", font::papersize);
+ else
+ paper_size = n;
+ }
+ if (ps >= 0)
+ paper_size = ps;
+ printf("\033&l%dA" // paper format
+ "\033&l%dO" // orientation
+ "\033&l0E", // no top margin
+ paper_table[paper_size].code,
+ landscape_flag != 0);
+ if (landscape_flag)
+ x_offset = paper_table[paper_size].x_offset_landscape;
+ else
+ x_offset = paper_table[paper_size].x_offset_portrait;
+ x_offset = (x_offset * font::res) / 300;
+ if (duplex_flag)
+ printf("\033&l%dS", duplex_flag);
+}
+
+lj4_printer::~lj4_printer()
+{
+ fputs("\033E", stdout);
+}
+
+void lj4_printer::begin_page(int)
+{
+}
+
+void lj4_printer::end_page(int)
+{
+ putchar('\f');
+ cur_hpos = -1;
+}
+
+void lj4_printer::end_of_line()
+{
+ cur_hpos = -1; // force absolute motion
+}
+
+inline
+int is_unprintable(unsigned char c)
+{
+ return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27);
+}
+
+void lj4_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ int code = f->get_code(g);
+
+ unsigned char ch = code & 0xff;
+ unsigned short symbol_set = code >> 8;
+ if (symbol_set != cur_symbol_set) {
+ printf("\033(%d%c", symbol_set/32, (symbol_set & 31) + 64);
+ cur_symbol_set = symbol_set;
+ }
+ if (f != cur_font) {
+ lj4_font *psf = (lj4_font *)f;
+ // FIXME only output those that are needed
+ printf("\033(s%dp%ds%db%dT",
+ psf->proportional,
+ psf->style,
+ psf->weight,
+ psf->typeface);
+ if (!psf->proportional || !cur_font || !cur_font->proportional)
+ cur_size = 0;
+ cur_font = psf;
+ }
+ if (env->size != cur_size) {
+ if (cur_font->proportional) {
+ static const char *quarters[] = { "", ".25", ".5", ".75" };
+ printf("\033(s%d%sV", env->size/4, quarters[env->size & 3]);
+ }
+ else {
+ double pitch = double(font::res)/w;
+ // PCL uses the next largest pitch, so round it down.
+ pitch = floor(pitch*100.0)/100.0;
+ printf("\033(s%.2fH", pitch);
+ }
+ cur_size = env->size;
+ }
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ if (is_unprintable(ch))
+ fputs("\033&p1X", stdout);
+ putchar(ch);
+ cur_hpos += w;
+}
+
+int lj4_printer::moveto1(int hpos, int vpos)
+{
+ if (hpos < x_offset || vpos < 0)
+ return 0;
+ fputs("\033*p", stdout);
+ if (cur_hpos < 0)
+ printf("%dx%dY", hpos - x_offset, vpos);
+ else {
+ if (cur_hpos != hpos)
+ printf("%s%d%c", hpos > cur_hpos ? "+" : "",
+ hpos - cur_hpos, vpos == cur_vpos ? 'X' : 'x');
+ if (cur_vpos != vpos)
+ printf("%s%dY", vpos > cur_vpos ? "+" : "", vpos - cur_vpos);
+ }
+ cur_hpos = hpos;
+ cur_vpos = vpos;
+ return 1;
+}
+
+void lj4_printer::draw(int code, int *p, int np, const environment *env)
+{
+ switch (code) {
+ case 'R':
+ {
+ if (np != 2) {
+ error("2 arguments required for rule");
+ break;
+ }
+ int hpos = env->hpos;
+ int vpos = env->vpos;
+ int hsize = p[0];
+ int vsize = p[1];
+ if (hsize < 0) {
+ hpos += hsize;
+ hsize = -hsize;
+ }
+ if (vsize < 0) {
+ vpos += vsize;
+ vsize = -vsize;
+ }
+ if (!moveto(hpos, vpos))
+ return;
+ printf("\033*c%da%db0P", hsize, vsize);
+ break;
+ }
+ case 'l':
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ set_line_thickness(env->size, p[0] == 0 && p[1] == 0);
+ printf("PD%d,%d", p[0], p[1]);
+ hpgl_end();
+ break;
+ case 'p':
+ case 'P':
+ {
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ if (code == 'p')
+ set_line_thickness(env->size);
+ printf("PMPD%d", p[0]);
+ for (int i = 1; i < np; i++)
+ printf(",%d", p[i]);
+ printf("PM2%cP", code == 'p' ? 'E' : 'F');
+ hpgl_end();
+ break;
+ }
+ case '~':
+ {
+ if (np & 1) {
+ error("even number of arguments required for spline");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for spline");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ set_line_thickness(env->size);
+ printf("PD%d,%d", p[0]/2, p[1]/2);
+ const int tnum = 2;
+ const int tden = 3;
+ if (np > 2) {
+ fputs("BR", stdout);
+ for (int i = 0; i < np - 2; i += 2) {
+ if (i != 0)
+ putchar(',');
+ printf("%d,%d,%d,%d,%d,%d",
+ (p[i]*tnum)/(2*tden),
+ (p[i + 1]*tnum)/(2*tden),
+ p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden),
+ p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden),
+ (p[i] - p[i]/2) + p[i + 2]/2,
+ (p[i + 1] - p[i + 1]/2) + p[i + 3]/2);
+ }
+ }
+ printf("PR%d,%d", p[np - 2] - p[np - 2]/2, p[np - 1] - p[np - 1]/2);
+ hpgl_end();
+ break;
+ }
+ case 'c':
+ case 'C':
+ // troff adds an extra argument to C
+ if (np != 1 && !(code == 'C' && np == 2)) {
+ error("1 argument required for circle");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos + p[0]/2, env->vpos))
+ return;
+ hpgl_start();
+ if (code == 'c') {
+ set_line_thickness(env->size);
+ printf("CI%d", p[0]/2);
+ }
+ else
+ printf("WG%d,0,360", p[0]/2);
+ hpgl_end();
+ break;
+ case 'e':
+ case 'E':
+ if (np != 2) {
+ error("2 arguments required for ellipse");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos + p[0]/2, env->vpos))
+ return;
+ hpgl_start();
+ printf("SC0,%.4f,0,-%.4f,2", hpgl_scale * double(p[0])/p[1], hpgl_scale);
+ if (code == 'e') {
+ set_line_thickness(env->size);
+ printf("CI%d", p[1]/2);
+ }
+ else
+ printf("WG%d,0,360", p[1]/2);
+ printf("SC0,%.4f,0,-%.4f,2", hpgl_scale, hpgl_scale);
+ hpgl_end();
+ break;
+ case 'a':
+ {
+ if (np != 4) {
+ error("4 arguments required for arc");
+ break;
+ }
+ hpgl_init();
+ if (!moveto(env->hpos, env->vpos))
+ return;
+ hpgl_start();
+ set_line_thickness(env->size);
+ double c[2];
+ if (adjust_arc_center(p, c)) {
+ double sweep = ((atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])
+ - atan2(-c[1], -c[0]))
+ * 180.0/PI);
+ if (sweep > 0.0)
+ sweep -= 360.0;
+ printf("PDAR%d,%d,%f", int(c[0]), int(c[1]), sweep);
+ }
+ else
+ printf("PD%d,%d", p[0] + p[2], p[1] + p[3]);
+ hpgl_end();
+ }
+ break;
+ case 'f':
+ if (np != 1 && np != 2) {
+ error("1 argument required for fill");
+ break;
+ }
+ hpgl_init();
+ hpgl_start();
+ if (p[0] >= 0 && p[0] <= 1000)
+ printf("FT10,%d", p[0]/10);
+ hpgl_end();
+ break;
+ case 'F':
+ // not implemented yet
+ break;
+ case 't':
+ {
+ if (np == 0) {
+ line_thickness = -1;
+ }
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ }
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+}
+
+void lj4_printer::hpgl_init()
+{
+ if (hpgl_inited)
+ return;
+ hpgl_inited = 1;
+ hpgl_scale = double(DEFAULT_HPGL_UNITS)/font::res;
+ printf("\033&f0S" // push position
+ "\033*p0x0Y" // move to 0,0
+ "\033*c%dx%dy0T" // establish picture frame
+ "\033%%1B" // switch to HPGL
+ "SP1SC0,%.4f,0,-%.4f,2IR0,100,0,100" // set up scaling
+ "LA1,4,2,4" // round line ends and joins
+ "PR" // relative plotting
+ "TR0" // opaque
+ ";\033%%1A" // back to PCL
+ "\033&f1S", // pop position
+ MAX_PAPER_WIDTH, MAX_PAPER_HEIGHT,
+ hpgl_scale, hpgl_scale);
+}
+
+void lj4_printer::set_line_thickness(int size, int dot)
+{
+ double pw;
+ if (line_thickness < 0)
+ pw = (size * (line_width_factor * 25.4))/(font::sizescale * 72000.0);
+ else
+ pw = line_thickness*25.4/font::res;
+ if (dot && pw < MIN_DOT_PEN_WIDTH)
+ pw = MIN_DOT_PEN_WIDTH;
+ if (pw != pen_width) {
+ printf("PW%f", pw);
+ pen_width = pw;
+ }
+}
+
+font *lj4_printer::make_font(const char *nm)
+{
+ return lj4_font::load_lj4_font(nm);
+}
+
+printer *make_printer()
+{
+ return new lj4_printer(user_paper_size);
+}
+
+static
+int lookup_paper_size(const char *s)
+{
+ for (unsigned int i = 0;
+ i < sizeof(paper_table)/sizeof(paper_table[0]); i++) {
+ // FIXME Perhaps allow unique prefix.
+ if (strcasecmp(s, paper_table[i].name) == 0)
+ return i;
+ }
+ return -1;
+}
+
+static void usage(FILE *stream);
+
+extern "C" int optopt, optind;
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "c:d:F:I:lp:vw:", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'l':
+ landscape_flag = 1;
+ break;
+ case 'I':
+ // ignore include search path
+ break;
+ case ':':
+ if (optopt == 'd') {
+ fprintf(stderr, "duplex assumed to be long-side\n");
+ duplex_flag = 1;
+ } else
+ fprintf(stderr, "option -%c requires an argument\n", optopt);
+ fflush(stderr);
+ break;
+ case 'd':
+ if (!isdigit(*optarg)) // this ugly hack prevents -d without
+ optind--; // args from messing up the arg list
+ duplex_flag = atoi(optarg);
+ if (duplex_flag != 1 && duplex_flag != 2) {
+ fprintf(stderr, "odd value for duplex; assumed to be long-side\n");
+ duplex_flag = 1;
+ }
+ break;
+ case 'p':
+ {
+ int n = lookup_paper_size(optarg);
+ if (n < 0)
+ error("ignoring invalid paper format '%1'", font::papersize);
+ else
+ user_paper_size = n;
+ break;
+ }
+ case 'v':
+ printf("GNU grolj4 (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'c':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg)
+ error("argument for -c must be a positive integer");
+ else if (n <= 0 || n > 32767)
+ error("out of range argument for -c");
+ else
+ ncopies = unsigned(n);
+ break;
+ }
+ case 'w':
+ {
+ char *ptr;
+ long n = strtol(optarg, &ptr, 10);
+ if (n == 0 && ptr == optarg)
+ error("argument for -w must be a non-negative integer");
+ else if (n < 0 || n > INT_MAX)
+ error("out of range argument for -w");
+ else
+ line_width_factor = int(n);
+ break;
+ }
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ SET_BINARY(fileno(stdout));
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+ "usage: %s [-l] [-c n] [-d [n]] [-F dir] [-p paper-format]"
+ " [-w n] [file ...]\n"
+ "usage: %s {-v | --version}\n"
+ "usage: %s --help\n",
+ program_name, program_name, program_name);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/gropdf/TODO b/src/devices/gropdf/TODO
new file mode 100644
index 0000000..12042c2
--- /dev/null
+++ b/src/devices/gropdf/TODO
@@ -0,0 +1,31 @@
+pspic.tmac
+----------
+
+Equiv for gropdf is pdfpic (which is dependent on a program pdfbb (to
+extract MediaBox (etc.) from the pdf) which is not written yet! Meanwhile
+you could use \X'pdf: pdfpic filename -L|R|C wid (hgt) (linelen)' (-R and -C
+require a linelen) Wid or hgt may be zero (in which case the same scaling as
+the other axis is used). The disadvantage of this call (over pdfpic macro)
+is that it is transparent to groff, after placing the image the current X/Y
+position remains what it was, so you need to do your own 'motion control'
+(like a .sp) to "step over" the image you just placed.
+
+psfig.tmac
+----------
+
+No equiv for gropdf.
+
+psatk.tmac
+----------
+
+No equiv for gropdf.
+
+-I : search -I directory for included files
+
+-w : set line width
+
+Another \X : \X'ps: exec 0 setlinejoin'\X'ps: exec 0 setlinecap' for mom
+
+Cater for fonts with >255 glyphs (currently accessing a glyph above 255
+(i.e. \N[260]) causes a fail). This will be fixed when font subsetting is
+implemented.
diff --git a/src/devices/gropdf/gropdf.1.man b/src/devices/gropdf/gropdf.1.man
new file mode 100644
index 0000000..d1d39bb
--- /dev/null
+++ b/src/devices/gropdf/gropdf.1.man
@@ -0,0 +1,1845 @@
+.TH gropdf @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+gropdf \-
+.I groff
+output driver for Portable Document Format
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2011-2022 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_gropdf_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'ps' .ft \\$1
+. if '\\*(.T'pdf' .ft \\$1
+..
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY gropdf
+.RB [ \-dels ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-p\~\c
+.IR paper-format ]
+\#.RB [ \-w\~\c
+\#.IR n ]
+.RB [ \-u
+.RI [ cmap-file ]]
+.RB [ \-y\~\c
+.IR foundry ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY gropdf
+.B \-\-help
+.YS
+.
+.
+.SY gropdf
+.B \-v
+.
+.SY gropdf
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+PDF output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into Portable Document Format.
+.
+Normally,
+.I gropdf
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~pdf \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR gropdf .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I gropdf
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+See section \[lq]Font installation\[rq] below for a guide to installing
+fonts for
+.IR gropdf .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-d
+Include debug information as comments within the PDF.
+.
+Also produces an uncompressed PDF.
+.
+.
+.TP
+.B \-e
+Forces
+.I gropdf
+to embed
+.I all
+fonts (even the 14 base PDF fonts).
+.
+.
+.TP
+.BI \-F " dir"
+Prepend directory
+.IR dir /dev name
+to the search path for font, and device description files;
+.I name
+is the name of the device, usually
+.BR pdf .
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+for files named in
+.B \[rs]X\[aq]pdf: pdfpic\[aq]
+device control commands.
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.
+.TP
+.B \-l
+Orient the document in landscape format.
+.
+.TP
+.BI \-p " paper-format"
+Set the physical dimensions of the output medium.
+.
+This overrides the
+.BR papersize ,
+.BR paperlength ,
+and
+.B paperwidth
+directives in the
+.I DESC
+file;
+it accepts the same arguments as the
+.B papersize
+directive.
+.
+See
+.MR groff_font @MAN5EXT@
+for details.
+.
+.
+.TP
+.B \-s
+Append a comment line to end of PDF showing statistics,
+i.e.\& number of pages in document.
+.
+Ghostscript's
+.B ps2pdf
+complains about this line if it is included, but works anyway.
+.
+.
+.TP
+.BR \-u \~[\c
+.IR cmap-file ]
+.I gropdf
+normally includes a ToUnicode CMap with any font created using
+.I text.enc
+as the encoding file,
+this makes it easier to search for words which contain ligatures.
+.
+You can include your own CMap by specifying a
+.I cmap-file
+or have no CMap at all by omitting the argument.
+.
+.
+.\" .TP
+.\" .BI \-w n
+.\" Lines should be drawn using a thickness of
+.\" .IR n \~\c
+.\" thousandths of an em.
+.\" .
+.\" If this option is not given, the line thickness defaults to
+.\" 0.04\~em.
+.\" .
+.\" .
+.TP
+.BI \-y " foundry"
+Set the foundry to use for selecting fonts of the same name.
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+The input to
+.I gropdf
+must be in the format output by
+.MR @g@troff @MAN1EXT@ .
+.
+This is described in
+.MR groff_out @MAN5EXT@ .
+.
+In addition, the device and font description files for the device used
+must meet certain requirements:
+.
+The resolution must be an integer multiple of\~72 times the
+.BR sizescale .
+.
+The
+.B pdf
+device uses a resolution of 72000 and a sizescale of 1000.
+.
+.
+.LP
+The device description file must contain a valid paper format;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.I gropdf
+uses the same Type\~1 Adobe PostScript fonts as the
+.B grops
+device driver.
+.
+Although the PDF Standard allows the use of other font types (like
+TrueType) this implementation only accepts the Type\~1 PostScript
+font.
+.
+Fewer Type\~1 fonts are supported natively in PDF documents than the
+standard 35 fonts supported by
+.B grops
+and all PostScript printers, but all the fonts are available since any
+which aren't supported natively are automatically embedded in the
+PDF.
+.
+.
+.LP
+.I gropdf
+supports the concept of foundries,
+that is different versions of basically the same font.
+.
+During install a
+.I Foundry
+file controls where fonts are found and builds
+.I groff
+fonts from the files it discovers on your system.
+.
+.
+.LP
+Each font description file must contain a command
+.
+.IP
+.BI internalname\ psname
+.
+.LP
+which says that the PostScript name of the font is
+.IR psname .
+.
+Lines starting with
+.B #
+and blank lines are ignored.
+.
+The code for each character given in the font file must correspond
+to the code in the default encoding for the font.
+.
+This code can be used with the
+.B \[rs]N
+escape sequence in
+.B troff
+to select the character,
+even if the character does not have a
+.I groff
+name.
+.
+Every character in the font file must exist in the PostScript font, and
+the widths given in the font file must match the widths used
+in the PostScript font.
+.
+.
+.LP
+Note that
+.I gropdf
+is currently only able to display the first 256 glyphs in any font.
+This restriction will be lifted in a later version.
+.
+.
+.\" .LP
+.\" Note that
+.\" .B grops
+.\" is able to display all glyphs in a PostScript font, not only 256.
+.\" .I enc_file
+.\" (or the default encoding if no encoding file specified) just defines
+.\" the order of glyphs for the first 256 characters;
+.\" all other glyphs are accessed with additional encoding vectors which
+.\" .B grops
+.\" produces on the fly.
+.
+.
+.LP
+.I gropdf
+can automatically include the downloadable fonts necessary
+to print the document.
+.
+Fonts may be in PFA or PFB format.
+.LP
+.
+Any downloadable fonts which should, when required, be included by
+.I gropdf
+must be listed in the file
+.IR @FONTDIR@/\:\%devpdf/\:\%download ;
+this should consist of lines of the form
+.
+.IP
+.I
+foundry font filename
+.
+.LP
+where
+.I foundry
+is the foundry name or blank for the default foundry.
+.
+.I font
+is the PostScript name of the font,
+and
+.I filename
+is the name of the file containing the font;
+lines beginning with
+.B #
+and blank lines are ignored;
+fields must be separated by tabs
+(spaces are
+.B not
+allowed);
+.I filename
+is searched for using the same mechanism that is used
+for
+.I groff
+font metric files.
+.
+The
+.I download
+file itself is also sought using this mechanism.
+.
+Foundry names are usually a single character
+(such as \[oq]U\[cq] for the URW foundry)
+or empty for the default foundry.
+.
+This default uses the same fonts as
+.I ghostscript
+uses when it embeds fonts in a PDF file.
+.
+.
+.LP
+In the default setup there are styles called
+.BR R ,
+.BR I ,
+.BR B ,
+and
+.B BI
+mounted at font positions 1 to\~4.
+.
+The fonts are grouped into families
+.BR A ,
+.BR BM ,
+.BR C ,
+.BR H ,
+.BR HN ,
+.BR N ,
+.BR P ,
+and\~\c
+.B T
+having members in each of these styles:
+.
+.RS
+.TP
+.B AR
+.FT AR
+AvantGarde-Book
+.FT
+.
+.TQ
+.B AI
+.FT AI
+AvantGarde-BookOblique
+.FT
+.
+.TQ
+.B AB
+.FT AB
+AvantGarde-Demi
+.FT
+.
+.TQ
+.B ABI
+.FT ABI
+AvantGarde-DemiOblique
+.FT
+.
+.TQ
+.B BMR
+.FT BMR
+Bookman-Light
+.FT
+.
+.TQ
+.B BMI
+.FT BMI
+Bookman-LightItalic
+.FT
+.
+.TQ
+.B BMB
+.FT BMB
+Bookman-Demi
+.FT
+.
+.TQ
+.B BMBI
+.FT BMBI
+Bookman-DemiItalic
+.FT
+.
+.TQ
+.B CR
+.FT CR
+Courier
+.FT
+.
+.TQ
+.B CI
+.FT CI
+Courier-Oblique
+.FT
+.
+.TQ
+.B CB
+.FT CB
+Courier-Bold
+.FT
+.
+.TQ
+.B CBI
+.FT CBI
+Courier-BoldOblique
+.FT
+.
+.TQ
+.B HR
+.FT HR
+Helvetica
+.FT
+.
+.TQ
+.B HI
+.FT HI
+Helvetica-Oblique
+.FT
+.
+.TQ
+.B HB
+.FT HB
+Helvetica-Bold
+.FT
+.
+.TQ
+.B HBI
+.FT HBI
+Helvetica-BoldOblique
+.FT
+.
+.TQ
+.B HNR
+.FT HNR
+Helvetica-Narrow
+.FT
+.
+.TQ
+.B HNI
+.FT HNI
+Helvetica-Narrow-Oblique
+.FT
+.
+.TQ
+.B HNB
+.FT HNB
+Helvetica-Narrow-Bold
+.FT
+.
+.TQ
+.B HNBI
+.FT HNBI
+Helvetica-Narrow-BoldOblique
+.FT
+.
+.TQ
+.B NR
+.FT NR
+NewCenturySchlbk-Roman
+.FT
+.
+.TQ
+.B NI
+.FT NI
+NewCenturySchlbk-Italic
+.FT
+.
+.TQ
+.B NB
+.FT NB
+NewCenturySchlbk-Bold
+.FT
+.
+.TQ
+.B NBI
+.FT NBI
+NewCenturySchlbk-BoldItalic
+.FT
+.
+.TQ
+.B PR
+.FT PR
+Palatino-Roman
+.FT
+.
+.TQ
+.B PI
+.FT PI
+Palatino-Italic
+.FT
+.
+.TQ
+.B PB
+.FT PB
+Palatino-Bold
+.FT
+.
+.TQ
+.B PBI
+.FT PBI
+Palatino-BoldItalic
+.FT
+.
+.TQ
+.B TR
+.FT TR
+Times-Roman
+.FT
+.
+.TQ
+.B TI
+.FT TI
+Times-Italic
+.FT
+.
+.TQ
+.B TB
+.FT TB
+Times-Bold
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+Times-BoldItalic
+.FT
+.RE
+.
+.
+.LP
+There is also the following font which is not a member of a family:
+.
+.RS
+.TP
+.B ZCMI
+.FT ZCMI
+ZapfChancery-MediumItalic
+.FT
+.RE
+.
+.
+.LP
+There are also some special fonts called
+.B S
+for the PS Symbol font.
+.
+The lower case greek characters are automatically slanted (to match
+the SymbolSlanted font (SS) available to PostScript).
+.
+Zapf Dingbats is available as
+.BR ZD ;
+the \[lq]hand pointing left\[rq] glyph
+.RB ( \[rs][lh] )
+is available since it has been defined using the
+.B \[rs]X\[aq]pdf: xrev\[aq]
+device control command,
+which reverses the direction of letters within words.
+.
+.
+.LP
+The default color for
+.B \[rs]m
+and
+.B \[rs]M
+is black.
+.
+.
+.LP
+.I gropdf
+understands some of the device control commands supported by
+.MR grops 1 .
+.
+.
+.TP
+.B \[rs]X\[aq]ps: invis\[aq]
+Suppress output.
+.
+.
+.TP
+.B \[rs]X\[aq]ps: endinvis\[aq]
+Stop suppressing output.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec gsave currentpoint 2 copy translate\~" n\~\c
+.B rotate neg exch neg exch translate\[aq]
+where
+.I n
+is the angle of rotation.
+.
+This is to support the
+.B align
+command in
+.MR @g@pic 1 .
+.
+.
+.TP
+.B \[rs]X\[aq]ps: exec grestore\[aq]
+Used by
+.MR @g@pic 1
+to restore state after rotation.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec " "n\~" "setlinejoin\[aq]"
+where
+.I n
+can be one of the following values.
+.
+.
+.IP
+0 = Miter join
+.br
+1 = Round join
+.br
+2 = Bevel join
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec " "n " "setlinecap\[aq]"
+where
+.I n
+can be one of the following values.
+.
+.
+.IP
+0 = Butt cap
+.br
+1 = Round cap, and
+.br
+2 = Projecting square cap
+.
+.
+.LP
+.TP
+.BR "\[rs]X\[aq]ps:\~" .\|.\|.\& "\~pdfmark\[aq]"
+All the
+.I pdfmark
+macros installed by using
+.I \-m pdfmark
+or
+.I \-m mspdf
+(see documentation in
+.IR pdfmark.pdf ).
+.
+A subset of these macros are installed automatically when you use
+.B \-Tpdf
+so you should not need to use
+.RB \[lq] "\-m pdfmark" \[rq]
+to access most PDF functionality.
+.
+.
+.LP
+.I gropdf
+also supports a subset of the commands introduced in
+.IR present.tmac .
+.
+Specifically it supports:-
+.
+.
+.IP
+PAUSE
+.br
+BLOCKS
+.br
+BLOCKE
+.
+.
+.LP
+Which allows you to create presentation type PDFs.
+.
+Many of the other
+commands are already available in other macro packages.
+.
+.
+.LP
+These commands are implemented with
+.I groff
+X commands:-
+.
+.
+.LP
+.TP
+.B \[rs]X\[aq]ps: exec %%%%PAUSE\[aq]
+The section before this is treated as a block and is introduced using
+the current
+.B BLOCK
+transition setting
+(see
+.RB \[lq] "\[rs]X\[aq]pdf: transition\[aq]" \[rq]
+below).
+.
+Equivalently,
+.B \%.pdfpause
+is available as a macro.
+.TP
+.B \[rs]X\[aq]ps: exec %%%%BEGINONCE\[aq]
+Any text following this command (up to %%%%ENDONCE) is shown only once,
+the next %%%%PAUSE will remove it.
+If producing a non-presentation PDF, i.e.\&
+ignoring the pauses, see
+.I \%GROPDF_NOSLIDE
+below, this text is ignored.
+.LP
+.TP
+.B \[rs]X\[aq]ps: exec %%%%ENDONCE\[aq]
+This terminates the block defined by %%%%BEGINONCE.
+This pair of commands
+is what implements the \&.BLOCKS Once/.BLOCKE commands in
+.IR present.tmac .
+.
+.
+.LP
+The
+.I mom
+macro package already integrates these extensions,
+so you can build slides with
+.IR mom .
+.
+.
+.LP
+If you use
+.I present.tmac
+with
+.I gropdf
+there is no need to run the program
+.MR presentps @MAN1EXT@
+since the output will already be a presentation PDF.
+.
+.
+.LP
+All other
+.B ps:
+tags are silently ignored.
+.
+.
+.LP
+One
+.B \[rs]X
+device control command used by the DVI driver is also recognised.
+.
+.
+.TP
+.BI \[rs]X\[aq]papersize= paper-format \[aq]
+where the
+.I paper-format
+parameter is the same as that to the
+.B papersize
+directive.
+.
+See
+.MR groff_font @MAN5EXT@ .
+.
+This means that you can alter the page size at will within the PDF file
+being created by
+.IR gropdf .
+.
+If you do want to change the paper format,
+it must be done before you start creating the page.
+.
+.
+.LP
+.I gropdf
+supports several more device control features using the
+.B pdf:
+tag.
+.
+Some have counterpart
+.I convenience macros
+that take the same arguments and behave equivalently.
+.
+.
+.TP
+.BI "\[rs]X\[aq]pdf: pdfpic\~" file\~\c
+.IR "alignment width height line-length" \[aq]
+Place an image of the specified
+.I width
+containing the PDF drawing from file
+.I file
+of desired
+.I width
+and
+.I height
+(if
+.I height
+is missing or zero then it is scaled proportionally).
+.
+If
+.I alignment
+is
+.B \-L
+the drawing is left-aligned.
+.
+If it is
+.B \-C
+or
+.B \-R
+a
+.I line-length
+greater than the width of the drawing is required as well.
+.
+If
+.I width
+is specified as zero then the width is scaled in proportion to the
+height.
+.
+.\" .IP
+.\" See
+.\" .BR groff_tmac (@MAN7EXT@)
+.\" for a description of the
+.\" .B PSPIC
+.\" macro which provides a convenient high-level interface for inclusion
+.\" of PostScript graphics.
+.
+.TP
+.B \[rs]X\[aq]pdf: xrev\[aq]
+Toggle the reversal of glyph direction.
+.
+This feature works \[lq]letter by letter\[rq],
+that is,
+each letter in a word is reversed left-to-right,
+not the entire word.
+.
+One application is the reversal of glyphs in the Zapf Dingbats font.
+.
+To restore the normal glyph orientation,
+repeat the command.
+.
+.
+.TP
+.BI "\[rs]X\[aq]pdf: markstart " "/ANN-definition" \[aq]
+.TQ
+.B \[rs]X\[aq]pdf: markend\[aq]
+Macros that support PDF bookmarks use these calls internally to
+start and stop (respectively) the placement of the bookmark's
+.I hot spot;
+the user will have called
+.RB \[lq] .pdfhref\~L \[rq]
+with the text of the hot spot.
+.
+Normally,
+these are never used except from within the
+.I pdfmark
+macros.
+.
+.
+.TP
+.B \[rs]X\[aq]pdf: marksuspend\[aq]
+.TQ
+.B \[rs]X\[aq]pdf: markrestart\[aq]
+If you use a page location trap to produce a header or footer,
+or otherwise interrupt a document's text,
+you need to use these commands if a PDF
+.I hot spot
+crosses a trap boundary;
+otherwise any text output by the trap will be marked as part of the hot
+spot.
+.
+To prevent this error,
+place these device control commands or their corresponding
+convenience macros
+.B \%.pdfmarksuspend
+and
+.B \%.pdfmarkrestart
+at the start and end of the trap macro,
+respectively.
+.
+.
+.TP
+.BI "\[rs]X\[aq]pdf: pagename\~" name \[aq]
+Assign the current page a
+.IR name .
+.
+All documents bear two default names,
+.RB \[oq] top "\[cq] and \[oq]" bottom \[cq].
+.
+The convenience macro for this command is
+.BR \%.pdfpagename .
+.
+.
+.TP
+.BI "\[rs]X'pdf: switchtopage\~" "when name" \[aq]
+Normally each new page is appended to the end of the document,
+this command allows following pages to be inserted at a
+.I \[oq]named\[cq]
+position within the document (see pagename command above).
+.I \[oq]when\[cq]
+can be either
+.RI \[oq] after "\[cq] or \[oq]" before \[cq].
+If it is omitted it defaults to
+.RI \[oq] before \[cq].
+.
+It should be used at the end of the page before you want the switch to
+happen.
+.
+This allows pages such as a TOC to be moved to elsewhere in the
+document,
+but more esoteric uses are possible.
+.
+The convenience macro for this command is
+.BR \%.pdfswitchtopage .
+.
+.
+.TP
+.BI \[rs]X\[aq]pdf:\~transition\~ feature\~\c
+.IB "mode duration dimension motion direction scale bool" \[aq]
+where
+.I feature
+can be either SLIDE or BLOCK.
+When it is SLIDE the transition is used
+when a new slide is introduced to the screen,
+if BLOCK then this transition is used for the individual blocks which
+make up the slide.
+.
+.
+.IP
+.I mode
+is the transition type between slides:-
+.RS
+.IP
+.B Split
+- Two lines sweep across the screen, revealing the new page.
+The lines
+may be either horizontal or vertical and may move inward from the
+edges of the page or outward from the center, as specified by the
+.I dimension
+and
+.I motion
+entries, respectively.
+.br
+.B Blinds
+- Multiple lines, evenly spaced across the screen, synchronously
+sweep in the same direction to reveal the new page.
+The lines may be
+either horizontal or vertical, as specified by the
+.I dimension
+entry.
+Horizontal
+lines move downward; vertical lines move to the right.
+.br
+.B Box
+- A rectangular box sweeps inward from the edges of the page or
+outward from the center, as specified by the
+.I motion
+entry, revealing the new page.
+.br
+.B Wipe
+- A single line sweeps across the screen from one edge to the other in
+the direction specified by the
+.I direction
+entry, revealing the new page.
+.br
+.B Dissolve
+- The old page dissolves gradually to reveal the new one.
+.br
+.B Glitter
+- Similar to Dissolve,
+except that the effect sweeps across the page in a wide band moving from
+one side of the screen to the other in the direction specified by the
+.I direction
+entry.
+.br
+.B R
+- The new page simply replaces the old one with no special transition
+effect; the
+.I direction
+entry shall be ignored.
+.br
+.B Fly
+- (PDF 1.5) Changes are flown out or in (as specified by
+.IR motion ),
+in the
+direction specified by
+.IR direction ,
+to or from a location that is offscreen except
+when
+.I direction
+is
+.BR None .
+.br
+.B Push
+- (PDF 1.5) The old page slides off the screen while the new page
+slides in, pushing the old page out in the direction specified by
+.IR direction .
+.br
+.B Cover
+- (PDF 1.5) The new page slides on to the screen in the direction
+specified by
+.IR direction ,
+covering the old page.
+.br
+.B Uncover
+- (PDF 1.5) The old page slides off the screen in the direction
+specified by
+.IR direction ,
+uncovering the new page in the direction
+specified by
+.IR direction .
+.br
+.B Fade
+- (PDF 1.5) The new page gradually becomes visible through the
+old one.
+.LP
+.RE
+.IP
+.I duration
+is the length of the transition in seconds (default 1).
+.LP
+.IP
+.I dimension
+(Optional;
+.BR Split " and " Blinds
+transition styles only) The dimension in which the
+specified transition effect shall occur:
+.B H
+Horizontal, or
+.B V
+Vertical.
+.LP
+.IP
+.I motion
+(Optional;
+.BR Split ,
+.BR Box " and " Fly
+transition styles only) The direction of motion for
+the specified transition effect:
+.B I
+Inward from the edges of the page, or
+.B O
+Outward from the center of the page.
+.LP
+.IP
+.I direction
+(Optional;
+.BR Wipe ,
+.BR Glitter ,
+.BR Fly ,
+.BR Cover ,
+.BR Uncover " and " Push
+transition styles only)
+The direction in which the specified transition effect shall moves,
+expressed in degrees counterclockwise starting from a left-to-right
+direction.
+If the value is a number, it shall be one of:
+.B 0
+= Left to right,
+.B 90
+= Bottom to top (Wipe only),
+.B 180
+= Right to left (Wipe only),
+.B 270
+= Top to bottom,
+.B 315
+= Top-left to bottom-right (Glitter only)
+The value can be
+.BR None ,
+which is relevant only for the
+.B Fly
+transition when the value of
+.I scale
+is not 1.0.
+.LP
+.IP
+.I scale
+(Optional; PDF 1.5;
+.B Fly
+transition style only) The starting or ending scale at
+which the changes shall be drawn.
+If
+.I motion
+specifies an inward transition, the scale
+of the changes drawn shall progress from
+.I scale
+to 1.0 over the course of the
+transition.
+If
+.I motion
+specifies an outward transition, the scale of the changes drawn
+shall progress from 1.0 to
+.I scale
+over the course of the transition
+.LP
+.IP
+.I bool
+(Optional; PDF 1.5;
+.B Fly
+transition style only) If
+.BR true ,
+the area that shall be flown
+in is rectangular and opaque.
+.LP
+.IP
+This command can be used by calling the macro
+.B .pdftransition
+using the parameters described above.
+Any of the parameters may be
+replaced with a "." which signifies the parameter retains its
+previous value, also any trailing missing parameters are ignored.
+.LP
+.IP
+.B Note:
+not all PDF Readers support any or all these transitions.
+.LP
+.
+.
+.TP
+.BI "\eX\[aq]pdf: background\~" "cmd left top right bottom weight" \[aq]
+.TQ
+.B "\eX\[aq]pdf: background off\[aq]"
+.TQ
+.BI "\eX\[aq]pdf: background footnote\~" bottom \[aq]
+produces a background rectangle on the page,
+where
+.RS
+.TP
+.I cmd
+is the command,
+which can be any of
+.RB \[lq] page | fill | box \[rq]
+in combination.
+.
+Thus,
+.RB \[lq] pagefill \[rq]
+would draw a rectangle which covers the whole current page size
+(in which case the rest of the parameters can be omitted because the box
+dimensions are taken from the current media size).
+.
+.RB \[lq] boxfill \[rq],
+on the other hand,
+requires the given dimensions to place the box.
+.
+Including
+.RB \[lq] fill \[rq]
+in the command will paint the rectangle with the current fill colour
+(as with
+.BR \[rs]M[] )
+and including
+.RB \[lq] box \[rq]
+will give the rectangle a border in the current stroke colour
+(as with
+.BR \[rs]m[] ).
+.
+.
+.IP
+.I cmd
+may also be
+.RB \[lq] off \[rq]
+on its own,
+which will terminate drawing the current box.
+.
+If you have specified a page colour with
+.RB \[lq] pagefill \[rq],
+it is always the first box in the stack,
+and if you specify it again,
+it will replace the first entry.
+.
+Be aware that the
+.RB \[lq] pagefill \[rq]
+box renders the page opaque,
+so tools that \[lq]watermark\[rq] PDF pages are unlikely to be
+successful.
+.
+To return the background to transparent,
+issue an
+.RB \[lq] off \[rq]
+command with no other boxes open.
+.
+.
+.IP
+Finally,
+.I cmd
+may be
+.RB \[lq] footnote \[rq]
+followed by a new value for
+.IR bottom ,
+which will be used for all open boxes on the current page.
+This is to allow room for footnote areas that grow while a page is
+processed
+(to accommodate multiple footnotes,
+for instance).
+.
+(If the value is negative,
+it is used as an offset from the bottom of the page.)
+.
+.
+.TP
+.I left
+.TQ
+.I top
+.TQ
+.I right
+.TQ
+.I bottom
+are the coordinates of the box.
+.
+The
+.I top
+and
+.I bottom
+coordinates are the minimum and maximum for the box,
+since the actual start of the box is
+.IR groff 's
+drawing position when you issue the command,
+and the bottom of the box is the point where you turn the box
+.RB \[lq] off \[rq].
+.
+The top and bottom coordinates are used only if the box drawing extends
+onto the next page;
+ordinarily,
+they would be set to the header and footer margins.
+.
+.
+.TP
+.I weight
+provides the line width for the border if
+.RB \[lq] box \[rq]
+is included in the command.
+.
+.
+.P
+The convenience macro for this escape sequence is
+.BR .pdfbackground .
+.
+An
+.I sboxes
+macro file is also available;
+see
+.MR groff_tmac @MAN5EXT@ .
+.RE
+.
+.
+.\" ====================================================================
+.SS Macros
+.\" ====================================================================
+.
+.IR gropdf 's
+support macros in
+.I pdf\.tmac
+define the convenience macros described above.
+.
+Some features have no direct device control command counterpart.
+.
+.
+.\" pdfhref
+.
+.
+.TP
+.BI ".pdfinfo /" "field content"\~\c
+\&.\|.\|.
+Define PDF metadata.
+.
+.I field
+may be be one of
+.BR Title ,
+.BR Author ,
+.BR Subject ,
+.BR Keywords ,
+or another datum supported by the PDF standard or your reader.
+.
+.I field
+must be prefixed with a slash.
+.
+.
+.\" ====================================================================
+.SS "Importing graphics"
+.\" ====================================================================
+.
+.I gropdf
+supports only the inclusion of other PDF files for inline images.
+.
+Such a PDF file may,
+however,
+contain any of the graphic formats supported by
+the PDF standard,
+such as JPEG/JFIF,
+PNG,
+and GIF.
+.
+Any application that outputs PDF can thus be used to prepare files for
+embedding in documents processed by
+.I groff
+and
+.IR gropdf .
+.
+.
+.P
+The PDF file you wish to insert must be a single page and the drawing
+must just fit inside the media size of the PDF file.
+.
+In
+.MR inkscape 1
+or
+.MR gimp 1 ,
+for example,
+make sure the canvas size just fits the image.
+.
+.
+.P
+The PDF parser
+.I gropdf
+implements has not been rigorously tested with all applications that
+produce PDF.
+.
+If you find a single-page PDF which fails to import properly,
+try processing it with the
+.MR pdftk 1
+program.
+.
+.
+.RS
+.EX
+pdftk\~\c
+.I existing-file\~\c
+output\~\c
+.I new-file
+.EE
+.RE
+.
+You may find that
+.I new-file
+imports successfully.
+.
+.
+.\" ====================================================================
+.SS "TrueType and other font formats"
+.\" ====================================================================
+.
+.I gropdf
+does not yet support any font formats besides Adobe Type 1
+(PFA or PFB).
+.
+.
+.\" ====================================================================
+.SH "Font installation"
+.\" ====================================================================
+.
+The following is a step-by-step font installation guide for
+.I gropdf.
+.
+.
+.IP \[bu] 2n
+Convert your font to something
+.I groff
+understands.
+.
+This is a PostScript Type\~1 font in PFA or PFB format,
+together with an AFM file.
+.
+A PFA file begins as follows.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+%!PS\-AdobeFont\-1.0:
+.EE
+.RE \" but only one to get back to it
+.
+A PFB file contains this string as well,
+preceded by some non-printing bytes.
+.
+In the following steps,
+we will consider the use of CTAN's
+.UR https://\:ctan.org/\:tex\-archive/\:fonts/\:brushscr
+BrushScriptX-Italic
+.UE
+font in PFA format.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Convert the AFM file to a
+.I groff
+font description file with the
+.MR afmtodit @MAN1EXT@
+program.
+.
+For instance,
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+$ \c
+.B afmtodit BrushScriptX\-Italic.afm text.map BSI
+.EE
+.RE \" but only one to get back to it
+.
+converts the Adobe Font Metric file
+.I BrushScriptX\-Italic.afm
+to the
+.I groff
+font description file
+.IR BSI .
+.RE \" now restore left margin
+.
+.
+.IP
+If you have a font family which provides regular upright (roman),
+bold,
+italic,
+and
+bold-italic styles,
+(where \[lq]italic\[rq] may be \[lq]oblique\[rq] or \[lq]slanted\[rq]),
+we recommend using
+.BR R ,
+.BR B ,
+.BR I ,
+and
+.BR BI ,
+respectively,
+as suffixes to the
+.I groff
+font family name to enable
+.IR groff 's
+font family and style selection features.
+.
+An example is
+.IR groff 's
+built-in support for Times:
+the font family
+name is abbreviated as
+.BR T ,
+and the
+.I groff
+font names are therefore
+.BR TR ,
+.BR TB ,
+.BR TI ,
+and
+.BR TBI .
+.
+In our example,
+however,
+the BrushScriptX font is available in a single style only,
+italic.
+.
+.
+.IP \[bu]
+Install the
+.I groff
+font description file(s) in a
+.I devpdf
+subdirectory in the search path that
+.I groff
+uses for device and font file descriptions.
+.
+See the
+.I GROFF_FONT_PATH
+entry in section \[lq]Environment\[rq] of
+.MR @g@troff @MAN1EXT@
+for the current value of the font search path.
+.
+While
+.I groff
+doesn't directly use AFM files,
+it is a good idea to store them alongside its font description files.
+.
+.
+.IP \[bu]
+Register fonts in the
+.I devpdf/download
+file so they can be located for embedding in PDF files
+.I gropdf
+generates.
+.
+Only the first
+.I download
+file encountered in the font search path is read.
+.
+If in doubt,
+copy the default
+.I download
+file
+(see section \[lq]Files\[rq] below)
+to the first directory in the font search path and add your fonts there.
+.
+The PostScript font name used by
+.I gropdf
+is stored in the
+.B internalname
+field in the
+.I groff
+font description file.
+.
+(This name does not necessarily resemble the font's file name.)
+.
+If the font in our example had originated from a foundry named
+.BR Z ,
+we would add the following line to
+.IR download .
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+Z\[->]BrushScriptX\-Italic\[->]BrushScriptX\-Italic.pfa
+.EE
+.RE \" but only one to get back to it
+.
+A tab character,
+depicted as \[->],
+separates the fields.
+.
+The default foundry has no name:
+its field is empty and
+entries corresponding to it start with a tab character,
+as will the one in our example.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Test the selection and embedding of the new font.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+printf "\[rs]\[rs]f[BSI]Hello, world!\[rs]n" \
+| groff \-T pdf \-P \-e >hello.pdf
+see hello.pdf
+.EE
+.RE
+.RE
+.
+.
+.br
+.ne 5v
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+If,
+in the
+.I download
+file,
+the font file has been specified with a full path,
+no directories are searched.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROPDF_NOSLIDE
+If set and evaluates to a true value
+(to Perl),
+.\" XXX: The above is inconsistent with the way grotty(1) handles
+.\" "GROFF_NO_SGR".
+.I gropdf
+ignores commands specific to presentation PDFs,
+producing a normal PDF instead.
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using Perl's
+.I \%localtime()
+function and recorded in a PDF comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devpdf/\:DESC
+describes the
+.B pdf
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devpdf/ F
+describes the font known
+.RI as\~ F
+on device
+.BR pdf .
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devpdf/\:U\- F
+describes the font
+from the URW foundry
+(versus the Adobe default)
+known
+.RI as\~ F
+on device
+.BR pdf .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devpdf/\%download
+lists fonts available for embedding within the PDF document
+(by analogy to the
+.B ps
+device's downloadable font support).
+.
+.
+.\" XXX: Why are we shipping this but not BuildFoundries.pl?
+.TP
+.I @FONTDIR@/\:\%devpdf/\%Foundry
+is a data file used by the
+.I groff
+build system to locate PostScript Type\~1 fonts.
+.
+.
+.TP
+.I @FONTDIR@/\:\%devpdf/\:enc/\:\%text\:.enc
+describes the encoding scheme used by most PostScript Type\~1 fonts;
+the
+.B \%encoding
+directive of
+font description files for the
+.B pdf
+device refers to it.
+.
+.
+.TP
+.I @MACRODIR@/\:pdf\:.tmac
+defines macros for use with the
+.B pdf
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B pdf
+output device is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:\%pdfpic\:.tmac
+defines the
+.B PDFPIC
+macro for embedding images in a document;
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+It is automatically loaded by
+.I troffrc.
+.\"
+.\"
+.\" .TP
+.\" .B @MACRODIR@/pspic.tmac
+.\" Definition of
+.\" .B PSPIC
+.\" macro,
+.\" automatically loaded by
+.\" .BR ps.tmac .
+.\" .
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I gropdf
+was written and is maintained by
+.MT deri@\:chuzzlewit\:.myzen\:.co\:.uk
+Deri James
+.ME .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.TP
+.I @DOCDIR@/\:\%sboxes/\:\%msboxes\:.ms
+.TQ
+.I @DOCDIR@/\:\%sboxes/\:\%msboxes\:.pdf
+\[lq]Using PDF boxes with
+.I groff
+and the
+.I ms
+macros\[rq],
+by Deri James.
+.
+.
+.TP
+.I present.tmac
+is part of
+.UR https://\:bob\:.diertens\:.org/\:corner/\:useful/\:gpresent/
+.I gpresent
+.UE ,
+a software package by Bob Diertens that works with
+.I groff
+to produce presentations
+(\[lq]foils\[rq],
+or \[lq]slide decks\[rq]).
+.
+.
+.P
+.MR afmtodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_out @MAN5EXT@
+.\" Not actually referenced in above discussion.
+.\" .BR \%pfbtops (@MAN1EXT@),
+.\" .BR \%groff_tmac (@MAN5EXT@),
+.
+.
+.\" Clean up.
+.rm FT
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_gropdf_1_man_C]
+.do rr *groff_gropdf_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/gropdf/gropdf.am b/src/devices/gropdf/gropdf.am
new file mode 100644
index 0000000..3dbd865
--- /dev/null
+++ b/src/devices/gropdf/gropdf.am
@@ -0,0 +1,58 @@
+# Copyright (C) 2011-2020 Free Software Foundation, Inc.
+# Written by Deri James <deri@chuzzlewit.myzen.co.uk>
+# Automake migration by Bertrand Garrigues
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+gropdf_dir = $(top_srcdir)/src/devices/gropdf
+
+bin_SCRIPTS += gropdf pdfmom
+EXTRA_DIST += \
+ src/devices/gropdf/TODO \
+ src/devices/gropdf/gropdf.pl \
+ src/devices/gropdf/pdfmom.pl \
+ src/devices/gropdf/gropdf.1.man \
+ src/devices/gropdf/pdfmom.1.man
+
+man1_MANS += \
+ src/devices/gropdf/gropdf.1 \
+ src/devices/gropdf/pdfmom.1
+
+gropdf: $(gropdf_dir)/gropdf.pl $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)$(RM) $@ \
+ && sed -f $(SH_DEPS_SED_SCRIPT) \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ -e "s|[@]PERL[@]|$(PERL)|" \
+ -e "s|[@]GROFF_FONT_DIR[@]|$(fontpath)|" \
+ -e "s|[@]RT_SEP[@]|$(RT_SEP)|" $(gropdf_dir)/gropdf.pl \
+ >$@ \
+ && chmod +x $@
+
+pdfmom: $(gropdf_dir)/pdfmom.pl $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)$(RM) $@ \
+ && sed -f $(SH_DEPS_SED_SCRIPT) \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ -e "s|[@]RT_SEP[@]|$(RT_SEP)|" \
+ -e "s|[@]PERL[@]|$(PERL)|" $(gropdf_dir)/pdfmom.pl \
+ >$@ \
+ && chmod +x $@
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/gropdf/gropdf.pl b/src/devices/gropdf/gropdf.pl
new file mode 100644
index 0000000..c65a105
--- /dev/null
+++ b/src/devices/gropdf/gropdf.pl
@@ -0,0 +1,3928 @@
+#!@PERL@
+#
+# gropdf : PDF post processor for groff
+#
+# Copyright (C) 2011-2020 Free Software Foundation, Inc.
+# Written by Deri James <deri@chuzzlewit.myzen.co.uk>
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use Getopt::Long qw(:config bundling);
+
+use constant
+{
+ WIDTH => 0,
+ CHRCODE => 1,
+ PSNAME => 2,
+ ASSIGNED => 3,
+ USED => 4,
+};
+
+my $prog=$0;
+
+my $gotzlib=0;
+
+my $rc = eval
+{
+ require Compress::Zlib;
+ Compress::Zlib->import();
+ 1;
+};
+
+if($rc)
+{
+ $gotzlib=1;
+}
+else
+{
+ Warn("Perl module 'Compress::Zlib' not available; cannot compress"
+ . " this PDF");
+}
+
+my %cfg;
+
+$cfg{GROFF_VERSION}='@VERSION@';
+$cfg{GROFF_FONT_PATH}='@GROFF_FONT_DIR@';
+$cfg{RT_SEP}='@RT_SEP@';
+binmode(STDOUT);
+
+my @obj; # Array of PDF objects
+my $objct=0; # Count of Objects
+my $fct=0; # Output count
+my %fnt; # Used fonts
+my $lct=0; # Input Line Count
+my $src_name='';
+my %env; # Current environment
+my %fontlst; # Fonts Loaded
+my $rot=0; # Portrait
+my %desc; # Contents of DESC
+my %download; # Contents of downlopad file
+my $pages; # Pointer to /Pages object
+my $devnm='devpdf';
+my $cpage; # Pointer to current pages
+my $cpageno=0; # Object no of current page
+my $cat; # Pointer to catalogue
+my $dests; # Pointer to Dests
+my @mediabox=(0,0,595,842);
+my @defaultmb=(0,0,595,842);
+my $stream=''; # Current Text/Graphics stream
+my $cftsz=10; # Current font sz
+my $cft; # Current Font
+my $lwidth=1; # current linewidth
+my $linecap=1;
+my $linejoin=1;
+my $textcol=''; # Current groff text
+my $fillcol=''; # Current groff fill
+my $curfill=''; # Current PDF fill
+my $strkcol='';
+my $curstrk='';
+my @lin=(); # Array holding current line of text
+my @ahead=(); # Buffer used to hol the next line
+my $mode='g'; # Graphic (g) or Text (t) mode;
+my $xpos=0; # Current X position
+my $ypos=0; # Current Y position
+my $tmxpos=0;
+my $kernadjust=0;
+my $curkern=0;
+my $widtbl; # Pointer to width table for current font size
+my $origwidtbl; # Pointer to width table
+my $krntbl; # Pointer to kern table
+my $matrix="1 0 0 1";
+my $whtsz; # Current width of a space
+my $poschg=0; # V/H pending
+my $fontchg=0; # font change pending
+my $tnum=2; # flatness of B-Spline curve
+my $tden=3; # flatness of B-Spline curve
+my $linewidth=40;
+my $w_flg=0;
+my $nomove=0;
+my $pendmv=0;
+my $gotT=0;
+my $suppress=0; # Suppress processing?
+my %incfil; # Included Files
+my @outlev=([0,undef,0,0]); # Structure pdfmark /OUT entries
+my $curoutlev=\@outlev;
+my $curoutlevno=0; # Growth point for @curoutlev
+my $Foundry='';
+my $xrev=0; # Reverse x direction of font
+my $matrixchg=0;
+my $wt=-1;
+my $thislev=1;
+my $mark=undef;
+my $suspendmark=undef;
+my $boxmax=0;
+my %missing; # fonts in download files which are not found/readable
+
+
+
+my $n_flg=1;
+my $pginsert=-1; # Growth point for kids array
+my %pgnames; # 'names' of pages for switchtopage
+my @outlines=(); # State of Bookmark Outlines at end of each page
+my $custompaper=0; # Has there been an X papersize
+my $textenccmap=''; # CMap for groff text.enc encoding
+my @XOstream=();
+my @PageAnnots={};
+my $noslide=0;
+my $transition={PAGE => {Type => '/Trans', S => '', D => 1, Dm => '/H', M => '/I', Di => 0, SS => 1.0, B => 0},
+ BLOCK => {Type => '/Trans', S => '', D => 1, Dm => '/H', M => '/I', Di => 0, SS => 1.0, B => 0}};
+my $firstpause=0;
+my $present=0;
+my @bgstack; # Stack of background boxes
+my $bgbox=''; # Draw commands for boxes on this page
+
+$noslide=1 if exists($ENV{GROPDF_NOSLIDE}) and $ENV{GROPDF_NOSLIDE};
+
+my %ppsz=(
+ 'ledger'=>[1224,792],
+ 'legal'=>[612,1008],
+ 'letter'=>[612,792],
+ 'a0'=>[2384,3370],
+ 'a1'=>[1684,2384],
+ 'a2'=>[1191,1684],
+ 'a3'=>[842,1191],
+ 'a4'=>[595,842],
+ 'a5'=>[420,595],
+ 'a6'=>[297,420],
+ 'a7'=>[210,297],
+ 'a8'=>[148,210],
+ 'a9'=>[105,148],
+ 'a10'=>[73,105],
+ 'b0'=>[2835,4008],
+ 'b1'=>[2004,2835],
+ 'b2'=>[1417,2004],
+ 'b3'=>[1001,1417],
+ 'b4'=>[709,1001],
+ 'b5'=>[499,709],
+ 'b6'=>[354,499],
+ 'c0'=>[2599,3677],
+ 'c1'=>[1837,2599],
+ 'c2'=>[1298,1837],
+ 'c3'=>[918,1298],
+ 'c4'=>[649,918],
+ 'c5'=>[459,649],
+ 'c6'=>[323,459],
+ 'com10'=>[297,684],
+);
+
+my $ucmap=<<'EOF';
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<< /Registry (Adobe)
+/Ordering (UCS)
+/Supplement 0
+>> def
+/CMapName /Adobe-Identity-UCS def
+/CMapType 2 def
+1 begincodespacerange
+<0000> <FFFF>
+endcodespacerange
+2 beginbfrange
+<008b> <008f> [<00660066> <00660069> <0066006c> <006600660069> <00660066006C>]
+<00ad> <00ad> <002d>
+endbfrange
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+EOF
+
+sub usage
+{
+ my $stream = *STDOUT;
+ my $had_error = shift;
+ $stream = *STDERR if $had_error;
+ print $stream
+"usage: $prog [-dels] [-F font-directory] [-I inclusion-directory]" .
+" [-p paper-format] [-u [cmap-file]] [-y foundry] [file ...]\n" .
+"usage: $prog {-v | --version}\n" .
+"usage: $prog --help\n";
+ if (!$had_error)
+ {
+ print $stream "\n" .
+"Translate the output of troff(1) into Portable Document Format.\n" .
+"See the gropdf(1) manual page.\n";
+ }
+ exit($had_error);
+}
+
+my $fd;
+my $frot;
+my $fpsz;
+my $embedall=0;
+my $debug=0;
+my $want_help=0;
+my $version=0;
+my $stats=0;
+my $unicodemap;
+my @idirs;
+
+if (!GetOptions('F=s' => \$fd, 'I=s' => \@idirs, 'l' => \$frot,
+ 'p=s' => \$fpsz, 'd!' => \$debug, 'help' => \$want_help,
+ 'v' => \$version, 'version' => \$version,
+ 'e' => \$embedall, 'y=s' => \$Foundry, 's' => \$stats,
+ 'u:s' => \$unicodemap))
+{
+ &usage(1);
+}
+
+unshift(@idirs,'.');
+
+&usage(0) if ($want_help);
+
+if ($version)
+{
+ print "GNU gropdf (groff) version $cfg{GROFF_VERSION}\n";
+ exit;
+}
+
+if (defined($unicodemap))
+{
+ if ($unicodemap eq '')
+ {
+ $ucmap='';
+ }
+ elsif (-r $unicodemap)
+ {
+ local $/;
+ open(F,"<$unicodemap") or Die("failed to open '$unicodemap'");
+ ($ucmap)=(<F>);
+ close(F);
+ }
+ else
+ {
+ Warn("failed to find '$unicodemap'; ignoring");
+ }
+}
+
+# Search for 'font directory': paths in -f opt, shell var
+# GROFF_FONT_PATH, default paths
+
+my $fontdir=$cfg{GROFF_FONT_PATH};
+$fontdir=$ENV{GROFF_FONT_PATH}.$cfg{RT_SEP}.$fontdir if exists($ENV{GROFF_FONT_PATH});
+$fontdir=$fd.$cfg{RT_SEP}.$fontdir if defined($fd);
+
+$rot=90 if $frot;
+$matrix="0 1 -1 0" if $frot;
+
+LoadDownload();
+LoadDesc();
+
+my $unitwidth=$desc{unitwidth};
+
+$env{FontHT}=0;
+$env{FontSlant}=0;
+MakeMatrix();
+
+my $possiblesizes = $desc{papersize};
+$possiblesizes = $fpsz if $fpsz;
+my $papersz;
+for $papersz ( split(" ", lc($possiblesizes).' #duff#') )
+{
+ # No valid papersize found?
+ if ($papersz eq '#duff#')
+ {
+ Warn("ignoring unrecognized paper format(s) '$possiblesizes'");
+ last;
+ }
+
+ # Check for "/etc/papersize"
+ elsif (substr($papersz,0,1) eq '/' and -r $papersz)
+ {
+ if (open(P,"<$papersz"))
+ {
+ while (<P>)
+ {
+ chomp;
+ s/# .*//;
+ next if $_ eq '';
+ $papersz=lc($_);
+ last;
+ }
+ close(P);
+ }
+ }
+
+ # Allow height,width specified directly in centimeters, inches, or points.
+ if ($papersz=~m/([\d.]+)([cipP]),([\d.]+)([cipP])/)
+ {
+ @defaultmb=@mediabox=(0,0,ToPoints($3,$4),ToPoints($1,$2));
+ last;
+ }
+ # Look $papersz up as a name such as "a4" or "letter".
+ elsif (exists($ppsz{$papersz}))
+ {
+ @defaultmb=@mediabox=(0,0,$ppsz{$papersz}->[0],$ppsz{$papersz}->[1]);
+ last;
+ }
+ # Check for a landscape version
+ elsif (substr($papersz,-1) eq 'l' and exists($ppsz{substr($papersz,0,-1)}))
+ {
+ # Note 'legal' ends in 'l' but will be caught above
+ @defaultmb=@mediabox=(0,0,$ppsz{substr($papersz,0,-1)}->[1],$ppsz{substr($papersz,0,-1)}->[0]);
+ last;
+ }
+
+ # If we get here, $papersz was invalid, so try the next one.
+}
+
+my (@dt)=localtime($ENV{SOURCE_DATE_EPOCH} || time);
+my $dt=PDFDate(\@dt);
+
+my %info=('Creator' => "(groff version $cfg{GROFF_VERSION})",
+ 'Producer' => "(gropdf version $cfg{GROFF_VERSION})",
+ 'ModDate' => "($dt)",
+ 'CreationDate' => "($dt)");
+map { $_="< ".$_."\0" } @ARGV;
+
+while (<>)
+{
+ chomp;
+ s/\r$//;
+ $lct++;
+
+ do # The ahead buffer behaves like 'ungetc'
+ {{
+ if (scalar(@ahead))
+ {
+ $_=shift(@ahead);
+ }
+
+
+ my $cmd=substr($_,0,1);
+ next if $cmd eq '#'; # just a comment
+ my $lin=substr($_,1);
+
+ while ($cmd eq 'w')
+ {
+ $cmd=substr($lin,0,1);
+ $lin=substr($lin,1);
+ $w_flg=1 if $gotT;
+ }
+
+ $lin=~s/^\s+//;
+# $lin=~s/\s#.*?$//; # remove comment
+ $stream.="\% $_\n" if $debug;
+
+ do_x($lin),next if ($cmd eq 'x');
+ next if $suppress;
+ do_p($lin),next if ($cmd eq 'p');
+ do_f($lin),next if ($cmd eq 'f');
+ do_s($lin),next if ($cmd eq 's');
+ do_m($lin),next if ($cmd eq 'm');
+ do_D($lin),next if ($cmd eq 'D');
+ do_V($lin),next if ($cmd eq 'V');
+ do_v($lin),next if ($cmd eq 'v');
+ do_t($lin),next if ($cmd eq 't');
+ do_u($lin),next if ($cmd eq 'u');
+ do_C($lin),next if ($cmd eq 'C');
+ do_c($lin),next if ($cmd eq 'c');
+ do_N($lin),next if ($cmd eq 'N');
+ do_h($lin),next if ($cmd eq 'h');
+ do_H($lin),next if ($cmd eq 'H');
+ do_n($lin),next if ($cmd eq 'n');
+
+ my $tmp=scalar(@ahead);
+ }} until scalar(@ahead) == 0;
+
+}
+
+exit 0 if $lct==0;
+
+if ($cpageno > 0)
+{
+ my $trans='BLOCK';
+
+ $trans='PAGE' if $firstpause;
+
+ if (scalar(@XOstream))
+ {
+ MakeXO() if $stream;
+ $stream=join("\n",@XOstream)."\n";
+ }
+
+ my %t=%{$transition->{$trans}};
+ $cpage->{MediaBox}=\@mediabox if $custompaper;
+ $cpage->{Trans}=FixTrans(\%t) if $t{S};
+
+ if ($#PageAnnots >= 0)
+ {
+ @{$cpage->{Annots}}=@PageAnnots;
+ }
+
+ if ($#bgstack > -1 or $bgbox)
+ {
+ my $box="q 1 0 0 1 0 0 cm ";
+
+ foreach my $bg (@bgstack)
+ {
+ # 0=$bgtype # 1=stroke 2=fill. 4=page
+ # 1=$strkcol
+ # 2=$fillcol
+ # 3=(Left,Top,Right,bottom,LineWeight)
+ # 4=Start ypos
+ # 5=Endypos
+ # 6=Line Weight
+
+ my $pg=$bg->[3] || \@mediabox;
+
+ $bg->[5]=$pg->[3]; # box is continuing to next page
+ $box.=DrawBox($bg);
+ $bg->[4]=$pg->[1]; # will continue from page top
+ }
+
+ $stream=$box.$bgbox."Q\n".$stream;
+ $bgbox='';
+ }
+
+ $boxmax=0;
+ PutObj($cpageno);
+ OutStream($cpageno+1);
+}
+
+$cat->{PageMode}='/FullScreen' if $present;
+
+PutOutlines(\@outlev);
+
+PutObj(1);
+
+my $info=BuildObj(++$objct,\%info);
+
+PutObj($objct);
+
+foreach my $fontno (sort keys %fontlst)
+{
+ my $o=$fontlst{$fontno}->{FNT};
+
+ foreach my $ch (@{$o->{NO}})
+ {
+ my $psname=$o->{NAM}->{$ch->[1]}->[PSNAME] || '/.notdef';
+ my $wid=$o->{NAM}->{$ch->[1]}->[WIDTH] || 0;
+
+ push(@{$o->{DIFF}},$psname);
+ push(@{$o->{WIDTH}},$wid);
+ last if $#{$o->{DIFF}} >= 256;
+ }
+ unshift(@{$o->{DIFF}},0);
+ my $p=GetObj($fontlst{$fontno}->{OBJ});
+
+ if (exists($p->{LastChar}) and $p->{LastChar} > 255)
+ {
+ $p->{LastChar} = 255;
+ splice(@{$o->{DIFF}},257);
+ splice(@{$o->{WIDTH}},257);
+ }
+}
+
+foreach my $o (3..$objct)
+{
+ PutObj($o) if (!exists($obj[$o]->{XREF}));
+}
+
+#my $encrypt=BuildObj(++$objct,{'Filter' => '/Standard', 'V' => 1, 'R' => 2, 'P' => 252});
+#PutObj($objct);
+PutObj(2);
+
+my $xrefct=$fct;
+
+$objct+=1;
+print "xref\n0 $objct\n0000000000 65535 f \n";
+
+foreach my $xr (@obj)
+{
+ next if !defined($xr);
+ printf("%010d 00000 n \n",$xr->{XREF});
+}
+
+print "trailer\n<<\n/Info $info\n/Root 1 0 R\n/Size $objct\n>>\nstartxref\n$fct\n\%\%EOF\n";
+print "\% Pages=$pages->{Count}\n" if $stats;
+
+
+sub MakeMatrix
+{
+ my $fontxrev=shift||0;
+ my @mat=($frot)?(0,1,-1,0):(1,0,0,1);
+
+ if (!$frot)
+ {
+ if ($env{FontHT} != 0)
+ {
+ $mat[3]=sprintf('%.3f',$env{FontHT}/$cftsz);
+ }
+
+ if ($env{FontSlant} != 0)
+ {
+ my $slant=$env{FontSlant};
+ $slant*=$env{FontHT}/$cftsz if $env{FontHT} != 0;
+ my $ang=rad($slant);
+
+ $mat[2]=sprintf('%.3f',sin($ang)/cos($ang));
+ }
+
+ if ($fontxrev)
+ {
+ $mat[0]=-$mat[0];
+ }
+ }
+
+ $matrix=join(' ',@mat);
+ $matrixchg=1;
+}
+
+sub PutOutlines
+{
+ my $o=shift;
+ my $outlines;
+
+ if ($#{$o} > 0)
+ {
+ # We've got Outlines to deal with
+ my $openct=$curoutlev->[0]->[2];
+
+ while ($thislev-- > 1)
+ {
+ my $nxtoutlev=$curoutlev->[0]->[1];
+ $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1;
+ $openct=0 if $nxtoutlev->[0]->[3]==-1;
+ $curoutlev=$nxtoutlev;
+ }
+
+ $cat->{Outlines}=BuildObj(++$objct,{'Count' => abs($o->[0]->[0])+$o->[0]->[2]});
+ $outlines=$obj[$objct]->{DATA};
+ }
+ else
+ {
+ return;
+ }
+
+ SetOutObj($o);
+
+ $outlines->{First}=$o->[1]->[2];
+ $outlines->{Last}=$o->[$#{$o}]->[2];
+
+ LinkOutObj($o,$cat->{Outlines});
+}
+
+sub SetOutObj
+{
+ my $o=shift;
+
+ for my $j (1..$#{$o})
+ {
+ my $ono=BuildObj(++$objct,$o->[$j]->[0]);
+ $o->[$j]->[2]=$ono;
+
+ SetOutObj($o->[$j]->[1]) if $#{$o->[$j]->[1]} > -1;
+ }
+}
+
+sub LinkOutObj
+{
+ my $o=shift;
+ my $parent=shift;
+
+ for my $j (1..$#{$o})
+ {
+ my $op=GetObj($o->[$j]->[2]);
+
+ $op->{Next}=$o->[$j+1]->[2] if ($j < $#{$o});
+ $op->{Prev}=$o->[$j-1]->[2] if ($j > 1);
+ $op->{Parent}=$parent;
+
+ if ($#{$o->[$j]->[1]} > -1)
+ {
+ $op->{Count}=$o->[$j]->[1]->[0]->[2]*$o->[$j]->[1]->[0]->[3];# if exists($op->{Count}) and $op->{Count} > 0;
+ $op->{First}=$o->[$j]->[1]->[1]->[2];
+ $op->{Last}=$o->[$j]->[1]->[$#{$o->[$j]->[1]}]->[2];
+ LinkOutObj($o->[$j]->[1],$o->[$j]->[2]);
+ }
+ }
+}
+
+sub GetObj
+{
+ my $ono=shift;
+ ($ono)=split(' ',$ono);
+ return($obj[$ono]->{DATA});
+}
+
+
+
+sub PDFDate
+{
+ my $dt=shift;
+ return(sprintf("D:%04d%02d%02d%02d%02d%02d%+03d'00'",$dt->[5]+1900,$dt->[4]+1,$dt->[3],$dt->[2],$dt->[1],$dt->[0],( localtime time() + 3600*( 12 - (gmtime)[2] ) )[2] - 12));
+}
+
+sub ToPoints
+{
+ my $num=shift;
+ my $unit=shift;
+
+ if ($unit eq 'i')
+ {
+ return($num*72);
+ }
+ elsif ($unit eq 'c')
+ {
+ return int($num*72/2.54);
+ }
+ elsif ($unit eq 'm') # millimetres
+ {
+ return int($num*72/25.4);
+ }
+ elsif ($unit eq 'p')
+ {
+ return($num);
+ }
+ elsif ($unit eq 'P')
+ {
+ return($num*6);
+ }
+ elsif ($unit eq 'z')
+ {
+ return($num/$unitwidth);
+ }
+ else
+ {
+ Die("invalid scaling unit '$unit'");
+ }
+}
+
+sub LoadDownload
+{
+ my $f;
+ my $found=0;
+
+ my (@dirs)=split($cfg{RT_SEP},$fontdir);
+
+ foreach my $dir (@dirs)
+ {
+ $f=undef;
+ OpenFile(\$f,$dir,"download");
+ next if !defined($f);
+ $found++;
+
+ while (<$f>)
+ {
+ chomp;
+ s/#.*$//;
+ next if $_ eq '';
+ my ($foundry,$name,$file)=split(/\t+/);
+ if (substr($file,0,1) eq '*')
+ {
+ next if !$embedall;
+ $file=substr($file,1);
+ }
+
+ my $pth=$file;
+ $pth=$dir."/$devnm/$file" if substr($file,0,1) ne '/';
+
+ if (!-r $pth)
+ {
+ $missing{"$foundry $name"}="$dir/$devnm";
+ next;
+ }
+
+ $download{"$foundry $name"}=$file if !exists($download{"$foundry $name"});
+ }
+
+ close($f);
+ }
+
+ Die("failed to open 'download' file") if !$found;
+}
+
+sub OpenFile
+{
+ my $f=shift;
+ my $dirs=shift;
+ my $fnm=shift;
+
+ if (substr($fnm,0,1) eq '/' or substr($fnm,1,1) eq ':') # dos
+ {
+ return if -r "$fnm" and open($$f,"<$fnm");
+ }
+
+ my (@dirs)=split($cfg{RT_SEP},$dirs);
+
+ foreach my $dir (@dirs)
+ {
+ last if -r "$dir/$devnm/$fnm" and open($$f,"<$dir/$devnm/$fnm");
+ }
+}
+
+sub LoadDesc
+{
+ my $f;
+
+ OpenFile(\$f,$fontdir,"DESC");
+ Die("failed to open device description file 'DESC'")
+ if !defined($f);
+
+ while (<$f>)
+ {
+ chomp;
+ s/#.*$//;
+ next if $_ eq '';
+ my ($name,$prms)=split(' ',$_,2);
+ $desc{lc($name)}=$prms;
+ }
+
+ close($f);
+
+ foreach my $directive ('unitwidth', 'res', 'sizescale')
+ {
+ Die("device description file 'DESC' missing mandatory directive"
+ . " '$directive'") if !exists($desc{$directive});
+ }
+
+ foreach my $directive ('unitwidth', 'res', 'sizescale')
+ {
+ my $val=$desc{$directive};
+ Die("device description file 'DESC' directive '$directive'"
+ . " value must be positive; got '$val'")
+ if ($val !~ m/^\d+$/ or $val <= 0);
+ }
+
+ if (exists($desc{'hor'}))
+ {
+ my $hor=$desc{'hor'};
+ Die("device horizontal motion quantum must be 1, got '$hor'")
+ if ($hor != 1);
+ }
+
+ if (exists($desc{'vert'}))
+ {
+ my $vert=$desc{'vert'};
+ Die("device vertical motion quantum must be 1, got '$vert'")
+ if ($vert != 1);
+ }
+
+ my ($res,$ss)=($desc{'res'},$desc{'sizescale'});
+ Die("device resolution must be a multiple of 72*sizescale, got"
+ . " '$res' ('sizescale'=$ss)") if (($res % ($ss * 72)) != 0);
+}
+
+sub rad { $_[0]*3.14159/180 }
+
+my $InPicRotate=0;
+
+sub do_x
+{
+ my $l=shift;
+ my ($xcmd,@xprm)=split(' ',$l);
+ $xcmd=substr($xcmd,0,1);
+
+ if ($xcmd eq 'T')
+ {
+ Warn("expecting a PDF pipe (got $xprm[0])")
+ if $xprm[0] ne substr($devnm,3);
+ }
+ elsif ($xcmd eq 'f') # Register Font
+ {
+ $xprm[1]="${Foundry}-$xprm[1]" if $Foundry ne '';
+ LoadFont($xprm[0],$xprm[1]);
+ }
+ elsif ($xcmd eq 'F') # Source File (for errors)
+ {
+ $env{SourceFile}=$xprm[0];
+ }
+ elsif ($xcmd eq 'H') # FontHT
+ {
+ $xprm[0]/=$unitwidth;
+ $xprm[0]=0 if $xprm[0] == $cftsz;
+ $env{FontHT}=$xprm[0];
+ MakeMatrix();
+ }
+ elsif ($xcmd eq 'S') # FontSlant
+ {
+ $env{FontSlant}=$xprm[0];
+ MakeMatrix();
+ }
+ elsif ($xcmd eq 'i') # Initialise
+ {
+ if ($objct == 0)
+ {
+ $objct++;
+ @defaultmb=@mediabox;
+ BuildObj($objct,{'Pages' => BuildObj($objct+1,
+ {'Kids' => [],
+ 'Count' => 0,
+ 'Type' => '/Pages',
+ 'Rotate' => $rot,
+ 'MediaBox' => \@defaultmb,
+ 'Resources' =>
+ {'Font' => {},
+ 'ProcSet' => ['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI']}
+ }
+ ),
+ 'Type' => '/Catalog'});
+
+ $cat=$obj[$objct]->{DATA};
+ $objct++;
+ $pages=$obj[2]->{DATA};
+ Put("%PDF-1.4\n\x25\xe2\xe3\xcf\xd3\n");
+ }
+ }
+ elsif ($xcmd eq 'X')
+ {
+ # There could be extended args
+ do
+ {{
+ LoadAhead(1);
+ if (substr($ahead[0],0,1) eq '+')
+ {
+ $l.="\n".substr($ahead[0],1);
+ shift(@ahead);
+ }
+ }} until $#ahead==0;
+
+ ($xcmd,@xprm)=split(' ',$l);
+ $xcmd=substr($xcmd,0,1);
+
+ if ($xprm[0]=~m/^(.+:)(.+)/)
+ {
+ splice(@xprm,1,0,$2);
+ $xprm[0]=$1;
+ }
+
+ my $par=join(' ',@xprm[1..$#xprm]);
+
+ if ($xprm[0] eq 'ps:')
+ {
+ if ($xprm[1] eq 'invis')
+ {
+ $suppress=1;
+ }
+ elsif ($xprm[1] eq 'endinvis')
+ {
+ $suppress=0;
+ }
+ elsif ($par=~m/exec gsave currentpoint 2 copy translate (.+) rotate neg exch neg exch translate/)
+ {
+ # This is added by gpic to rotate a single object
+
+ my $theta=-rad($1);
+
+ IsGraphic();
+ my ($curangle,$hyp)=RtoP($xpos,GraphY($ypos));
+ my ($x,$y)=PtoR($theta+$curangle,$hyp);
+ my ($tx, $ty) = ($xpos - $x, GraphY($ypos) - $y);
+ if ($frot) {
+ ($tx, $ty) = ($tx * sin($theta) + $ty * -cos($theta),
+ $tx * -cos($theta) + $ty * -sin($theta));
+ }
+ $stream.="q\n".sprintf("%.3f %.3f %.3f %.3f %.3f %.3f cm",cos($theta),sin($theta),-sin($theta),cos($theta),$tx,$ty)."\n";
+ $InPicRotate=1;
+ }
+ elsif ($par=~m/exec grestore/ and $InPicRotate)
+ {
+ IsGraphic();
+ $stream.="Q\n";
+ $InPicRotate=0;
+ }
+ elsif ($par=~m/exec (\d) setlinejoin/)
+ {
+ IsGraphic();
+ $linejoin=$1;
+ $stream.="$linejoin j\n";
+ }
+ elsif ($par=~m/exec (\d) setlinecap/)
+ {
+ IsGraphic();
+ $linecap=$1;
+ $stream.="$linecap J\n";
+ }
+ elsif ($par=~m/exec %%%%PAUSE/i and !$noslide)
+ {
+ my $trans='BLOCK';
+
+ if ($firstpause)
+ {
+ $trans='PAGE';
+ $firstpause=0;
+ }
+ MakeXO();
+ NewPage($trans);
+ $present=1;
+ }
+ elsif ($par=~m/exec %%%%BEGINONCE/)
+ {
+ if ($noslide)
+ {
+ $suppress=1;
+ }
+ else
+ {
+ my $trans='BLOCK';
+
+ if ($firstpause)
+ {
+ $trans='PAGE';
+ $firstpause=0;
+ }
+ MakeXO();
+ NewPage($trans);
+ $present=1;
+ }
+ }
+ elsif ($par=~m/exec %%%%ENDONCE/)
+ {
+ if ($noslide)
+ {
+ $suppress=0;
+ }
+ else
+ {
+ MakeXO();
+ NewPage('BLOCK');
+ $cat->{PageMode}='/FullScreen';
+ pop(@XOstream);
+ }
+ }
+ elsif ($par=~m/\[(.+) pdfmark/)
+ {
+ my $pdfmark=$1;
+ $pdfmark=~s((\d{4,6}) u)(sprintf("%.1f",$1/$desc{sizescale}))eg;
+ $pdfmark=~s(\\\[u00(..)\])(chr(hex($1)))eg;
+ $pdfmark=~s/\\n/\n/g;
+
+ if ($pdfmark=~m/(.+) \/DOCINFO\s*$/s)
+ {
+ my @xwds=split(/ /,"<< $1 >>");
+ my $docinfo=ParsePDFValue(\@xwds);
+
+ foreach my $k (sort keys %{$docinfo})
+ {
+ $info{$k}=$docinfo->{$k} if $k ne 'Producer';
+ }
+ }
+ elsif ($pdfmark=~m/(.+) \/DOCVIEW\s*$/)
+ {
+ my @xwds=split(' ',"<< $1 >>");
+ my $docview=ParsePDFValue(\@xwds);
+
+ foreach my $k (sort keys %{$docview})
+ {
+ $cat->{$k}=$docview->{$k} if !exists($cat->{$k});
+ }
+ }
+ elsif ($pdfmark=~m/(.+) \/DEST\s*$/)
+ {
+ my @xwds=split(' ',"<< $1 >>");
+ my $dest=ParsePDFValue(\@xwds);
+ $dest->{View}->[1]=GraphY($dest->{View}->[1]*-1);
+ unshift(@{$dest->{View}},"$cpageno 0 R");
+
+ if (!defined($dests))
+ {
+ $cat->{Dests}=BuildObj(++$objct,{});
+ $dests=$obj[$objct]->{DATA};
+ }
+
+ my $k=substr($dest->{Dest},1);
+ $dests->{$k}=$dest->{View};
+ }
+ elsif ($pdfmark=~m/(.+) \/ANN\s*$/)
+ {
+ my $l=$1;
+ $l=~s/Color/C/;
+ $l=~s/Action/A/;
+ $l=~s/Title/T/;
+ $l=~s'/Subtype /URI'/S /URI';
+ my @xwds=split(' ',"<< $l >>");
+ my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds));
+ my $annot=$obj[$objct];
+ $annot->{DATA}->{Type}='/Annot';
+ FixRect($annot->{DATA}->{Rect}); # Y origin to ll
+ FixPDFColour($annot->{DATA});
+ push(@PageAnnots,$annotno);
+ }
+ elsif ($pdfmark=~m/(.+) \/OUT\s*$/)
+ {
+ my $t=$1;
+ $t=~s/\\\) /\\\\\) /g;
+ $t=~s/\\e/\\\\/g;
+ $t=~m/(^.*\/Title \()(.*)(\).*)/;
+ my ($pre,$title,$post)=($1,$2,$3);
+ $title=~s/(?<!\\)\(/\\\(/g;
+ $title=~s/(?<!\\)\)/\\\)/g;
+ my @xwds=split(' ',"<< $pre$title$post >>");
+ my $out=ParsePDFValue(\@xwds);
+
+ my $this=[$out,[]];
+
+ if (exists($out->{Level}))
+ {
+ my $lev=abs($out->{Level});
+ my $levsgn=sgn($out->{Level});
+ delete($out->{Level});
+
+ if ($lev > $thislev)
+ {
+ my $thisoutlev=$curoutlev->[$#{$curoutlev}]->[1];
+ $thisoutlev->[0]=[0,$curoutlev,0,$levsgn];
+ $curoutlev=$thisoutlev;
+ $curoutlevno=$#{$curoutlev};
+ $thislev++;
+ }
+ elsif ($lev < $thislev)
+ {
+ my $openct=$curoutlev->[0]->[2];
+
+ while ($thislev > $lev)
+ {
+ my $nxtoutlev=$curoutlev->[0]->[1];
+ $nxtoutlev->[0]->[2]+=$openct if $curoutlev->[0]->[3]==1;
+ $openct=0 if $nxtoutlev->[0]->[3]==-1;
+ $curoutlev=$nxtoutlev;
+ $thislev--;
+ }
+
+ $curoutlevno=$#{$curoutlev};
+ }
+
+# push(@{$curoutlev},$this);
+ splice(@{$curoutlev},++$curoutlevno,0,$this);
+ $curoutlev->[0]->[2]++;
+ }
+ else
+ {
+ # This code supports old pdfmark.tmac, unused by pdf.tmac
+ while ($curoutlev->[0]->[0] == 0 and defined($curoutlev->[0]->[1]))
+ {
+ $curoutlev=$curoutlev->[0]->[1];
+ }
+
+ $curoutlev->[0]->[0]--;
+ $curoutlev->[0]->[2]++;
+ push(@{$curoutlev},$this);
+
+
+ if (exists($out->{Count}) and $out->{Count} != 0)
+ {
+ push(@{$this->[1]},[abs($out->{Count}),$curoutlev,0,sgn($out->{Count})]);
+ $curoutlev=$this->[1];
+
+ if ($out->{Count} > 0)
+ {
+ my $p=$curoutlev;
+
+ while (defined($p))
+ {
+ $p->[0]->[2]+=$out->{Count};
+ $p=$p->[0]->[1];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ elsif (lc($xprm[0]) eq 'pdf:')
+ {
+ if (lc($xprm[1]) eq 'import')
+ {
+ my $fil=$xprm[2];
+ my $llx=$xprm[3];
+ my $lly=$xprm[4];
+ my $urx=$xprm[5];
+ my $ury=$xprm[6];
+ my $wid=GetPoints($xprm[7]);
+ my $hgt=GetPoints($xprm[8])||-1;
+ my $mat=[1,0,0,1,0,0];
+
+ if (!exists($incfil{$fil}))
+ {
+ if ($fil=~m/\.pdf$/)
+ {
+ $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"import");
+ }
+ elsif ($fil=~m/\.swf$/)
+ {
+ my $xscale=$wid/($urx-$llx+1);
+ my $yscale=($hgt<=0)?$xscale:($hgt/($ury-$lly+1));
+ $hgt=($ury-$lly+1)*$yscale;
+
+ if ($rot)
+ {
+ $mat->[3]=$xscale;
+ $mat->[0]=$yscale;
+ }
+ else
+ {
+ $mat->[0]=$xscale;
+ $mat->[3]=$yscale;
+ }
+
+ $incfil{$fil}=LoadSWF($fil,[$llx,$lly,$urx,$ury],$mat);
+ }
+ else
+ {
+ Warn("unrecognized 'import' file type '$fil'");
+ return undef;
+ }
+ }
+
+ if (defined($incfil{$fil}))
+ {
+ IsGraphic();
+ if ($fil=~m/\.pdf$/)
+ {
+ my $bbox=$incfil{$fil}->[1];
+ my $xscale=d3($wid/($bbox->[2]-$bbox->[0]+1));
+ my $yscale=d3(($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1]+1)));
+ $wid=($bbox->[2]-$bbox->[0])*$xscale;
+ $hgt=($bbox->[3]-$bbox->[1])*$yscale;
+ $ypos+=$hgt;
+ $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm";
+ $stream.=" 0 1 -1 0 0 0 cm" if $rot;
+ $stream.=" /$incfil{$fil}->[0] Do Q\n";
+ }
+ elsif ($fil=~m/\.swf$/)
+ {
+ $stream.=PutXY($xpos,$ypos)." m /$incfil{$fil} Do\n";
+ }
+ }
+ }
+ elsif (lc($xprm[1]) eq 'pdfpic')
+ {
+ my $fil=$xprm[2];
+ my $flag=uc($xprm[3]||'-L');
+ my $wid=GetPoints($xprm[4])||-1;
+ my $hgt=GetPoints($xprm[5]||-1);
+ my $ll=GetPoints($xprm[6]||0);
+ my $mat=[1,0,0,1,0,0];
+
+ if (!exists($incfil{$fil}))
+ {
+ $incfil{$fil}=LoadPDF($fil,$mat,$wid,$hgt,"pdfpic");
+ }
+
+ if (defined($incfil{$fil}))
+ {
+ IsGraphic();
+ my $bbox=$incfil{$fil}->[1];
+ $wid=($bbox->[2]-$bbox->[0]) if $wid <= 0;
+ my $xscale=d3($wid/($bbox->[2]-$bbox->[0]));
+ my $yscale=d3(($hgt<=0)?$xscale:($hgt/($bbox->[3]-$bbox->[1])));
+ $xscale=($wid<=0)?$yscale:$xscale;
+ $xscale=$yscale if $yscale < $xscale;
+ $yscale=$xscale if $xscale < $yscale;
+ $wid=($bbox->[2]-$bbox->[0])*$xscale;
+ $hgt=($bbox->[3]-$bbox->[1])*$yscale;
+
+ if ($flag eq '-C' and $ll > $wid)
+ {
+ $xpos+=int(($ll-$wid)/2);
+ }
+ elsif ($flag eq '-R' and $ll > $wid)
+ {
+ $xpos+=$ll-$wid;
+ }
+
+ $ypos+=$hgt;
+ $stream.="q $xscale 0 0 $yscale ".PutXY($xpos,$ypos)." cm";
+ $stream.=" 0 1 -1 0 0 0 cm" if $rot;
+ $stream.=" /$incfil{$fil}->[0] Do Q\n";
+ }
+ }
+ elsif (lc($xprm[1]) eq 'xrev')
+ {
+ $xrev=!$xrev;
+ }
+ elsif (lc($xprm[1]) eq 'markstart')
+ {
+ $mark={'rst' => ($xprm[2]+$xprm[4])/$unitwidth, 'rsb' => ($xprm[3]-$xprm[4])/$unitwidth, 'xpos' => $xpos-($xprm[4]/$unitwidth),
+ 'ypos' => $ypos, 'lead' => $xprm[4]/$unitwidth, 'pdfmark' => join(' ',@xprm[5..$#xprm])};
+ }
+ elsif (lc($xprm[1]) eq 'markend')
+ {
+ PutHotSpot($xpos) if defined($mark);
+ $mark=undef;
+ }
+ elsif (lc($xprm[1]) eq 'marksuspend')
+ {
+ $suspendmark=$mark;
+ $mark=undef;
+ }
+ elsif (lc($xprm[1]) eq 'markrestart')
+ {
+ $mark=$suspendmark;
+ $suspendmark=undef;
+ }
+ elsif (lc($xprm[1]) eq 'pagename')
+ {
+ if ($pginsert > -1)
+ {
+ $pgnames{$xprm[2]}=$pages->{Kids}->[$pginsert];
+ }
+ else
+ {
+ $pgnames{$xprm[2]}='top';
+ }
+ }
+ elsif (lc($xprm[1]) eq 'switchtopage')
+ {
+ my $ba=$xprm[2];
+ my $want=$xprm[3];
+
+ if ($pginsert > -1)
+ {
+ if (!defined($want) or $want eq '')
+ {
+ # no before/after
+ $want=$ba;
+ $ba='before';
+ }
+
+ if (!defined($ba) or $ba eq '' or $want eq 'bottom')
+ {
+ $pginsert=$#{$pages->{Kids}};
+ }
+ elsif ($want eq 'top')
+ {
+ $pginsert=-1;
+ }
+ else
+ {
+ if (exists($pgnames{$want}))
+ {
+ my $ref=$pgnames{$want};
+
+ if ($ref eq 'top')
+ {
+ $pginsert=-1;
+ }
+ else
+ {
+ FIND: while (1)
+ {
+ foreach my $j (0..$#{$pages->{Kids}})
+ {
+ if ($ref eq $pages->{Kids}->[$j])
+ {
+ if ($ba eq 'before')
+ {
+ $pginsert=$j-1;
+ last FIND;
+ }
+ elsif ($ba eq 'after')
+ {
+ $pginsert=$j;
+ last FIND;
+ }
+ else
+ {
+ # XXX: indentation wince
+ Warn(
+"expected 'switchtopage' parameter to be one of"
+. "'top|bottom|before|after', got '$ba'");
+ last FIND;
+ }
+ }
+
+ }
+
+ Warn("cannot find page ref '$ref'");
+ last FIND
+
+ }
+ }
+ }
+ else
+ {
+ Warn("cannot find page named '$want'");
+ }
+ }
+
+ if ($pginsert < 0)
+ {
+ ($curoutlev,$curoutlevno,$thislev)=(\@outlev,0,1);
+ }
+ else
+ {
+ ($curoutlev,$curoutlevno,$thislev)=(@{$outlines[$pginsert]});
+ }
+ }
+ }
+ elsif (lc($xprm[1]) eq 'transition' and !$noslide)
+ {
+ if (uc($xprm[2]) eq 'PAGE' or uc($xprm[2] eq 'SLIDE'))
+ {
+ $transition->{PAGE}->{S}='/'.ucfirst($xprm[3]) if $xprm[3] and $xprm[3] ne '.';
+ $transition->{PAGE}->{D}=$xprm[4] if $xprm[4] and $xprm[4] ne '.';
+ $transition->{PAGE}->{Dm}='/'.$xprm[5] if $xprm[5] and $xprm[5] ne '.';
+ $transition->{PAGE}->{M}='/'.$xprm[6] if $xprm[6] and $xprm[6] ne '.';
+ $xprm[7]='/None' if $xprm[7] and uc($xprm[7]) eq 'NONE';
+ $transition->{PAGE}->{Di}=$xprm[7] if $xprm[7] and $xprm[7] ne '.';
+ $transition->{PAGE}->{SS}=$xprm[8] if $xprm[8] and $xprm[8] ne '.';
+ $transition->{PAGE}->{B}=$xprm[9] if $xprm[9] and $xprm[9] ne '.';
+ }
+ elsif (uc($xprm[2]) eq 'BLOCK')
+ {
+ $transition->{BLOCK}->{S}='/'.ucfirst($xprm[3]) if $xprm[3] and $xprm[3] ne '.';
+ $transition->{BLOCK}->{D}=$xprm[4] if $xprm[4] and $xprm[4] ne '.';
+ $transition->{BLOCK}->{Dm}='/'.$xprm[5] if $xprm[5] and $xprm[5] ne '.';
+ $transition->{BLOCK}->{M}='/'.$xprm[6] if $xprm[6] and $xprm[6] ne '.';
+ $xprm[7]='/None' if $xprm[7] and uc($xprm[7]) eq 'NONE';
+ $transition->{BLOCK}->{Di}=$xprm[7] if $xprm[7] and $xprm[7] ne '.';
+ $transition->{BLOCK}->{SS}=$xprm[8] if $xprm[8] and $xprm[8] ne '.';
+ $transition->{BLOCK}->{B}=$xprm[9] if $xprm[9] and $xprm[9] ne '.';
+ }
+
+ $present=1;
+ }
+ elsif (lc($xprm[1]) eq 'background')
+ {
+ splice(@xprm,0,2);
+ my $type=shift(@xprm);
+# print STDERR "ypos=$ypos\n";
+
+ if (lc($type) eq 'off')
+ {
+ my $sptr=$#bgstack;
+ if ($sptr > -1)
+ {
+ if ($sptr == 0 and $bgstack[0]->[0] & 4)
+ {
+ pop(@bgstack);
+ }
+ else
+ {
+ $bgstack[$sptr]->[5]=GraphY($ypos);
+ $bgbox=DrawBox(pop(@bgstack)).$bgbox;
+ }
+ }
+ }
+ elsif (lc($type) eq 'footnote')
+ {
+ my $t=GetPoints($xprm[0]);
+ $boxmax=($t<0)?abs($t):GraphY($t);
+ }
+ else
+ {
+ my $bgtype=0;
+
+ foreach (@xprm)
+ {
+ $_=GetPoints($_);
+ }
+
+ $bgtype|=2 if $type=~m/box/i;
+ $bgtype|=1 if $type=~m/fill/i;
+ $bgtype|=4 if $type=~m/page/i;
+ $bgtype=5 if $bgtype==4;
+ my $bgwt=$xprm[4];
+ $bgwt=$xprm[0] if !defined($bgwt) and $#xprm == 0;
+ my (@bg)=(@xprm);
+ my $bg=\@bg;
+
+ if (!defined($bg[3]) or $bgtype & 4)
+ {
+ $bg=undef;
+ }
+ else
+ {
+ FixRect($bg);
+ }
+
+ if ($bgtype)
+ {
+ if ($bgtype & 4)
+ {
+ shift(@bgstack) if $#bgstack >= 0 and $bgstack[0]->[0] & 4;
+ unshift(@bgstack,[$bgtype,$strkcol,$fillcol,$bg,GraphY($ypos),GraphY($bg[3]||0),$bgwt || 0.4]);
+ }
+ else
+ {
+ push(@bgstack,[$bgtype,$strkcol,$fillcol,$bg,GraphY($ypos),GraphY($bg[3]||0),$bgwt || 0.4]);
+ }
+ }
+ }
+ }
+ }
+ elsif (lc(substr($xprm[0],0,9)) eq 'papersize')
+ {
+ my ($px,$py)=split(',',substr($xprm[0],10));
+ $px=GetPoints($px);
+ $py=GetPoints($py);
+ @mediabox=(0,0,$px,$py);
+ my @mb=@mediabox;
+ $matrixchg=1;
+ $custompaper=1;
+ $cpage->{MediaBox}=\@mb;
+ }
+ }
+}
+
+sub FixPDFColour
+{
+ my $o=shift;
+ my $a=$o->{C};
+ my @r=();
+ my $c=$a->[0];
+
+ if ($#{$a}==3)
+ {
+ if ($c > 1)
+ {
+ foreach my $j (0..2)
+ {
+ push(@r,sprintf("%1.3f",$a->[$j]/0xffff));
+ }
+
+ $o->{C}=\@r;
+ }
+ }
+ elsif (substr($c,0,1) eq '#')
+ {
+ if (length($c) == 7)
+ {
+ foreach my $j (0..2)
+ {
+ push(@r,sprintf("%1.3f",hex(substr($c,$j*2+1,2))/0xff));
+ }
+
+ $o->{C}=\@r;
+ }
+ elsif (length($c) == 14)
+ {
+ foreach my $j (0..2)
+ {
+ push(@r,sprintf("%1.3f",hex(substr($c,$j*4+2,4))/0xffff));
+ }
+
+ $o->{C}=\@r;
+ }
+ }
+}
+
+sub PutHotSpot
+{
+ my $endx=shift;
+ my $l=$mark->{pdfmark};
+ $l=~s/Color/C/;
+ $l=~s/Action/A/;
+ $l=~s'/Subtype /URI'/S /URI';
+ $l=~s(\\\[u00(..)\])(chr(hex($1)))eg;
+ my @xwds=split(' ',"<< $l >>");
+ my $annotno=BuildObj(++$objct,ParsePDFValue(\@xwds));
+ my $annot=$obj[$objct];
+ $annot->{DATA}->{Type}='/Annot';
+ $annot->{DATA}->{Rect}=[$mark->{xpos},$mark->{ypos}-$mark->{rsb},$endx+$mark->{lead},$mark->{ypos}-$mark->{rst}];
+ FixPDFColour($annot->{DATA});
+ FixRect($annot->{DATA}->{Rect}); # Y origin to ll
+ push(@PageAnnots,$annotno);
+}
+
+sub sgn
+{
+ return(1) if $_[0] > 0;
+ return(-1) if $_[0] < 0;
+ return(0);
+}
+
+sub FixRect
+{
+ my $rect=shift;
+
+ return if !defined($rect);
+ $rect->[1]=GraphY($rect->[1]);
+ $rect->[3]=GraphY($rect->[3]);
+
+ if ($rot)
+ {
+ ($rect->[0],$rect->[1])=Rotate($rect->[0],$rect->[1]);
+ ($rect->[2],$rect->[3])=Rotate($rect->[2],$rect->[3]);
+ }
+}
+
+sub Rotate
+{
+ my ($tx,$ty)=(@_);
+ my $theta=rad($rot);
+
+ ($tx,$ty)=(d3($tx * cos(-$theta) - $ty * sin(-$theta)),
+ d3($tx * sin( $theta) + $ty * cos( $theta)));
+ return($tx,$ty);
+}
+
+sub GetPoints
+{
+ my $val=shift;
+
+ $val=ToPoints($1,$2) if ($val and $val=~m/(-?[\d.]+)([cipnz])/);
+
+ return $val;
+}
+
+# Although the PDF reference mentions XObject/Form as a way of
+# incorporating an external PDF page into the current PDF, it seems not
+# to work with any current PDF reader (although I am told (by Leonard
+# Rosenthol, who helped author the PDF ISO standard) that Acroread 9
+# does support it, empirical observation shows otherwise!!). So... do
+# it the hard way - full PDF parser and merge required objects!!!
+
+# sub BuildRef
+# {
+# my $fil=shift;
+# my $bbox=shift;
+# my $mat=shift;
+# my $wid=($bbox->[2]-$bbox->[0])*$mat->[0];
+# my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3];
+#
+# if (!open(PDF,"<$fil"))
+# {
+# Warn("failed to open '$fil'");
+# return(undef);
+# }
+#
+# my (@f)=(<PDF>);
+#
+# close(PDF);
+#
+# $objct++;
+# my $xonm="XO$objct";
+#
+# $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject',
+# 'Subtype' => '/Form',
+# 'BBox' => $bbox,
+# 'Matrix' => $mat,
+# 'Resources' => $pages->{'Resources'},
+# 'Ref' => {'Page' => '1',
+# 'F' => BuildObj($objct+1,{'Type' => '/Filespec',
+# 'F' => "($fil)",
+# 'EF' => {'F' => BuildObj($objct+2,{'Type' => '/EmbeddedFile'})}
+# })
+# }
+# });
+#
+# $obj[$objct]->{STREAM}="q 1 0 0 1 0 0 cm
+# q BT
+# 1 0 0 1 0 0 Tm
+# .5 g .5 G
+# /F5 20 Tf
+# (Proxy) Tj
+# ET Q
+# 0 0 m 72 0 l s
+# Q\n";
+#
+# # $obj[$objct]->{STREAM}=PutXY($xpos,$ypos)." m ".PutXY($xpos+$wid,$ypos)." l ".PutXY($xpos+$wid,$ypos+$hgt)." l ".PutXY($xpos,$ypos+$hgt)." l f\n";
+# $obj[$objct+2]->{STREAM}=join('',@f);
+# PutObj($objct);
+# PutObj($objct+1);
+# PutObj($objct+2);
+# $objct+=2;
+# return($xonm);
+# }
+
+sub LoadSWF
+{
+ my $fil=shift;
+ my $bbox=shift;
+ my $mat=shift;
+ my $wid=($bbox->[2]-$bbox->[0])*$mat->[0];
+ my $hgt=($bbox->[3]-$bbox->[1])*$mat->[3];
+ my (@path)=split('/',$fil);
+ my $node=pop(@path);
+
+ if (!open(PDF,"<$fil"))
+ {
+ Warn("failed to open SWF '$fil'");
+ return(undef);
+ }
+
+ my (@f)=(<PDF>);
+
+ close(PDF);
+
+ $objct++;
+ my $xonm="XO$objct";
+
+ $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($objct,{'Type' => '/XObject', 'BBox' => $bbox, 'Matrix' => $mat, 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject"});
+ $obj[$objct]->{STREAM}='';
+ PutObj($objct);
+ $objct++;
+ my $asset=BuildObj($objct,{'EF' => {'F' => BuildObj($objct+1,{})},
+ 'F' => "($node)",
+ 'Type' => '/Filespec',
+ 'UF' => "($node)"});
+
+ PutObj($objct);
+ $objct++;
+ $obj[$objct]->{STREAM}=join('',@f);
+ PutObj($objct);
+ $objct++;
+ my $config=BuildObj($objct,{'Instances' => [BuildObj($objct+1,{'Params' => { 'Binding' => '/Background'}, 'Asset' => $asset})],
+ 'Subtype' => '/Flash'});
+
+ PutObj($objct);
+ $objct++;
+ PutObj($objct);
+ $objct++;
+
+ my ($x,$y)=split(' ',PutXY($xpos,$ypos));
+
+ push(@{$cpage->{Annots}},BuildObj($objct,{'RichMediaContent' => {'Subtype' => '/Flash', 'Configurations' => [$config], 'Assets' => {'Names' => [ "($node)", $asset ] }},
+ 'P' => "$cpageno 0 R",
+ 'RichMediaSettings' => { 'Deactivation' => { 'Condition' => '/PI',
+ 'Type' => '/RichMediaDeactivation'},
+ 'Activation' => { 'Condition' => '/PV',
+ 'Type' => '/RichMediaActivation'}},
+ 'F' => 68,
+ 'Subtype' => '/RichMedia',
+ 'Type' => '/Annot',
+ 'Rect' => "[ $x $y ".($x+$wid)." ".($y+$hgt)." ]",
+ 'Border' => [0,0,0]}));
+
+ PutObj($objct);
+
+ return $xonm;
+}
+
+sub OpenInc
+{
+ my $fn=shift;
+ my $fnm=$fn;
+ my $F;
+
+ if (substr($fnm,0,1) eq '/' or substr($fnm,1,1) eq ':') # dos
+ {
+ if (-r $fnm and open($F,"<$fnm"))
+ {
+ return($F,$fnm);
+ }
+ }
+ else
+ {
+ foreach my $dir (@idirs)
+ {
+ $fnm="$dir/$fn";
+
+ if (-r "$fnm" and open($F,"<$fnm"))
+ {
+ return($F,$fnm);
+ }
+ }
+ }
+
+ return(undef,$fn);
+}
+
+sub LoadPDF
+{
+ my $pdfnm=shift;
+ my $mat=shift;
+ my $wid=shift;
+ my $hgt=shift;
+ my $type=shift;
+ my $pdf;
+ my $pdftxt='';
+ my $strmlen=0;
+ my $curobj=-1;
+ my $instream=0;
+ my $cont;
+ my $adj=0;
+ my $keepsep=$/;
+
+ my ($PD,$PDnm)=OpenInc($pdfnm);
+
+ if (!defined($PD))
+ {
+ Warn("failed to open PDF '$pdfnm'");
+ return undef;
+ }
+
+ my $hdr=<$PD>;
+
+ $/="\r",$adj=1 if (length($hdr) > 10);
+
+ while (<$PD>)
+ {
+ chomp;
+
+ s/\n//;
+
+ if (m/endstream(\s+.*)?$/)
+ {
+ $instream=0;
+ $_="endstream";
+ $_.=$1 if defined($1)
+ }
+
+ next if $instream;
+
+ if (m'/Length\s+(\d+)(\s+\d+\s+R)?')
+ {
+ if (!defined($2))
+ {
+ $strmlen=$1;
+ }
+ else
+ {
+ $strmlen=0;
+ }
+ }
+
+ if (m'^(\d+) \d+ obj')
+ {
+ $curobj=$1;
+ $pdf->[$curobj]->{OBJ}=undef;
+ }
+
+ if (m'stream\s*$' and ! m/^endstream/)
+ {
+ if ($curobj > -1)
+ {
+ $pdf->[$curobj]->{STREAMPOS}=[tell($PD)+$adj,$strmlen];
+ seek($PD,$strmlen,1);
+ $instream=1;
+ }
+ else
+ {
+ Warn("parsing PDF '$pdfnm' failed");
+ return undef;
+ }
+ }
+
+ s/%.*?$//;
+ $pdftxt.=$_.' ';
+ }
+
+ close($PD);
+
+ open(PD,"<$PDnm");
+# $pdftxt=~s/\]/ \]/g;
+ my (@pdfwds)=split(' ',$pdftxt);
+ my $wd;
+ my $root;
+
+ while ($wd=nextwd(\@pdfwds),length($wd))
+ {
+ if ($wd=~m/\d+/ and defined($pdfwds[1]) and $pdfwds[1]=~m/^obj(.*)/)
+ {
+ $curobj=$wd;
+ shift(@pdfwds); shift(@pdfwds);
+ unshift(@pdfwds,$1) if defined($1) and length($1);
+ $pdf->[$curobj]->{OBJ}=ParsePDFObj(\@pdfwds);
+ my $o=$pdf->[$curobj];
+
+ if (ref($o->{OBJ}) eq 'HASH' and exists($o->{OBJ}->{Type}) and $o->{OBJ}->{Type} eq '/ObjStm')
+ {
+ LoadStream($o,$pdf);
+ my $pos=$o->{OBJ}->{First};
+ my $s=$o->{STREAM};
+ my @o=split(' ',substr($s,0,$pos));
+ substr($s,0,$pos)='';
+ push(@o,-1,length($s));
+
+ for (my $j=0; $j<=$#o-2; $j+=2)
+ {
+ my @w=split(' ',substr($s,$o[$j+1],$o[$j+3]-$o[$j+1]));
+ $pdf->[$o[$j]]->{OBJ}=ParsePDFObj(\@w);
+ }
+
+ $pdf->[$curobj]=undef;
+ }
+
+ $root=$curobj if ref($pdf->[$curobj]->{OBJ}) eq 'HASH' and exists($pdf->[$curobj]->{OBJ}->{Type}) and $pdf->[$curobj]->{OBJ}->{Type} eq '/XRef';
+ }
+ elsif ($wd eq 'trailer' and !exists($pdf->[0]->{OBJ}))
+ {
+ $pdf->[0]->{OBJ}=ParsePDFObj(\@pdfwds);
+ }
+ else
+ {
+# print "Skip '$wd'\n";
+ }
+ }
+
+ $pdf->[0]=$pdf->[$root] if !defined($pdf->[0]);
+ my $catalog=${$pdf->[0]->{OBJ}->{Root}};
+ my $page=FindPage(1,$pdf);
+ my $xobj=++$objct;
+
+ # Load the streamas
+
+ foreach my $o (@{$pdf})
+ {
+ if (exists($o->{STREAMPOS}) and !exists($o->{STREAM}))
+ {
+ LoadStream($o,$pdf);
+ }
+ }
+
+ close(PD);
+
+ # Find BBox
+ my $BBox;
+ my $insmap={};
+
+ foreach my $k (qw( ArtBox TrimBox BleedBox CropBox MediaBox ))
+ {
+ $BBox=FindKey($pdf,$page,$k);
+ last if $BBox;
+ }
+
+ $BBox=[0,0,595,842] if !defined($BBox);
+
+ $wid=($BBox->[2]-$BBox->[0]+1) if $wid==0;
+ my $xscale=d3(abs($wid)/($BBox->[2]-$BBox->[0]+1));
+ my $yscale=d3(($hgt<=0)?$xscale:(abs($hgt)/($BBox->[3]-$BBox->[1]+1)));
+ $hgt=($BBox->[3]-$BBox->[1]+1)*$yscale;
+
+ if ($type eq "import")
+ {
+ $mat->[0]=$xscale;
+ $mat->[3]=$yscale;
+ }
+
+ # Find Resource
+
+ my $res=FindKey($pdf,$page,'Resources');
+ my $xonm="XO$xobj";
+
+ # Map inserted objects to current PDF
+
+ MapInsValue($pdf,$page,'',$insmap,$xobj,$pdf->[$page]->{OBJ});
+#
+# Many PDFs include 'Resources' at the 'Page' level but if 'Resources' is held at a higher level (i.e 'Pages')
+# then we need to include its objects as well.
+#
+ MapInsValue($pdf,$page,'',$insmap,$xobj,$res) if !exists($pdf->[$page]->{OBJ}->{Resources});
+
+ # Copy Resources
+
+ my %incres=%{$res};
+
+ $incres{ProcSet}=['/PDF', '/Text', '/ImageB', '/ImageC', '/ImageI'];
+
+ ($mat->[4],$mat->[5])=split(' ',PutXY($xpos,$ypos));
+ $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => '/XObject', 'BBox' => $BBox, 'Name' => "/$xonm", 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject", 'Resources' => \%incres});
+
+ if ($BBox->[0] != 0 or $BBox->[1] != 0)
+ {
+ my (@matrix)=(1,0,0,1,-$BBox->[0],-$BBox->[1]);
+ $obj[$xobj]->{DATA}->{Matrix}=\@matrix;
+ }
+
+ BuildStream($xobj,$pdf,$pdf->[$page]->{OBJ}->{Contents});
+
+ $/=$keepsep;
+ return([$xonm,$BBox] );
+}
+
+sub LoadStream
+{
+ my $o=shift;
+ my $pdf=shift;
+ my $l;
+
+ $l=$o->{OBJ}->{Length} if exists($o->{OBJ}->{Length});
+
+ $l=$pdf->[$$l]->{OBJ} if (defined($l) && ref($l) eq 'OBJREF');
+
+ Die("unable to determine length of stream \@$o->{STREAMPOS}->[0]")
+ if !defined($l);
+
+ sysseek(PD,$o->{STREAMPOS}->[0],0);
+ Warn("failed to read all of the stream")
+ if $l != sysread(PD,$o->{STREAM},$l);
+
+ if ($gotzlib and exists($o->{OBJ}->{'Filter'}) and $o->{OBJ}->{'Filter'} eq '/FlateDecode')
+ {
+ $o->{STREAM}=Compress::Zlib::uncompress($o->{STREAM});
+ delete($o->{OBJ }->{'Filter'});
+ }
+}
+
+sub BuildStream
+{
+ my $xobj=shift;
+ my $pdf=shift;
+ my $val=shift;
+ my $strm='';
+ my $objs;
+ my $refval=ref($val);
+
+ if ($refval eq 'OBJREF')
+ {
+ push(@{$objs}, $val);
+ }
+ elsif ($refval eq 'ARRAY')
+ {
+ $objs=$val;
+ }
+ else
+ {
+ Warn("unexpected 'Contents'");
+ }
+
+ foreach my $o (@{$objs})
+ {
+ $strm.="\n" if $strm;
+ $strm.=$pdf->[$$o]->{STREAM} if exists($pdf->[$$o]->{STREAM});
+ }
+
+ $obj[$xobj]->{STREAM}=$strm;
+}
+
+
+sub MapInsHash
+{
+ my $pdf=shift;
+ my $o=shift;
+ my $insmap=shift;
+ my $parent=shift;
+ my $val=shift;
+
+
+ foreach my $k (sort keys(%{$val}))
+ {
+ MapInsValue($pdf,$o,$k,$insmap,$parent,$val->{$k}) if $k ne 'Contents';
+ }
+}
+
+sub MapInsValue
+{
+ my $pdf=shift;
+ my $o=shift;
+ my $k=shift;
+ my $insmap=shift;
+ my $parent=shift;
+ my $val=shift;
+ my $refval=ref($val);
+
+ if ($refval eq 'OBJREF')
+ {
+ if ($k ne 'Parent')
+ {
+ if (!exists($insmap->{IMP}->{$$val}))
+ {
+ $objct++;
+ $insmap->{CUR}->{$objct}=$$val;
+ $insmap->{IMP}->{$$val}=$objct;
+ $obj[$objct]->{DATA}=$pdf->[$$val]->{OBJ};
+ $obj[$objct]->{STREAM}=$pdf->[$$val]->{STREAM} if exists($pdf->[$$val]->{STREAM});
+ MapInsValue($pdf,$$val,'',$insmap,$o,$pdf->[$$val]->{OBJ});
+ }
+
+ $$val=$insmap->{IMP}->{$$val};
+ }
+ else
+ {
+ $$val=$parent;
+ }
+ }
+ elsif ($refval eq 'ARRAY')
+ {
+ foreach my $v (@{$val})
+ {
+ MapInsValue($pdf,$o,'',$insmap,$parent,$v)
+ }
+ }
+ elsif ($refval eq 'HASH')
+ {
+ MapInsHash($pdf,$o,$insmap,$parent,$val);
+ }
+
+}
+
+sub FindKey
+{
+ my $pdf=shift;
+ my $page=shift;
+ my $k=shift;
+
+ if (exists($pdf->[$page]->{OBJ}->{$k}))
+ {
+ my $val=$pdf->[$page]->{OBJ}->{$k};
+ $val=$pdf->[$$val]->{OBJ} if ref($val) eq 'OBJREF';
+ return($val);
+ }
+ else
+ {
+ if (exists($pdf->[$page]->{OBJ}->{Parent}))
+ {
+ return(FindKey($pdf,${$pdf->[$page]->{OBJ}->{Parent}},$k));
+ }
+ }
+
+ return(undef);
+}
+
+sub FindPage
+{
+ my $wantpg=shift;
+ my $pdf=shift;
+ my $catalog=${$pdf->[0]->{OBJ}->{Root}};
+ my $pages=${$pdf->[$catalog]->{OBJ}->{Pages}};
+
+ return(NextPage($pdf,$pages,\$wantpg));
+}
+
+sub NextPage
+{
+ my $pdf=shift;
+ my $pages=shift;
+ my $wantpg=shift;
+ my $ret;
+
+ if ($pdf->[$pages]->{OBJ}->{Type} eq '/Pages')
+ {
+ foreach my $kid (@{$pdf->[$pages]->{OBJ}->{Kids}})
+ {
+ $ret=NextPage($pdf,$$kid,$wantpg);
+ last if $$wantpg<=0;
+ }
+ }
+ elsif ($pdf->[$pages]->{OBJ}->{Type} eq '/Page')
+ {
+ $$wantpg--;
+ $ret=$pages;
+ }
+
+ return($ret);
+}
+
+sub nextwd
+{
+ my $pdfwds=shift;
+
+ my $wd=shift(@{$pdfwds});
+
+ return('') if !defined($wd);
+
+ if ($wd=~m/^(.*?)(<<|>>|(?:(?<!\\)\[|\]))(.*)/)
+ {
+ if (defined($1) and length($1))
+ {
+ unshift(@{$pdfwds},$3) if defined($3) and length($3);
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ else
+ {
+ unshift(@{$pdfwds},$3) if defined($3) and length($3);
+ $wd=$2;
+ }
+ }
+
+ return($wd);
+}
+
+sub ParsePDFObj
+{
+
+ my $pdfwds=shift;
+ my $rtn;
+ my $wd;
+
+ while ($wd=nextwd($pdfwds),length($wd))
+ {
+ if ($wd eq 'stream' or $wd eq 'endstream')
+ {
+ next;
+ }
+ elsif ($wd eq 'endobj' or $wd eq 'startxref')
+ {
+ last;
+ }
+ else
+ {
+ unshift(@{$pdfwds},$wd);
+ $rtn=ParsePDFValue($pdfwds);
+ }
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFHash
+{
+ my $pdfwds=shift;
+ my $rtn={};
+ my $wd;
+
+ while ($wd=nextwd($pdfwds),length($wd))
+ {
+ if ($wd eq '>>')
+ {
+ last;
+ }
+
+ my (@w)=split('/',$wd,3);
+
+ if ($w[0])
+ {
+ Warn("PDF Dict Key '$wd' does not start with '/'");
+ exit 1;
+ }
+ else
+ {
+ unshift(@{$pdfwds},"/$w[2]") if $w[2];
+ $wd=$w[1];
+ (@w)=split('\(',$wd,2);
+ $wd=$w[0];
+ unshift(@{$pdfwds},"($w[1]") if defined($w[1]);
+ (@w)=split('\<',$wd,2);
+ $wd=$w[0];
+ unshift(@{$pdfwds},"<$w[1]") if defined($w[1]);
+
+ $rtn->{$wd}=ParsePDFValue($pdfwds);
+ }
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFValue
+{
+ my $pdfwds=shift;
+ my $rtn;
+ my $wd=nextwd($pdfwds);
+
+ if ($wd=~m/^\d+$/ and $pdfwds->[0]=~m/^\d+$/ and $pdfwds->[1]=~m/^R(\]|\>|\/)?/)
+ {
+ shift(@{$pdfwds});
+ if (defined($1) and length($1))
+ {
+ $pdfwds->[0]=substr($pdfwds->[0],1);
+ }
+ else
+ {
+ shift(@{$pdfwds});
+ }
+ return(bless(\$wd,'OBJREF'));
+ }
+
+ if ($wd eq '<<')
+ {
+ return(ParsePDFHash($pdfwds));
+ }
+
+ if ($wd eq '[')
+ {
+ return(ParsePDFArray($pdfwds));
+ }
+
+ if ($wd=~m/(.*?)(\(.*)$/)
+ {
+ if (defined($1) and length($1))
+ {
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ else
+ {
+ return(ParsePDFString($wd,$pdfwds));
+ }
+ }
+
+ if ($wd=~m/(.*?)(\<.*)$/)
+ {
+ if (defined($1) and length($1))
+ {
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ else
+ {
+ return(ParsePDFHexString($wd,$pdfwds));
+ }
+ }
+
+ if ($wd=~m/(.+?)(\/.*)$/)
+ {
+ if (defined($2) and length($2))
+ {
+ unshift(@{$pdfwds},$2);
+ $wd=$1;
+ }
+ }
+
+ return($wd);
+}
+
+sub ParsePDFString
+{
+ my $wd=shift;
+ my $rtn='';
+ my $pdfwds=shift;
+ my $lev=0;
+
+ while (length($wd))
+ {
+ $rtn.=' ' if length($rtn);
+
+ while ($wd=~m/(?<!\\)\(/g) {$lev++;}
+ while ($wd=~m/(?<!\\)\)/g) {$lev--;}
+
+
+ if ($lev<=0 and $wd=~m/^(.*?\))([^)]+)$/)
+ {
+ unshift(@{$pdfwds},$2) if defined($2) and length($2);
+ $wd=$1;
+ }
+
+ $rtn.=$wd;
+
+ last if $lev <= 0;
+
+ $wd=nextwd($pdfwds);
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFHexString
+{
+ my $wd=shift;
+ my $rtn='';
+ my $pdfwds=shift;
+ my $lev=0;
+
+ if ($wd=~m/^(<.+?>)(.*)/)
+ {
+ unshift(@{$pdfwds},$2) if defined($2) and length($2);
+ $rtn=$1;
+ }
+
+ return($rtn);
+}
+
+sub ParsePDFArray
+{
+ my $pdfwds=shift;
+ my $rtn=[];
+ my $wd;
+
+ while (1)
+ {
+ $wd=ParsePDFValue($pdfwds);
+ last if $wd eq ']' or length($wd)==0;
+ push(@{$rtn},$wd);
+ }
+
+ return($rtn);
+}
+
+sub Warn
+{
+ Msg(0,(@_));
+}
+
+sub Die
+{
+ Msg(1,(@_));
+}
+
+sub Msg
+{
+ my ($fatal,$msg)=@_;
+
+ print STDERR "$prog:";
+ print STDERR "$env{SourceFile}:" if exists($env{SourceFile});
+ print STDERR " ";
+
+ if ($fatal)
+ {
+ print STDERR "fatal error: ";
+ }
+ else
+ {
+ print STDERR "warning: ";
+ }
+
+ print STDERR "$msg\n";
+ exit 1 if $fatal;
+}
+
+sub PutXY
+{
+ my ($x,$y)=(@_);
+
+ if ($frot)
+ {
+ return(d3($y)." ".d3($x));
+ }
+ else
+ {
+ $y=$mediabox[3]-$y;
+ return(d3($x)." ".d3($y));
+ }
+}
+
+sub GraphY
+{
+ my $y=shift;
+
+ if ($frot)
+ {
+ return($y);
+ }
+ else
+ {
+ return($mediabox[3]-$y);
+ }
+}
+
+sub Put
+{
+ my $msg=shift;
+
+ print $msg;
+ $fct+=length($msg);
+}
+
+sub PutObj
+{
+ my $ono=shift;
+ my $msg="$ono 0 obj ";
+ $obj[$ono]->{XREF}=$fct;
+ if (exists($obj[$ono]->{STREAM}))
+ {
+ if ($gotzlib && !$debug && !exists($obj[$ono]->{DATA}->{'Filter'}))
+ {
+ $obj[$ono]->{STREAM}=Compress::Zlib::compress($obj[$ono]->{STREAM});
+ $obj[$ono]->{DATA}->{'Filter'}='/FlateDecode';
+ }
+
+ $obj[$ono]->{DATA}->{'Length'}=length($obj[$ono]->{STREAM});
+ }
+ PutField(\$msg,$obj[$ono]->{DATA});
+ PutStream(\$msg,$ono) if exists($obj[$ono]->{STREAM});
+ Put($msg."endobj\n");
+}
+
+sub PutStream
+{
+ my $msg=shift;
+ my $ono=shift;
+
+ # We could 'flate' here
+ $$msg.="stream\n$obj[$ono]->{STREAM}endstream\n";
+}
+
+sub PutField
+{
+ my $pmsg=shift;
+ my $fld=shift;
+ my $term=shift||"\n";
+ my $typ=ref($fld);
+
+ if ($typ eq '')
+ {
+ $$pmsg.="$fld$term";
+ }
+ elsif ($typ eq 'ARRAY')
+ {
+ $$pmsg.='[';
+ foreach my $cell (@{$fld})
+ {
+ PutField($pmsg,$cell,' ');
+ }
+ $$pmsg.="]$term";
+ }
+ elsif ($typ eq 'HASH')
+ {
+ $$pmsg.='<< ';
+ foreach my $key (sort keys %{$fld})
+ {
+ $$pmsg.="/$key ";
+ PutField($pmsg,$fld->{$key});
+ }
+ $$pmsg.=">>$term";
+ }
+ elsif ($typ eq 'OBJREF')
+ {
+ $$pmsg.="$$fld 0 R$term";
+ }
+}
+
+sub BuildObj
+{
+ my $ono=shift;
+ my $val=shift;
+
+ $obj[$ono]->{DATA}=$val;
+
+ return("$ono 0 R ");
+}
+
+sub LoadFont
+{
+ my $fontno=shift;
+ my $fontnm=shift;
+ my $ofontnm=$fontnm;
+
+ return $fontlst{$fontno}->{OBJ} if (exists($fontlst{$fontno}));
+
+ my $f;
+ OpenFile(\$f,$fontdir,"$fontnm");
+
+ if (!defined($f) and $Foundry)
+ {
+ # Try with no foundry
+ $fontnm=~s/.*?-//;
+ OpenFile(\$f,$fontdir,$fontnm);
+ }
+
+ Die("unable to open font '$ofontnm' for mounting") if !defined($f);
+
+ my $foundry='';
+ $foundry=$1 if $fontnm=~m/^(.*?)-/;
+ my $stg=1;
+ my %fnt;
+ my @fntbbox=(0,0,0,0);
+ my $capheight=0;
+ my $lastchr=0;
+ my $lastnm;
+ my $t1flags=0;
+ my $fixwid=-1;
+ my $ascent=0;
+ my $charset='';
+
+ while (<$f>)
+ {
+ chomp;
+
+ s/^ +//;
+ s/^#.*// if $stg == 1;
+ next if $_ eq '';
+
+ if ($stg == 1)
+ {
+ my ($key,$val)=split(' ',$_,2);
+
+ $key=lc($key);
+ $stg=2,next if $key eq 'kernpairs';
+ $stg=3,next if lc($_) eq 'charset';
+
+ $fnt{$key}=$val
+ }
+ elsif ($stg == 2)
+ {
+ $stg=3,next if lc($_) eq 'charset';
+
+ my ($ch1,$ch2,$k)=split;
+# $fnt{KERN}->{$ch1}->{$ch2}=$k;
+ }
+ else
+ {
+ my (@r)=split;
+ my (@p)=split(',',$r[1]);
+
+ if ($r[1] eq '"')
+ {
+ $fnt{NAM}->{$r[0]}=[@{$fnt{NAM}->{$lastnm}}];
+ next;
+ }
+
+ $r[3]=oct($r[3]) if substr($r[3],0,1) eq '0';
+ $r[0]='u0020' if $r[3] == 32;
+ $r[0]="u00".hex($r[3]) if $r[0] eq '---';
+# next if $r[3] >255;
+ $r[4]=$r[0] if !defined($r[4]);
+ $fnt{NAM}->{$r[0]}=[$p[0],$r[3],'/'.$r[4],$r[3],0];
+ $fnt{NO}->[$r[3]]=[$r[0],$r[0]];
+ $lastnm=$r[0];
+ $lastchr=$r[3] if $r[3] > $lastchr;
+ $fixwid=$p[0] if $fixwid == -1;
+ $fixwid=-2 if $fixwid > 0 and $p[0] != $fixwid;
+
+ $fntbbox[1]=-$p[2] if defined($p[2]) and -$p[2] < $fntbbox[1];
+ $fntbbox[2]=$p[0] if $p[0] > $fntbbox[2];
+ $fntbbox[3]=$p[1] if defined($p[1]) and $p[1] > $fntbbox[3];
+ $ascent=$p[1] if defined($p[1]) and $p[1] > $ascent and $r[3] >= 32 and $r[3] < 128;
+ $charset.='/'.$r[4] if defined($r[4]);
+ $capheight=$p[1] if length($r[4]) == 1 and $r[4] ge 'A' and $r[4] le 'Z' and $p[1] > $capheight;
+ }
+ }
+
+ close($f);
+
+ foreach my $j (0..$lastchr)
+ {
+ $fnt{NO}->[$j]=['',''] if !defined($fnt{NO}->[$j]);
+ }
+
+ my $fno=0;
+ my $slant=0;
+ $fnt{DIFF}=[];
+ $fnt{WIDTH}=[];
+ $fnt{NAM}->{''}=[0,-1,'/.notdef',-1,0];
+ $slant=-$fnt{'slant'} if exists($fnt{'slant'});
+ $fnt{'spacewidth'}=700 if !exists($fnt{'spacewidth'});
+
+ $t1flags|=2**0 if $fixwid > -1;
+ $t1flags|=(exists($fnt{'special'}))?2**2:2**5;
+ $t1flags|=2**6 if $slant != 0;
+ my $fontkey="$foundry $fnt{internalname}";
+
+ if (exists($download{$fontkey}))
+ {
+ # Not a Base Font
+ my ($l1,$l2,$l3,$t1stream)=GetType1($download{$fontkey});
+ Warn("incorrect font format for '$fontkey' ($l1)")
+ if !defined($t1stream);
+ $fno=++$objct;
+ $fontlst{$fontno}->{OBJ}=BuildObj($objct,
+ {'Type' => '/Font',
+ 'Subtype' => '/Type1',
+ 'BaseFont' => '/'.$fnt{internalname},
+ 'Widths' => $fnt{WIDTH},
+ 'FirstChar' => 0,
+ 'LastChar' => $lastchr,
+ 'Encoding' => BuildObj($objct+1,
+ {'Type' => '/Encoding',
+ 'Differences' => $fnt{DIFF}
+ }
+ ),
+ 'FontDescriptor' => BuildObj($objct+2,
+ {'Type' => '/FontDescriptor',
+ 'FontName' => '/'.$fnt{internalname},
+ 'Flags' => $t1flags,
+ 'FontBBox' => \@fntbbox,
+ 'ItalicAngle' => $slant,
+ 'Ascent' => $ascent,
+ 'Descent' => $fntbbox[1],
+ 'CapHeight' => $capheight,
+ 'StemV' => 0,
+# 'CharSet' => "($charset)",
+ 'FontFile' => BuildObj($objct+3,
+ {'Length1' => $l1,
+ 'Length2' => $l2,
+ 'Length3' => $l3
+ }
+ )
+ }
+ )
+ }
+ );
+
+ $objct+=3;
+ $fontlst{$fontno}->{NM}='/F'.$fontno;
+ $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ};
+ $fontlst{$fontno}->{FNT}=\%fnt;
+ $obj[$objct]->{STREAM}=$t1stream;
+
+ }
+ else
+ {
+ if (exists($missing{$fontkey}))
+ {
+ Warn("The download file in '$missing{$fontkey}' "
+ . " has erroneous entry for '$fnt{internalname} ($ofontnm)'");
+ }
+ else
+ {
+ Warn("unable to embed font file for '$fnt{internalname}'"
+ . " ($ofontnm) (missing entry in 'download' file?)")
+ if $embedall;
+ }
+ $fno=++$objct;
+ $fontlst{$fontno}->{OBJ}=BuildObj($objct,
+ {'Type' => '/Font',
+ 'Subtype' => '/Type1',
+ 'BaseFont' => '/'.$fnt{internalname},
+ 'Widths' => $fnt{WIDTH},
+ 'FirstChar' => 0,
+ 'LastChar' => $lastchr,
+ 'Encoding' => BuildObj($objct+1,
+ {'Type' => '/Encoding',
+ 'Differences' => $fnt{DIFF}
+ }
+ ),
+ 'FontDescriptor' => BuildObj($objct+2,
+ {'Type' => '/FontDescriptor',
+ 'FontName' => '/'.$fnt{internalname},
+ 'Flags' => $t1flags,
+ 'FontBBox' => \@fntbbox,
+ 'ItalicAngle' => $slant,
+ 'Ascent' => $ascent,
+ 'Descent' => $fntbbox[1],
+ 'CapHeight' => $capheight,
+ 'StemV' => 0,
+ 'CharSet' => "($charset)",
+ }
+ )
+ }
+ );
+
+ $objct+=2;
+ $fontlst{$fontno}->{NM}='/F'.$fontno;
+ $pages->{'Resources'}->{'Font'}->{'F'.$fontno}=$fontlst{$fontno}->{OBJ};
+ $fontlst{$fontno}->{FNT}=\%fnt;
+ }
+
+ if (defined($fnt{encoding}) and $fnt{encoding} eq 'text.enc' and $ucmap ne '')
+ {
+ if ($textenccmap eq '')
+ {
+ $textenccmap = BuildObj($objct+1,{});
+ $objct++;
+ $obj[$objct]->{STREAM}=$ucmap;
+ }
+ $obj[$fno]->{DATA}->{'ToUnicode'}=$textenccmap;
+ }
+
+# PutObj($fno);
+# PutObj($fno+1);
+# PutObj($fno+2) if defined($obj[$fno+2]);
+# PutObj($fno+3) if defined($obj[$fno+3]);
+}
+
+sub GetType1
+{
+ my $file=shift;
+ my ($l1,$l2,$l3); # Return lengths
+ my ($head,$body,$tail); # Font contents
+ my $f;
+
+ OpenFile(\$f,$fontdir,"$file");
+ Die("unable to open font '$file' for embedding") if !defined($f);
+
+ $head=GetChunk($f,1,"currentfile eexec");
+ $body=GetChunk($f,2,"00000000") if !eof($f);
+ $tail=GetChunk($f,3,"cleartomark") if !eof($f);
+
+ $l1=length($head);
+ $l2=length($body);
+ $l3=length($tail);
+
+ return($l1,$l2,$l3,"$head$body$tail");
+}
+
+sub GetChunk
+{
+ my $F=shift;
+ my $segno=shift;
+ my $ascterm=shift;
+ my ($type,$hdr,$chunk,@msg);
+ binmode($F);
+ my $enc="ascii";
+
+ while (1)
+ {
+ # There may be multiple chunks of the same type
+
+ my $ct=read($F,$hdr,2);
+
+ if ($ct==2)
+ {
+ if (substr($hdr,0,1) eq "\x80")
+ {
+ # binary chunk
+
+ my $chunktype=ord(substr($hdr,1,1));
+ $enc="binary";
+
+ if (defined($type) and $type != $chunktype)
+ {
+ seek($F,-2,1);
+ last;
+ }
+
+ $type=$chunktype;
+ return if $chunktype == 3;
+
+ $ct=read($F,$hdr,4);
+ Die("failed to read binary segment length") if $ct != 4;
+ my $sl=unpack('V',$hdr);
+ my $data;
+ my $chk=read($F,$data,$sl);
+ Die("failed to read binary segment") if $chk != $sl;
+ $chunk.=$data;
+ }
+ else
+ {
+ # ascii chunk
+
+ my $hex=0;
+ seek($F,-2,1);
+ my $ct=0;
+
+ while (1)
+ {
+ my $lin=<$F>;
+
+ last if !$lin;
+
+ $hex=1,$enc.=" hex" if $segno == 2 and !$ct and $lin=~m/^[A-F0-9a-f]{4,4}/;
+
+ if ($segno !=2 and $lin=~m/^(.*$ascterm\n?)(.*)/)
+ {
+ $chunk.=$1;
+ seek($F,-length($2)-1,1) if $2;
+ last;
+ }
+ elsif ($segno == 2 and $lin=~m/^(.*?)($ascterm.*)/)
+ {
+ $chunk.=$1;
+ seek($F,-length($2)-1,1) if $2;
+ last;
+ }
+
+ chomp($lin), $lin=pack('H*',$lin) if $hex;
+ $chunk.=$lin; $ct++;
+ }
+
+ last;
+ }
+ }
+ else
+ {
+ push(@msg,"Failed to read 2 header bytes");
+ }
+ }
+
+ return $chunk;
+}
+
+sub OutStream
+{
+ my $ono=shift;
+
+ IsGraphic();
+ $stream.="Q\n";
+ $obj[$ono]->{STREAM}=$stream;
+ $obj[$ono]->{DATA}->{Length}=length($stream);
+ $stream='';
+ PutObj($ono);
+}
+
+sub do_p
+{
+ my $trans='BLOCK';
+
+ $trans='PAGE' if $firstpause;
+ NewPage($trans);
+ @XOstream=();
+ @PageAnnots=();
+ $firstpause=1;
+}
+
+sub FixTrans
+{
+ my $t=shift;
+ my $style=$t->{S};
+
+ if ($style)
+ {
+ delete($t->{Dm}) if $style ne '/Split' and $style ne '/Blinds';
+ delete($t->{M}) if !($style eq '/Split' or $style eq '/Box' or $style eq '/Fly');
+ delete($t->{Di}) if !($style eq '/Wipe' or $style eq '/Glitter' or $style eq '/Fly' or $style eq '/Cover' or $style eq '/Uncover' or $style eq '/Push') or ($style eq '/Fly' and $t->{Di} eq '/None' and $t->{SS} != 1);
+ delete($t->{SS}) if !($style eq '/Fly');
+ delete($t->{B}) if !($style eq '/Fly');
+ }
+
+ return($t);
+}
+
+sub NewPage
+{
+ my $trans=shift;
+ # Start of pages
+
+ if ($cpageno > 0)
+ {
+ if ($#XOstream>=0)
+ {
+ MakeXO() if $stream;
+ $stream=join("\n",@XOstream,'');
+ }
+
+ my %t=%{$transition->{$trans}};
+ $cpage->{MediaBox}=\@mediabox if $custompaper;
+ $cpage->{Trans}=FixTrans(\%t) if $t{S};
+
+ if ($#PageAnnots >= 0)
+ {
+ @{$cpage->{Annots}}=@PageAnnots;
+ }
+
+ if ($#bgstack > -1 or $bgbox)
+ {
+ my $box="q 1 0 0 1 0 0 cm ";
+
+ foreach my $bg (@bgstack)
+ {
+ # 0=$bgtype # 1=stroke 2=fill. 4=page
+ # 1=$strkcol
+ # 2=$fillcol
+ # 3=(Left,Top,Right,bottom,LineWeight)
+ # 4=Start ypos
+ # 5=Endypos
+ # 6=Line Weight
+
+ my $pg=$bg->[3] || \@defaultmb;
+
+ $bg->[5]=$pg->[3]; # box is continuing to next page
+ $box.=DrawBox($bg);
+ $bg->[4]=$pg->[1]; # will continue from page top
+ }
+
+ $stream=$box.$bgbox."Q\n".$stream;
+ $bgbox='';
+ $boxmax=0;
+ }
+
+ PutObj($cpageno);
+ OutStream($cpageno+1);
+ }
+
+ $cpageno=++$objct;
+
+ my $thispg=BuildObj($objct,
+ {'Type' => '/Page',
+ 'Group' => {'CS' => '/DeviceRGB', 'S' => '/Transparency'},
+ 'Parent' => '2 0 R',
+ 'Contents' => [ BuildObj($objct+1,
+ {'Length' => 0}
+ ) ],
+ }
+ );
+
+ splice(@{$pages->{Kids}},++$pginsert,0,$thispg);
+ splice(@outlines,$pginsert,0,[$curoutlev,$#{$curoutlev}+1,$thislev]);
+
+ $objct+=1;
+ $cpage=$obj[$cpageno]->{DATA};
+ $pages->{'Count'}++;
+ $stream="q 1 0 0 1 0 0 cm\n$linejoin J\n$linecap j\n0.4 w\n";
+ $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne '';
+ $mode='g';
+ $curfill='';
+# @mediabox=@defaultmb;
+}
+
+sub DrawBox
+{
+ my $bg=shift;
+ my $res='';
+ my $pg=$bg->[3] || \@mediabox;
+ $bg->[4]=$pg->[1], $bg->[5]=$pg->[3] if $bg->[0] & 4;
+ my $bot=$bg->[5];
+ $bot=$boxmax if $boxmax > $bot;
+ my $wid=$pg->[2]-$pg->[0];
+ my $dep=$bot-$bg->[4];
+
+ $res="$bg->[1] $bg->[2] $bg->[6] w\n";
+ $res.="$pg->[0] $bg->[4] $wid $dep re f " if $bg->[0] & 1;
+ $res.="$pg->[0] $bg->[4] $wid $dep re s " if $bg->[0] & 2;
+ return("$res\n");
+}
+
+sub MakeXO
+{
+ $stream.="%mode=$mode\n";
+ IsGraphic();
+ $stream.="Q\n";
+ my $xobj=++$objct;
+ my $xonm="XO$xobj";
+ $pages->{'Resources'}->{'XObject'}->{$xonm}=BuildObj($xobj,{'Type' => '/XObject', 'BBox' => \@mediabox, 'Name' => "/$xonm", 'FormType' => 1, 'Subtype' => '/Form', 'Length' => 0, 'Type' => "/XObject"});
+ $obj[$xobj]->{STREAM}=$stream;
+ $stream='';
+ push(@XOstream,"q") if $#XOstream==-1;
+ push(@XOstream,"/$xonm Do");
+}
+
+sub do_f
+{
+ my $par=shift;
+ my $fnt=$fontlst{$par}->{FNT};
+
+# IsText();
+ $cft="$par";
+ $fontchg=1;
+# $stream.="/F$cft $cftsz Tf\n" if $cftsz;
+ $widtbl=CacheWid($par);
+ $origwidtbl=[];
+
+ foreach my $w (@{$fnt->{NO}})
+ {
+ push(@{$origwidtbl},$fnt->{NAM}->{$w->[1]}->[WIDTH]);
+ }
+
+# $krntbl=$fnt->{KERN};
+}
+
+sub CacheWid
+{
+ my $par=shift;
+
+ if (!defined($fontlst{$par}->{CACHE}->{$cftsz}))
+ {
+ $fontlst{$par}->{CACHE}->{$cftsz}=BuildCache($fontlst{$par}->{FNT});
+ }
+
+ return($fontlst{$par}->{CACHE}->{$cftsz});
+}
+
+sub BuildCache
+{
+ my $fnt=shift;
+ my @cwid;
+ $origwidtbl=[];
+
+ foreach my $w (@{$fnt->{NO}})
+ {
+ my $wid=(defined($w) and defined($w->[1]))?$fnt->{NAM}->{$w->[1]}->[WIDTH]:0;
+ push(@cwid,$wid*$cftsz);
+ push(@{$origwidtbl},$wid);
+ }
+
+ return(\@cwid);
+}
+
+sub IsText
+{
+ if ($mode eq 'g')
+ {
+ $xpos+=$pendmv/$unitwidth;
+ $stream.="q BT\n$matrix ".PutXY($xpos,$ypos)." Tm\n";
+ $poschg=0;
+ $fontchg=0;
+ $pendmv=0;
+ $matrixchg=0;
+ $tmxpos=$xpos;
+ $stream.=$textcol."\n", $curfill=$textcol if $textcol ne $curfill;
+ if (defined($cft))
+ {
+ $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
+ $stream.="/F$cft $cftsz Tf\n";
+ }
+ $stream.="$curkern Tc\n";
+ }
+
+ if ($poschg or $matrixchg)
+ {
+ PutLine(0) if $matrixchg;
+ $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
+ $tmxpos=$xpos;
+ $matrixchg=0;
+ $stream.="$curkern Tc\n";
+ }
+
+ if ($fontchg)
+ {
+ PutLine(0);
+ $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
+ $stream.="/F$cft $cftsz Tf\n" if $cftsz and defined($cft);
+ $fontchg=0;
+ }
+
+ $mode='t';
+}
+
+sub IsGraphic
+{
+ if ($mode eq 't')
+ {
+ PutLine();
+ $stream.="ET Q\n";
+ $xpos+=($pendmv-$nomove)/$unitwidth;
+ $pendmv=0;
+ $nomove=0;
+ $stream.=$strkcol."\n", $curstrk=$strkcol if $strkcol ne $curstrk;
+ $curfill=$fillcol;
+ }
+ $mode='g';
+}
+
+sub do_s
+{
+ my $par=shift;
+ $par/=$unitwidth;
+
+ if ($par != $cftsz and defined($cft))
+ {
+ PutLine();
+ $cftsz=$par;
+ Set_LWidth() if $lwidth < 1;
+# $stream.="/F$cft $cftsz Tf\n";
+ $fontchg=1;
+ $widtbl=CacheWid($cft);
+ }
+ else
+ {
+ $cftsz=$par;
+ Set_LWidth() if $lwidth < 1;
+ }
+}
+
+sub Set_LWidth
+{
+ IsGraphic();
+ $stream.=((($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000)." w\n";
+ return;
+}
+
+sub do_m
+{
+ # Groff uses /m[] for text & graphic stroke, and /M[] (DF?) for graphic fill.
+ # PDF uses G/RG/K for graphic stroke, and g/rg/k for text & graphic fill.
+ #
+ # This means that we must maintain g/rg/k state separately for text colour & graphic fill (this is
+ # probably why 'gs' maintains separate graphic states for text & graphics when distilling PS -> PDF).
+ #
+ # To facilitate this:-
+ #
+ # $textcol = current groff stroke colour
+ # $fillcol = current groff fill colour
+ # $curfill = current PDF fill colour
+
+ my $par=shift;
+ my $mcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $par=~s/^ +//;
+
+# IsGraphic();
+
+ $textcol=set_col($mcmd,$par,0);
+ $strkcol=set_col($mcmd,$par,1);
+
+ if ($mode eq 't')
+ {
+ PutLine();
+ $stream.=$textcol."\n";
+ $curfill=$textcol;
+ }
+ else
+ {
+ $stream.="$strkcol\n";
+ $curstrk=$strkcol;
+ }
+}
+
+sub set_col
+{
+ my $mcmd=shift;
+ my $par=shift;
+ my $upper=shift;
+ my @oper=('g','k','rg');
+
+ @oper=('G','K','RG') if $upper;
+
+ if ($mcmd eq 'd')
+ {
+ # default colour
+ return("0 $oper[0]");
+ }
+
+ my (@c)=split(' ',$par);
+
+ if ($mcmd eq 'c')
+ {
+ # Text CMY
+ return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535)." 0 $oper[1]");
+ }
+ elsif ($mcmd eq 'k')
+ {
+ # Text CMYK
+ return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535).' '.d3($c[3]/65535)." $oper[1]");
+ }
+ elsif ($mcmd eq 'g')
+ {
+ # Text Grey
+ return(d3($c[0]/65535)." $oper[0]");
+ }
+ elsif ($mcmd eq 'r')
+ {
+ # Text RGB0
+ return(d3($c[0]/65535).' '.d3($c[1]/65535).' '.d3($c[2]/65535)." $oper[2]");
+ }
+}
+
+sub do_D
+{
+ my $par=shift;
+ my $Dcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+
+ IsGraphic();
+
+ if ($Dcmd eq 'F')
+ {
+ my $mcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $par=~s/^ +//;
+
+ $fillcol=set_col($mcmd,$par,0);
+ $stream.="$fillcol\n";
+ $curfill=$fillcol;
+ }
+ elsif ($Dcmd eq 'f')
+ {
+ my $mcmd=substr($par,0,1);
+
+ $par=substr($par,1);
+ $par=~s/^ +//;
+ ($par)=split(' ',$par);
+
+ if ($par >= 0 and $par <= 1000)
+ {
+ $fillcol=set_col('g',int((1000-$par)*65535/1000),0);
+ }
+ else
+ {
+ $fillcol=lc($textcol);
+ }
+
+ $stream.="$fillcol\n";
+ $curfill=$fillcol;
+ }
+ elsif ($Dcmd eq '~')
+ {
+ # B-Spline
+ my (@p)=split(' ',$par);
+ my ($nxpos,$nypos);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ $stream.=PutXY($xpos,$ypos)." m\n";
+ $xpos+=($p[0]/2);
+ $ypos+=($p[1]/2);
+ $stream.=PutXY($xpos,$ypos)." l\n";
+
+ for (my $i=0; $i < $#p-1; $i+=2)
+ {
+ $nxpos=(($p[$i]*$tnum)/(2*$tden));
+ $nypos=(($p[$i+1]*$tnum)/(2*$tden));
+ $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." ";
+ $nxpos=($p[$i]/2 + ($p[$i+2]*($tden-$tnum))/(2*$tden));
+ $nypos=($p[$i+1]/2 + ($p[$i+3]*($tden-$tnum))/(2*$tden));
+ $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." ";
+ $nxpos=(($p[$i]-$p[$i]/2) + $p[$i+2]/2);
+ $nypos=(($p[$i+1]-$p[$i+1]/2) + $p[$i+3]/2);
+ $stream.=PutXY(($xpos+$nxpos),($ypos+$nypos))." c\n";
+ $xpos+=$nxpos;
+ $ypos+=$nypos;
+ }
+
+ $xpos+=($p[$#p-1]-$p[$#p-1]/2);
+ $ypos+=($p[$#p]-$p[$#p]/2);
+ $stream.=PutXY($xpos,$ypos)." l\nS\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'p' or $Dcmd eq 'P')
+ {
+ # Polygon
+ my (@p)=split(' ',$par);
+ my ($nxpos,$nypos);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ $stream.=PutXY($xpos,$ypos)." m\n";
+
+ for (my $i=0; $i < $#p; $i+=2)
+ {
+ $xpos+=($p[$i]);
+ $ypos+=($p[$i+1]);
+ $stream.=PutXY($xpos,$ypos)." l\n";
+ }
+
+ if ($Dcmd eq 'p')
+ {
+ $stream.="s\n";
+ }
+ else
+ {
+ $stream.="f\n";
+ }
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'c')
+ {
+ # Stroke circle
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[0]);
+ $stream.="s\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'C')
+ {
+ # Fill circle
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[0]);
+ $stream.="f\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'e')
+ {
+ # Stroke ellipse
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[1]);
+ $stream.="s\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'E')
+ {
+ # Fill ellipse
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ DrawCircle($p[0],$p[1]);
+ $stream.="f\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 'l')
+ {
+ # Line To
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ $stream.=PutXY($xpos,$ypos)." m\n";
+ $xpos+=$p[0];
+ $ypos+=$p[1];
+ $stream.=PutXY($xpos,$ypos)." l\n";
+
+ $stream.="S\n";
+ $poschg=1;
+ }
+ elsif ($Dcmd eq 't')
+ {
+ # Line Thickness
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+ # $xpos+=$p[0]*100; # WTF!!!
+ #int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000;
+ $p[0]=(($desc{res}/(72*$desc{sizescale}))*$linewidth*$cftsz)/1000 if $p[0] < 0;
+ $lwidth=$p[0];
+ $stream.="$p[0] w\n";
+ $poschg=1;
+ $xpos+=$lwidth;
+ }
+ elsif ($Dcmd eq 'a')
+ {
+ # Arc
+ $par=substr($par,1);
+ my (@p)=split(' ',$par);
+ my $rad180=3.14159;
+ my $rad360=$rad180*2;
+ my $rad90=$rad180/2;
+
+ foreach my $p (@p) { $p/=$unitwidth; }
+
+ # Documentation is wrong. Groff does not use Dh1,Dv1 as centre of the circle!
+
+ my $centre=adjust_arc_centre(\@p);
+
+ # Using formula here : http://www.tinaja.com/glib/bezcirc2.pdf
+ # First calculate angle between start and end point
+
+ my ($startang,$r)=RtoP(-$centre->[0],$centre->[1]);
+ my ($endang,$r2)=RtoP(($p[0]+$p[2])-$centre->[0],-($p[1]+$p[3]-$centre->[1]));
+ $endang+=$rad360 if $endang < $startang;
+ my $totang=($endang-$startang)/4; # do it in 4 pieces
+
+ # Now 1 piece
+
+ my $x0=cos($totang/2);
+ my $y0=sin($totang/2);
+ my $x3=$x0;
+ my $y3=-$y0;
+ my $x1=(4-$x0)/3;
+ my $y1=((1-$x0)*(3-$x0))/(3*$y0);
+ my $x2=$x1;
+ my $y2=-$y1;
+
+ # Rotate to start position and draw 4 pieces
+
+ foreach my $j (0..3)
+ {
+ PlotArcSegment($totang/2+$startang+$j*$totang,$r,$xpos+$centre->[0],GraphY($ypos+$centre->[1]),$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3);
+ }
+
+ $xpos+=$p[0]+$p[2];
+ $ypos+=$p[1]+$p[3];
+
+ $poschg=1;
+ }
+}
+
+sub deg
+{
+ return int($_[0]*180/3.14159);
+}
+
+sub adjust_arc_centre
+{
+ # Taken from geometry.cpp
+
+ # We move the center along a line parallel to the line between
+ # the specified start point and end point so that the center
+ # is equidistant between the start and end point.
+ # It can be proved (using Lagrange multipliers) that this will
+ # give the point nearest to the specified center that is equidistant
+ # between the start and end point.
+
+ my $p=shift;
+ my @c;
+ my $x = $p->[0] + $p->[2]; # (x, y) is the end point
+ my $y = $p->[1] + $p->[3];
+ my $n = $x*$x + $y*$y;
+ if ($n != 0)
+ {
+ $c[0]= $p->[0];
+ $c[1] = $p->[1];
+ my $k = .5 - ($c[0]*$x + $c[1]*$y)/$n;
+ $c[0] += $k*$x;
+ $c[1] += $k*$y;
+ return(\@c);
+ }
+ else
+ {
+ return(undef);
+ }
+}
+
+
+sub PlotArcSegment
+{
+ my ($ang,$r,$transx,$transy,$x0,$y0,$x1,$y1,$x2,$y2,$x3,$y3)=@_;
+ my $cos=cos($ang);
+ my $sin=sin($ang);
+ my @mat=($cos,$sin,-$sin,$cos,0,0);
+ my $lw=$lwidth/$r;
+
+ $stream.="q $r 0 0 $r $transx $transy cm ".join(' ',@mat)." cm $lw w $x0 $y0 m $x1 $y1 $x2 $y2 $x3 $y3 c S Q\n";
+}
+
+sub DrawCircle
+{
+ my $hd=shift;
+ my $vd=shift;
+ my $hr=$hd/2/$unitwidth;
+ my $vr=$vd/2/$unitwidth;
+ my $kappa=0.5522847498;
+ $hd/=$unitwidth;
+ $vd/=$unitwidth;
+
+
+ $stream.=PutXY(($xpos+$hd),$ypos)." m\n";
+ $stream.=PutXY(($xpos+$hd),($ypos+$vr*$kappa))." ".PutXY(($xpos+$hr+$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos+$hr),($ypos+$vr))." c\n";
+ $stream.=PutXY(($xpos+$hr-$hr*$kappa),($ypos+$vr))." ".PutXY(($xpos),($ypos+$vr*$kappa))." ".PutXY(($xpos),($ypos))." c\n";
+ $stream.=PutXY(($xpos),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hr-$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hr),($ypos-$vr))." c\n";
+ $stream.=PutXY(($xpos+$hr+$hr*$kappa),($ypos-$vr))." ".PutXY(($xpos+$hd),($ypos-$vr*$kappa))." ".PutXY(($xpos+$hd),($ypos))." c\n";
+ $xpos+=$hd;
+
+ $poschg=1;
+}
+
+sub FindCircle
+{
+ my ($x1,$y1,$x2,$y2,$x3,$y3)=@_;
+ my ($Xo, $Yo);
+
+ my $x=$x2+$x3;
+ my $y=$y2+$y3;
+ my $n=$x**2+$y**2;
+
+ if ($n)
+ {
+ my $k=.5-($x2*$x + $y2*$y)/$n;
+ return(sqrt($n),$x2+$k*$x,$y2+$k*$y);
+ }
+ else
+ {
+ return(-1);
+ }
+
+}
+
+sub PtoR
+{
+ my ($theta,$r)=@_;
+
+ return($r*cos($theta),$r*sin($theta));
+}
+
+sub RtoP
+{
+ my ($x,$y)=@_;
+
+ return(atan2($y,$x),sqrt($x**2+$y**2));
+}
+
+sub PutLine
+{
+
+ my $f=shift;
+
+ IsText() if !defined($f);
+
+ return if (scalar(@lin) == 0) or (!defined($lin[0]->[0]) and $#lin == 0);
+
+# $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug;
+ $pendmv-=$nomove;
+ $lin[$#lin]->[1]=-$pendmv/$cftsz if ($pendmv != 0);
+
+ foreach my $wd (@lin)
+ {
+ next if !defined($wd->[0]);
+ $wd->[0]=~s/\\/\\\\/g;
+ $wd->[0]=~s/\(/\\(/g;
+ $wd->[0]=~s/\)/\\)/g;
+ $wd->[0]=~s/!\|!\|/\\/g;
+ $wd->[1]=d3($wd->[1]);
+ }
+
+ if (0)
+ {
+ if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0))
+ {
+ $stream.="($lin[0]->[0]) Tj\n";
+ }
+ else
+ {
+ $stream.="[";
+
+ foreach my $wd (@lin)
+ {
+ $stream.="($wd->[0]) " if defined($wd->[0]);
+ $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
+ }
+
+ $stream.="] TJ\n";
+ }
+ }
+ else
+ {
+ if (scalar(@lin) == 1 and (!defined($lin[0]->[1]) or $lin[0]->[1] == 0))
+ {
+ $stream.="0 Tw ($lin[0]->[0]) Tj\n";
+ }
+ else
+ {
+ if ($wt>=-1 or $#lin == 0 or $lin[0]->[1]>=0)
+ {
+ $stream.="0 Tw [";
+
+ foreach my $wd (@lin)
+ {
+ $stream.="($wd->[0]) " if defined($wd->[0]);
+ $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
+ }
+
+ $stream.="] TJ\n";
+ }
+ else
+ {
+ # $stream.="\%dg 0 Tw [";
+ #
+ # foreach my $wd (@lin)
+ # {
+ # $stream.="($wd->[0]) " if defined($wd->[0]);
+ # $stream.="$wd->[1] " if defined($wd->[1]) and $wd->[1] != 0;
+ # }
+ #
+ # $stream.="] TJ\n";
+ #
+ # my $wt=$lin[0]->[1]||0;
+
+ # while ($wt < -$whtsz/$cftsz)
+ # {
+ # $wt+=$whtsz/$cftsz;
+ # }
+
+ $stream.=sprintf( "%.3f Tw ",-($whtsz+$wt*$cftsz)/$unitwidth-$curkern );
+ if (!defined($lin[0]->[0]) and defined($lin[0]->[1]))
+ {
+ $stream.="[ $lin[0]->[1] (";
+ shift @lin;
+ }
+ else
+ {
+ $stream.="[(";
+ }
+
+ foreach my $wd (@lin)
+ {
+ my $wwt=$wd->[1]||0;
+
+ while ($wwt <= $wt+.1)
+ {
+ $wwt-=$wt;
+ $wd->[0].=' ';
+ }
+
+ if (abs($wwt) < .1 or $wwt == 0)
+ {
+ $stream.="$wd->[0]" if defined($wd->[0]);
+ }
+ else
+ {
+ $wwt=sprintf("%.3f",$wwt);
+ $stream.="$wd->[0]) $wwt (" if defined($wd->[0]);
+ }
+ }
+ $stream.=")] TJ\n";
+ }
+ }
+ }
+
+ @lin=();
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+ $nomove=0;
+ $wt=-1;
+}
+
+sub d3
+{
+ return(sprintf("%.3f",shift || 0));
+}
+
+sub LoadAhead
+{
+ my $no=shift;
+
+ foreach my $j (1..$no)
+ {
+ my $lin=<>;
+ chomp($lin);
+ $lin=~s/\r$//;
+ $lct++;
+
+ push(@ahead,$lin);
+ $stream.="%% $lin\n" if $debug;
+ }
+}
+
+sub do_V
+{
+ my $par=shift;
+
+ if ($mode eq 't')
+ {
+ PutLine();
+ }
+ else
+ {
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+ }
+
+ $ypos=$par/$unitwidth;
+
+ LoadAhead(1);
+
+ if (substr($ahead[0],0,1) eq 'H')
+ {
+ $xpos=substr($ahead[0],1)/$unitwidth;
+
+ $nomove=$pendmv=0;
+ @ahead=();
+
+ }
+
+ $poschg=1;
+}
+
+sub do_v
+{
+ my $par=shift;
+
+ PutLine() if $mode eq 't';
+
+ $ypos+=$par/$unitwidth;
+
+ $poschg=1;
+}
+
+sub TextWid
+{
+ my $txt=shift;
+ my $fnt=shift;
+ my $w=0;
+ my $ck=0;
+
+ foreach my $c (split('',$txt))
+ {
+ my $cn=ord($c);
+ $widtbl->[$cn]=$origwidtbl->[$cn]*$cftsz if !defined($widtbl->[$cn]);
+ $w+=$widtbl->[$cn];
+ }
+
+ $ck=length($txt)*$curkern;
+
+ return(($w/$unitwidth)+$ck);
+}
+
+sub do_t
+{
+ my $par=shift;
+ my $fnt=$fontlst{$cft}->{FNT};
+
+ if ($kernadjust != $curkern)
+ {
+ PutLine();
+ $stream.="$kernadjust Tc\n";
+ $curkern=$kernadjust;
+ }
+
+ my $par2=$par;
+ $par2=~s/^!\|!\|(\d\d\d)/chr(oct($1))/e;
+
+ foreach my $j (0..length($par2)-1)
+ {
+ my $cn=ord(substr($par2,$j,1));
+ my $chnm=$fnt->{NAM}->{$fnt->{NO}->[$cn]->[1]};
+
+ if ($chnm->[USED]==0)
+ {
+ $chnm->[USED]=1;
+ }
+ elsif ($fnt->{NO}->[$cn]->[0] ne $fnt->{NO}->[$cn]->[1])
+ {
+ # A glyph has already been remapped to this char, so find a spare
+
+ my $cn2=RemapChr($cn,$fnt,$fnt->{NO}->[$cn]->[0]);
+ $stream.="% MMM Remap $cn to $cn2\n" if $debug;
+
+ if ($cn2)
+ {
+ substr($par2,$j,1)=chr($cn2);
+
+ if ($par=~m/^!\|!\|(\d\d\d)/)
+ {
+ substr($par,4,3)=sprintf("%03o",$cn2);
+ }
+ else
+ {
+ substr($par,$j,1)=chr($cn2);
+ }
+ }
+ }
+ }
+ my $wid=TextWid($par2,$fnt);
+
+ $par=reverse(split('',$par)) if $xrev and $par!~m/^!\|!\|(\d\d\d)/;
+
+ if ($n_flg and defined($mark))
+ {
+ $mark->{ypos}=$ypos;
+ $mark->{xpos}=$xpos;
+ }
+
+ $n_flg=0;
+ IsText();
+
+ $xpos+=$wid;
+ $xpos+=($pendmv-$nomove)/$unitwidth;
+
+ $stream.="% == '$par'=$wid 'xpos=$xpos\n" if $debug;
+
+ # $pendmv = 'h' move since last 't'
+ # $nomove = width of char(s) added by 'C', 'N' or 'c'
+ # $w-flg = 'w' seen since last t
+
+ if ($fontchg)
+ {
+ PutLine();
+ $whtsz=$fontlst{$cft}->{FNT}->{spacewidth}*$cftsz;
+ $stream.="/F$cft $cftsz Tf\n", $fontchg=0 if $fontchg && defined($cft);
+ }
+
+ $gotT=1;
+
+ $stream.="% --- wht=$whtsz, pend=$pendmv, nomv=$nomove\n" if $debug;
+
+# if ($w_flg && $#lin > -1)
+# {
+# $lin[$#lin]->[0].=' ';
+# $pendmv-=$whtsz;
+# $dontglue=1 if $pendmv==0;
+# }
+
+ $wt=-$pendmv/$cftsz if $w_flg and $wt==-1;
+ $pendmv-=$nomove;
+ $nomove=0;
+ $w_flg=0;
+
+ if ($xrev)
+ {
+ PutLine(0) if $#lin > -1;
+ MakeMatrix(1);
+ $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
+ $stream.="$curkern Tc\n";
+ $stream.="0 Tw ";
+ $stream.="($par) Tj\n";
+ MakeMatrix();
+ $stream.="$matrix ".PutXY($xpos,$ypos)." Tm\n", $poschg=0;
+ $matrixchg=0;
+ $stream.="$curkern Tc\n";
+ return;
+ }
+
+ if ($pendmv)
+ {
+ if ($#lin == -1)
+ {
+ push(@lin,[undef,-$pendmv/$cftsz]);
+ }
+ else
+ {
+ $lin[$#lin]->[1]=-$pendmv/$cftsz;
+ }
+
+ push(@lin,[$par,undef]);
+# $xpos+=$pendmv/$unitwidth;
+ $pendmv=0
+ }
+ else
+ {
+ if ($#lin == -1)
+ {
+ push(@lin,[$par,undef]);
+ }
+ else
+ {
+ $lin[$#lin]->[0].=$par;
+ }
+ }
+}
+
+sub do_u
+{
+ my $par=shift;
+
+ $par=m/([+-]?\d+) (.*)/;
+ $kernadjust=$1/$unitwidth;
+ do_t($2);
+ $kernadjust=0;
+}
+
+sub do_h
+{
+ $pendmv+=shift;
+}
+
+sub do_H
+{
+ my $par=shift;
+
+ if ($mode eq 't')
+ {
+ PutLine();
+ }
+ else
+ {
+ $xpos+=$pendmv/$unitwidth;
+ $pendmv=0;
+ }
+
+ my $newx=$par/$unitwidth;
+ $stream.=sprintf("%.3f",$newx-$tmxpos)." 0 Td\n" if $mode eq 't';
+ $tmxpos=$xpos=$newx;
+ $pendmv=$nomove=0;
+}
+
+sub do_C
+{
+ my $par=shift;
+
+ my ($par2,$nm)=FindChar($par);
+
+ do_t($par2);
+ $nomove=$fontlst{$cft}->{FNT}->{NAM}->{$par}->[WIDTH]*$cftsz ;
+}
+
+sub FindChar
+{
+ my $chnm=shift;
+ my $fnt=$fontlst{$cft}->{FNT};
+
+ if (exists($fnt->{NAM}->{$chnm}))
+ {
+ my $ch=$fnt->{NAM}->{$chnm}->[ASSIGNED];
+ $ch=RemapChr($ch,$fnt,$chnm) if ($ch > 255);
+ $fnt->{NAM}->{$chnm}->[USED]=0 if $fnt->{NO}->[$ch]->[1] eq $chnm;
+
+ return(($ch<32)?sprintf("!|!|%03o",$ch):chr($ch),$widtbl->[$ch]);
+ }
+ else
+ {
+ return(' ');
+ }
+}
+
+sub RemapChr
+{
+ my $ch=shift;
+ my $fnt=shift;
+ my $chnm=shift;
+ my $unused=0;
+
+ foreach my $un (0..$#{$fnt->{NO}})
+ {
+ next if $un >= 139 and $un <= 144;
+ $unused=$un,last if $fnt->{NO}->[$un]->[1] eq '';
+ }
+
+ if (!$unused)
+ {
+ foreach my $un (128..255)
+ {
+ next if $un >= 139 and $un <= 144;
+ my $glyph=$fnt->{NO}->[$un]->[1];
+ $unused=$un,last if $fnt->{NAM}->{$glyph}->[USED] == 0;
+ }
+ }
+
+ if ($unused && $unused <= 255)
+ {
+ my $glyph=$fnt->{NO}->[$unused]->[1];
+ delete($fontlst{$cft}->{CACHE}->{$cftsz});
+ $fnt->{NAM}->{$chnm}->[ASSIGNED]=$unused;
+ $fnt->{NAM}->{$chnm}->[USED]=1;
+ $fnt->{NO}->[$unused]->[1]=$chnm;
+ $widtbl=CacheWid($cft);
+
+ $stream.="% AAA Assign $chnm ($ch) to $unused\n" if $debug;
+
+ $ch=$unused;
+ return($ch);
+ }
+ else
+ {
+ Warn("too many glyphs used in font '$cft'");
+ return(32);
+ }
+}
+
+sub do_c
+{
+ my $par=shift;
+
+ push(@ahead,substr($par,1));
+ $par=substr($par,0,1);
+ my $ch=ord($par);
+ do_N($ch);
+}
+
+sub do_N
+{
+ my $par=shift;
+ my $fnt=$fontlst{$cft}->{FNT};
+
+ if (!defined($fnt->{NO}->[$par]))
+ {
+ Warn("no chr($par) in font $fnt->{internalname}");
+ return;
+ }
+
+ my $chnm=$fnt->{NO}->[$par]->[0];
+ do_C($chnm);
+}
+
+sub do_n
+{
+ $gotT=0;
+ PutLine(0);
+ $pendmv=$nomove=0;
+ $n_flg=1;
+ @lin=();
+ PutHotSpot($xpos) if defined($mark);
+}
+
+1;
+
+# Local Variables:
+# fill-column: 72
+# mode: CPerl
+# End:
+# vim: set cindent noexpandtab shiftwidth=4 softtabstop=4 textwidth=72:
diff --git a/src/devices/gropdf/pdfmom.1.man b/src/devices/gropdf/pdfmom.1.man
new file mode 100644
index 0000000..08d789c
--- /dev/null
+++ b/src/devices/gropdf/pdfmom.1.man
@@ -0,0 +1,229 @@
+.TH pdfmom @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+pdfmom \- produce PDF documents using the
+.I mom
+macro package for
+.I groff
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 2012-2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_pdfmom_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY pdfmom
+.RB [ \-Tpdf ]
+.RI [ groff-options ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY pdfmom
+.B \-Tps
+.RI [ pdfroff-options ]
+.RI [ groff-options ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY pdfmom
+.B \-v
+.
+.SY pdfmom
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I pdfmom
+is a wrapper around
+.MR groff @MAN1EXT@
+that facilitates the production of PDF documents from files
+formatted with the
+.I mom
+macros.
+.
+.
+.P
+.I pdfmom
+prints to the standard output,
+so output must usually be redirected to a destination file.
+.
+The size of the final PDF can be reduced by piping the output
+through
+.MR ps2pdf 1 .
+.
+.
+.P
+If called with the
+.B \-Tpdf
+option (which is the default),
+.I pdfmom
+processes files using
+.IR groff 's
+native PDF driver,
+.MR gropdf @MAN1EXT@ .
+.
+If
+.B \-Tps
+is given,
+processing is passed over to
+.IR pdfroff ,
+which uses
+.IR groff 's
+PostScript driver.
+.
+In either case,
+multiple runs of the source file are performed in order to satisfy any
+forward references in the document.
+.
+.
+.P
+.I pdfmom
+accepts all the same options as
+.IR groff .
+.
+If
+.B \-Tps
+is given,
+the options associated with
+.I pdfroff
+are accepted as well.
+.
+When
+.I pdfmom
+calls
+.IR pdfroff ,
+the options
+.RB \[lq] "\-mpdfmark \-mom \-\-no\-toc" \[rq]
+options are implied and should not be given on the command line.
+.
+Equally,
+it is not necessary to supply the
+.B \-mom
+or
+.B "\-m\~mom"
+options when
+.B \-Tps
+is absent.
+.
+.
+.P
+PDF integration with the
+.I mom
+macros is discussed in full in the manual
+\[lq]Producing PDFs with
+.I groff
+and
+.IR mom \[rq],
+which was itself produced with
+.IR pdfmom .
+.
+.
+.P
+If called with the
+.B \-v
+or
+.B \-\-version
+options,
+.I pdfmom
+displays its version information and exits.
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I pdfmom
+was written by
+.MT deri@\:chuzzlewit\:.myzen\:.co\:.uk
+Deri James
+.ME
+and
+.MT peter@\:schaffter\:.ca
+Peter Schaffter
+.ME ,
+and is maintained by James.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.TP
+.I @PDFDOCDIR@/\:mom\-pdf.pdf
+\[lq]Producing PDFs with
+.I groff
+and
+.IR mom \[rq],
+by Deri James and Peter Schaffter.
+.
+This file,
+together with its source,
+.IR mom\-pdf.mom ,
+is part of the
+.I groff
+distribution.
+.
+.
+.P
+.MR groff @MAN1EXT@ ,
+.MR gropdf @MAN1EXT@ ,
+.MR pdfroff @MAN1EXT@ ,
+.MR ps2pdf 1
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_pdfmom_1_man_C]
+.do rr *groff_pdfmom_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/gropdf/pdfmom.pl b/src/devices/gropdf/pdfmom.pl
new file mode 100644
index 0000000..89977d4
--- /dev/null
+++ b/src/devices/gropdf/pdfmom.pl
@@ -0,0 +1,150 @@
+#!@PERL@
+#
+# pdfmom : Frontend to run groff -mom to produce PDFs
+# Deri James : Friday 16 Mar 2012
+#
+
+# Copyright (C) 2012-2020 Free Software Foundation, Inc.
+# Written by Deri James <deri@chuzzlewit.myzen.co.uk>
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use File::Temp qw/tempfile/;
+my @cmd;
+my $dev='pdf';
+my $preconv='';
+my $readstdin=1;
+my $RT_SEP='@RT_SEP@';
+
+$ENV{PATH}=$ENV{GROFF_BIN_PATH}.$RT_SEP.$ENV{PATH} if exists($ENV{GROFF_BIN_PATH});
+$ENV{TMPDIR}=$ENV{GROFF_TMPDIR} if exists($ENV{GROFF_TMPDIR});
+
+while (my $c=shift)
+{
+ $c=~s/(?<!\\)"/\\"/g;
+
+ if (substr($c,0,2) eq '-T')
+ {
+ if (length($c) > 2)
+ {
+ $dev=substr($c,2);
+ }
+ else
+ {
+ $dev=shift;
+ }
+ next;
+ }
+ elsif (substr($c,0,2) eq '-K')
+ {
+ if (length($c) > 2)
+ {
+ $preconv=$c;
+ }
+ else
+ {
+ $preconv=$c;
+ $preconv.=shift;
+ }
+ next;
+ }
+ elsif (substr($c,0,2) eq '-k')
+ {
+ $preconv=$c;
+ next;
+ }
+ elsif ($c eq '-z' or $c eq '-Z')
+ {
+ $dev=$c;
+ next;
+ }
+ elsif ($c eq '-v' or $c eq '--version')
+ {
+ print "GNU pdfmom (groff) version @VERSION@\n";
+ exit;
+ }
+ elsif (substr($c,0,1) eq '-')
+ {
+ if (length($c) > 1)
+ {
+ push(@cmd,"\"$c\"");
+ push(@cmd,"'".(shift)."'") if length($c)==2 and index('dDfFIKLmMnoPrwW',substr($c,-1)) >= 0;
+ }
+ else
+ {
+ # Just a '-'
+
+ push(@cmd,$c);
+ $readstdin=2;
+ }
+ }
+ else
+ {
+ # Got a filename?
+
+ push(@cmd,"\"$c\"");
+ $readstdin=0 if $readstdin == 1;
+
+ }
+
+}
+
+my $cmdstring=' '.join(' ',@cmd).' ';
+
+if ($readstdin)
+{
+ my ($fh,$tmpfn)=tempfile('pdfmom-XXXXX', UNLINK=>1);
+
+ while (<STDIN>)
+ {
+ print $fh ($_);
+ }
+
+ close($fh);
+
+ $cmdstring=~s/ - / $tmpfn / if $readstdin == 2;
+ $cmdstring.=" $tmpfn " if $readstdin == 1;
+}
+
+if ($dev eq 'pdf')
+{
+ system("groff -Tpdf -dLABEL.REFS=1 -mom -z $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | groff -Tpdf -dPDF.EXPORT=1 -dLABEL.REFS=1 -mom -z - $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | groff -Tpdf -mom $preconv - $cmdstring");
+}
+elsif ($dev eq 'ps')
+{
+ system("groff -Tpdf -dLABEL.REFS=1 -mom -z $cmdstring 2>&1 | LC_ALL=C grep '^\\. *ds' | pdfroff -mpdfmark -mom --no-toc - $preconv $cmdstring");
+}
+elsif ($dev eq '-z') # pseudo dev - just compile for warnings
+{
+ system("groff -Tpdf -mom -z $cmdstring");
+}
+elsif ($dev eq '-Z') # pseudo dev - produce troff output
+{
+ system("groff -Tpdf -mom -Z $cmdstring");
+}
+else
+{
+ print STDERR "Not compatible with device '-T $dev'\n";
+ exit 1;
+}
+
+# Local Variables:
+# fill-column: 72
+# mode: CPerl
+# End:
+# vim: set cindent noexpandtab shiftwidth=2 softtabstop=2 textwidth=72:
diff --git a/src/devices/grops/TODO b/src/devices/grops/TODO
new file mode 100644
index 0000000..eab8f83
--- /dev/null
+++ b/src/devices/grops/TODO
@@ -0,0 +1,24 @@
+Read PFB files directly.
+
+Generate %%For comment.
+
+Generate %%Title comment.
+
+Angles in arc command: don't generate more digits after the decimal
+point than are necessary.
+
+Possibly generate BoundingBox comment.
+
+Per font composite character mechanism (sufficient for fractions).
+
+Consider whether we ought to do rounding of graphical objects other
+than lines. What's the point?
+
+Error messages should refer to output page number.
+
+Search for downloadable fonts using their PostScript names if not
+found in download file.
+
+Separate path for searching for downloadable font files.
+
+Clip to the BoundingBox when importing documents.
diff --git a/src/devices/grops/grops.1.man b/src/devices/grops/grops.1.man
new file mode 100644
index 0000000..d0ec21d
--- /dev/null
+++ b/src/devices/grops/grops.1.man
@@ -0,0 +1,1831 @@
+.TH grops @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grops \-
+.I groff
+output driver for PostScript
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2018, 2020 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grops_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" This macro definition is poor style from a portability standpoint,
+.\" but it's a good test and demonstration of the standard font
+.\" repertoire for the devices where it has any effect at all, and so
+.\" should be retained.
+.de FT
+. if '\\*(.T'ps' .ft \\$1
+. if '\\*(.T'pdf' .ft \\$1
+..
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grops
+.RB [ \-glm ]
+.RB [ \-b\~\c
+.IR brokenness-flags ]
+.RB [ \-c\~\c
+.IR num-copies ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-p\~\c
+.IR paper-format ]
+.RB [ \-P\~\c
+.IR prologue-file ]
+.RB [ \-w\~\c
+.IR rule-thickness ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grops
+.B \-\-help
+.YS
+.
+.
+.SY grops
+.B \-v
+.
+.SY grops
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+PostScript output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into PostScript.
+.
+Normally,
+.I grops
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given the
+.RB \[lq] \-T\~ps \[rq]
+option.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grops .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grotty
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+When called with multiple
+.I file
+arguments,
+.I grops
+doesn't produce a valid document structure
+(one conforming to the Document Structuring Conventions).
+.
+To print such concatenated output,
+it is necessary to deactivate DSC handling in the printing program or
+previewer.
+.
+.
+.P
+See section \[lq]Font installation\[rq] below for a guide to installing
+fonts for
+.IR grops .
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.BI \-b\~ n
+Work around problems with spoolers,
+previewers,
+and older printers.
+.
+Normally,
+.I grops
+produces output at PostScript \%LanguageLevel\~2 that conforms to
+version 3.0 of the Document Structuring Conventions.
+.
+Some software and devices can't handle such a data stream.
+.
+The value
+.RI of\~ n
+determines what
+.I grops
+does to make its output acceptable to such consumers.
+.
+If
+.I n
+is
+.BR 0 ,
+.I grops
+employs no workarounds,
+which is the default;
+it can be changed by modifying the
+.B broken
+directive in
+.IR grops 's
+.I DESC
+file.
+.
+.
+.IP
+Add\~1 to suppress generation of
+.B %%Begin\%Document\%Setup
+and
+.B %%End\%Document\%Setup
+comments;
+this is needed for early versions of TranScript that get confused by
+anything between the
+.B %%End\%Prolog
+comment and the first
+.B %%Page
+comment.
+.
+.
+.IP
+Add\~2 to omit lines in included files beginning with
+.BR %!\& ,
+which confuse Sun's
+.I pageview
+previewer.
+.
+.
+.IP
+Add\~4 to omit lines in included files beginning with
+.BR %%Page ,
+.B %%Trailer
+and
+.BR %%End\%Prolog ;
+this is needed for spoolers that don't understand
+.B %%Begin\%Document
+and
+.B %%End\%Document
+comments.
+.
+.
+.IP
+Add\~8 to write
+.B %!PS\-Adobe\-2.0
+rather than
+.B %!PS\-Adobe\-3.0
+as the first line of the PostScript output;
+this is needed when using Sun's Newsprint with a printer that requires
+page reversal.
+.
+.
+.IP
+Add\~16 to omit media size information
+(that is,
+output neither a
+.B %%Document\%Media
+comment nor the
+.B setpagedevice
+PostScript command).
+.
+This was the behavior of
+.I groff
+1.18.1 and earlier;
+it is
+needed for older printers that don't understand PostScript
+\%LanguageLevel\~2,
+and is also necessary if the output is further processed to produce an
+EPS file;
+see subsection \[lq]Escapsulated PostScript\[rq] below.
+.
+.
+.TP
+.BI \-c\~ n
+Output
+.I n
+copies of each page.
+.
+.
+.TP
+.BI \-F\~ dir
+Prepend directory
+.RI dir /dev name
+to the search path for
+font and device description and PostScript prologue files;
+.I name
+is the name of the device,
+usually
+.BR ps .
+.
+.
+.TP
+.B \-g
+Generate PostScript code to guess the page length.
+.
+The guess is correct only if the imageable area is vertically centered
+on the page.
+.
+This option allows you to generate documents that can be printed on both
+U.S.\& letter and A4 paper formats without change.
+.
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+for files named in
+.B \[rs]X\[aq]ps: file\[aq]
+and
+.B \[rs]X\[aq]ps: import\[aq]
+escape sequences.
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.
+.TP
+.B \-l
+Use landscape orientation rather than portrait.
+.
+.
+.TP
+.B \-m
+Turn on manual feed for the document.
+.
+.
+.TP
+.BI \-p\~ fmt
+Set physical dimensions of output medium,
+overriding the
+.BR \%papersize ,
+.BR \%paperlength ,
+and
+.B \%paperwidth
+directives in the
+.I DESC
+file.
+.
+.I fmt
+can be any argument accepted by the
+.B \%papersize
+directive;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.BI \-P\~ prologue
+Use the file
+.IR prologue ,
+sought in the
+.I groff
+font search path,
+as the PostScript prologue,
+overriding the default
+(see section \[lq]Files\[rq] below)
+and the environment variable
+.I GROPS_PROLOGUE.
+.
+.
+.TP
+.BI \-w\~ n
+Draw rules (lines) with a thickness of
+.IR n \~thousandths
+of an em.
+.
+The default thickness is
+.B 40
+(0.04\~em).
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+The input to
+.I grops
+must be in the format output by
+.MR @g@troff @MAN1EXT@ ,
+described in
+.MR groff_out @MAN5EXT@ .
+.
+In addition,
+the device and font description files for the device used must meet
+certain requirements.
+.
+The device resolution must be an integer multiple of\~72 times the
+.BR sizescale .
+.
+The device description file must contain a valid paper format;
+see
+.MR groff_font @MAN5EXT@ .
+.
+Each font description file must contain a directive
+.
+.RS
+.EX
+.RI internalname\~ psname
+.EE
+.RE
+.
+which says that the PostScript name of the font is
+.IR psname .
+.
+.
+.P
+A font description file may also contain a directive
+.
+.RS
+.EX
+.RI encoding\~ enc-file
+.EE
+.RE
+.
+which says that
+the PostScript font should be reencoded using the encoding described in
+.IR enc-file ;
+this file should consist of a sequence of lines of the form
+.
+.
+.RS
+.EX
+.I pschar code
+.EE
+.RE
+.
+where
+.I pschar
+is the PostScript name of the character,
+and
+.I code
+is its position in the encoding expressed as a decimal integer;
+valid values are in the range 0 to\~255.
+.
+Lines starting with
+.B #
+and blank lines are ignored.
+.
+The code for each character given in the font description file must
+correspond to the code for the character in encoding file,
+or to the code in the default encoding for the font if the PostScript
+font is not to be reencoded.
+.
+This code can be used with the
+.B \[rs]N
+escape sequence in
+.I @g@troff
+to select the character,
+even if it does not have a
+.I groff
+glyph name.
+.
+Every character in the font description file must exist in the
+PostScript font,
+and the widths given in the font description file must match the widths
+used in the PostScript font.
+.
+.I grops
+assumes that a character with a
+.I groff
+name of
+.B space
+is blank
+(makes no marks on the page);
+it can make use of such a character to generate more efficient and
+compact PostScript output.
+.
+.
+.P
+.I grops
+is able to display all glyphs in a PostScript font;
+it is not limited to 256 of them.
+.
+.I enc-file
+(or the default encoding if no encoding file is specified)
+just defines the
+order of glyphs for the first 256 characters;
+all other glyphs are accessed with additional encoding vectors which
+.I grops
+produces on the fly.
+.
+.
+.P
+.I grops
+can embed fonts in a document that are necessary to render it;
+this is called \[lq]downloading\[rq].
+.
+Such fonts must be in PFA format.
+.
+Use
+.MR pfbtops @MAN1EXT@
+to convert a Type\~1 font in PFB format.
+.
+Downloadable fonts must be listed a
+.I download
+file containing lines of the form
+.
+.RS
+.EX
+.I psname file
+.EE
+.RE
+.
+where
+.I psname
+is the PostScript name of the font,
+and
+.I file
+is the name of the file containing it;
+lines beginning with
+.B #
+and blank lines are ignored;
+fields may be separated by tabs or spaces.
+.
+.I file
+is sought using the same mechanism as that for
+.I groff
+font description files.
+.
+The
+.I download
+file itself is also sought using this mechanism;
+currently,
+only the first matching file found in the device and font description
+search path is used.
+.
+.
+.P
+If the file containing a downloadable font or imported document
+conforms to the Adobe Document Structuring Conventions,
+then
+.I grops
+interprets any comments in the files sufficiently to ensure that its
+own output is conforming.
+.
+It also supplies any needed font resources that are listed in the
+.I download
+file
+as well as any needed file resources.
+.
+It is also able to handle inter-resource dependencies.
+.
+For example,
+suppose that you have a downloadable font called Garamond,
+and also a downloadable font called Garamond-Outline which depends on
+Garamond
+(typically it would be defined to copy Garamond's font dictionary,
+and change the PaintType),
+then it is necessary for Garamond to appear before Garamond-Outline in
+the PostScript document.
+.
+.I grops
+handles this automatically provided that the downloadable font file
+for Garamond-Outline indicates its dependence on Garamond by means of
+the Document Structuring Conventions,
+for example by beginning with the following lines.
+.
+.RS
+.EX
+%!PS\-Adobe\-3.0 Resource\-Font
+%%DocumentNeededResources: font Garamond
+%%EndComments
+%%IncludeResource: font Garamond
+.EE
+.RE
+.
+In this case,
+both Garamond and Garamond-Outline would need to be listed
+in the
+.I download
+file.
+.
+A downloadable font should not include its own name in a
+.B %%Document\%Supplied\%Resources
+comment.
+.
+.
+.P
+.I grops
+does not interpret
+.B %%Document\%Fonts
+comments.
+.
+The
+.BR %%Document\%Needed\%Resources ,
+.BR %%Document\%Supplied\%Resources ,
+.BR %%Include\%Resource ,
+.BR %%Begin\%Resource ,
+and
+.B %%End\%Resource
+comments
+(or possibly the old
+.BR %%Document\%Needed\%Fonts ,
+.BR %%Document\%Supplied\%Fonts ,
+.BR %%Include\%Font ,
+.BR %%Begin\%Font ,
+and
+.B %%End\%Font
+comments)
+should be used.
+.
+.
+.P
+The default stroke and fill color is black.
+.
+For colors defined in the \[lq]rgb\[rq] color space,
+.B setrgbcolor
+is used;
+for \[lq]cmy\[rq] and \[lq]cmyk\[rq],
+.BR setcmykcolor ;
+and for \[lq]gray\[rq],
+.BR setgray .
+.
+.B setcmykcolor
+is a PostScript \%LanguageLevel\~2 command and thus not available on
+some older printers.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.P
+Styles called
+.BR R ,
+.BR I ,
+.BR B ,
+and
+.B BI
+mounted at font positions 1 to\~4.
+.
+Text fonts are grouped into families
+.BR A ,
+.BR BM ,
+.BR C ,
+.BR H ,
+.BR HN ,
+.BR N ,
+.BR P ,
+.RB and\~ T ,
+each having members in each of these styles.
+.
+.
+.RS
+.TP
+.B AR
+.FT AR
+AvantGarde-Book
+.FT
+.
+.TQ
+.B AI
+.FT AI
+AvantGarde-BookOblique
+.FT
+.
+.TQ
+.B AB
+.FT AB
+AvantGarde-Demi
+.FT
+.
+.TQ
+.B ABI
+.FT ABI
+AvantGarde-DemiOblique
+.FT
+.
+.TQ
+.B BMR
+.FT BMR
+Bookman-Light
+.FT
+.
+.TQ
+.B BMI
+.FT BMI
+Bookman-LightItalic
+.FT
+.
+.TQ
+.B BMB
+.FT BMB
+Bookman-Demi
+.FT
+.
+.TQ
+.B BMBI
+.FT BMBI
+Bookman-DemiItalic
+.FT
+.
+.TQ
+.B CR
+.FT CR
+Courier
+.FT
+.
+.TQ
+.B CI
+.FT CI
+Courier-Oblique
+.FT
+.
+.TQ
+.B CB
+.FT CB
+Courier-Bold
+.FT
+.
+.TQ
+.B CBI
+.FT CBI
+Courier-BoldOblique
+.FT
+.
+.TQ
+.B HR
+.FT HR
+Helvetica
+.FT
+.
+.TQ
+.B HI
+.FT HI
+Helvetica-Oblique
+.FT
+.
+.TQ
+.B HB
+.FT HB
+Helvetica-Bold
+.FT
+.
+.TQ
+.B HBI
+.FT HBI
+Helvetica-BoldOblique
+.FT
+.
+.TQ
+.B HNR
+.FT HNR
+Helvetica-Narrow
+.FT
+.
+.TQ
+.B HNI
+.FT HNI
+Helvetica-Narrow-Oblique
+.FT
+.
+.TQ
+.B HNB
+.FT HNB
+Helvetica-Narrow-Bold
+.FT
+.
+.TQ
+.B HNBI
+.FT HNBI
+Helvetica-Narrow-BoldOblique
+.FT
+.
+.TQ
+.B NR
+.FT NR
+NewCenturySchlbk-Roman
+.FT
+.
+.TQ
+.B NI
+.FT NI
+NewCenturySchlbk-Italic
+.FT
+.
+.TQ
+.B NB
+.FT NB
+NewCenturySchlbk-Bold
+.FT
+.
+.TQ
+.B NBI
+.FT NBI
+NewCenturySchlbk-BoldItalic
+.FT
+.
+.TQ
+.B PR
+.FT PR
+Palatino-Roman
+.FT
+.
+.TQ
+.B PI
+.FT PI
+Palatino-Italic
+.FT
+.
+.TQ
+.B PB
+.FT PB
+Palatino-Bold
+.FT
+.
+.TQ
+.B PBI
+.FT PBI
+Palatino-BoldItalic
+.FT
+.
+.TQ
+.B TR
+.FT TR
+Times-Roman
+.FT
+.
+.TQ
+.B TI
+.FT TI
+Times-Italic
+.FT
+.
+.TQ
+.B TB
+.FT TB
+Times-Bold
+.FT
+.
+.TQ
+.B TBI
+.FT TBI
+Times-BoldItalic
+.FT
+.RE
+.
+.
+.br
+.ne 2v
+.P
+Another text font is not a member of a family.
+.
+.
+.RS
+.TP
+.B ZCMI
+.FT ZCMI
+ZapfChancery-MediumItalic
+.FT
+.RE
+.
+.
+.P
+Special fonts include
+.BR S ,
+the PostScript Symbol font;
+.BR ZD ,
+Zapf Dingbats;
+.B SS
+(slanted symbol),
+which contains oblique forms of lowercase Greek letters derived from
+Symbol;
+.BR EURO ,
+which offers a Euro glyph for use with old devices lacking it;
+and
+.BR ZDR ,
+a reversed version of ZapfDingbats
+(with symbols flipped about the vertical axis).
+.
+Most glyphs in these fonts are unnamed and must be accessed using
+.BR \[rs]N .
+.
+The last three are not standard PostScript fonts,
+but supplied by
+.I groff
+and therefore included in the default
+.I download
+file.
+.
+.
+.\" ====================================================================
+.SS "Device control commands"
+.\" ====================================================================
+.
+.I grops
+recognizes device control commands produced by the
+.B \[rs]X
+escape sequence,
+but interprets only those that begin with a
+.RB \[lq] ps: \[rq]
+tag.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: exec\~" code \[aq]
+.RS
+Execute the arbitrary PostScript commands
+.IR code .
+.
+The PostScript
+.I \%currentpoint
+is set to the
+.I groff
+drawing position when the
+.B \[rs]X
+escape sequence is interpreted before executing
+.IR code .
+.
+The origin is at the top left corner of the page;
+.IR x \~coordinates
+increase to the right,
+and
+.IR y \~coordinates
+down the page.
+.
+A
+.RB procedure\~ u
+is defined that converts
+.I groff
+basic units to the coordinate system in effect
+(provided the user doesn't change the scale).
+.
+For example,
+.
+.RS
+.EX
+\&.nr x 1i
+\[rs]X\[aq]ps: exec \[rs]nx u 0 rlineto stroke\[aq]
+.EE
+.RE
+.
+draws a horizontal line one inch long.
+.
+.I code
+may make changes to the graphics state,
+but any changes persist only to the end of the page.
+.
+A dictionary containing the definitions specified by the
+.B def
+and
+.B mdef
+commands is on top of the dictionary stack.
+.
+If your code adds definitions to this dictionary,
+you should allocate space for them using
+.RB \[lq] "\[rs]X\[aq]ps: mdef\~"
+.IB n \[aq]\c
+\[rq].
+.
+Any definitions persist only until the end of the page.
+.
+If you use the
+.B \[rs]Y
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+.
+For example,
+.
+.RS
+.EX
+\&.nr x 1i
+\&.de y
+\&ps: exec
+\&\[rs]nx u 0 rlineto
+\&stroke
+\&..
+\&\[rs]Yy
+.EE
+.RE
+.
+is another way to draw a horizontal line one inch long.
+.
+The single backslash before
+.RB \[lq] nx \[rq]\[em]the
+only reason to use a register while defining the macro
+.RB \[lq] y \[rq]\[em]is
+to convert a user-specified dimension
+.RB \[lq] 1i \[rq]
+to
+.I groff
+basic units which are in turn converted to PostScript units with the
+.B u
+procedure.
+.
+.
+.P
+.I grops
+wraps user-specified PostScript code into a dictionary,
+nothing more.
+.
+In particular,
+it doesn't start and end the inserted code with
+.B save
+and
+.BR restore ,
+respectively.
+.
+This must be supplied by the user,
+if necessary.
+.RE
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: file\~" name \[aq]
+This is the same as the
+.B exec
+command except that the PostScript code is read from file
+.IR name .
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: def\~" code \[aq]
+Place a PostScript definition contained in
+.I code
+in the prologue.
+.
+There should be at most one definition per
+.B \[rs]X
+command.
+.
+Long definitions can be split over several
+.B \[rs]X
+commands;
+all the
+.I code
+arguments are simply joined together separated by newlines.
+.
+The definitions are placed in a dictionary which is automatically
+pushed on the dictionary stack when an
+.B exec
+command is executed.
+.
+If you use the
+.B \[rs]Y
+escape sequence with an argument that names a macro,
+.I code
+can extend over multiple lines.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: mdef\~" "n code" \[aq]
+Like
+.BR def ,
+except that
+.I code
+may contain up to
+.IR n \~definitions.
+.
+.I grops
+needs to know how many definitions
+.I code
+contains
+so that it can create an appropriately sized PostScript dictionary
+to contain them.
+.
+.
+.TP
+.BI "\[rs]X\[aq]ps: import\~" "file llx lly urx ury width\~"\c
+.RI [ height ]\c
+.B \[aq]
+Import a PostScript graphic from
+.IR file .
+.
+The arguments
+.IR llx ,
+.IR lly ,
+.IR urx ,
+and
+.I ury
+give the bounding box of the graphic in the default PostScript
+coordinate system.
+.
+They should all be integers:
+.I llx
+and
+.I lly
+are the
+.I x
+and
+.IR y \~coordinates
+of the lower left corner of the graphic;
+.I urx
+and
+.I ury
+are the
+.I x
+and
+.IR y \~coordinates
+of the upper right corner of the graphic;
+.I width
+and
+.I height
+are integers that give the desired width and height in
+.I groff
+basic units of the graphic.
+.
+.
+.IP
+The graphic is scaled so that it has this width and height
+and translated so that the lower left corner of the graphic is
+located at the position associated with
+.B \[rs]X
+command.
+.
+If the height argument is omitted it is scaled uniformly in the
+.I x
+and
+.IR y \~axes
+so that it has the specified width.
+.
+.
+.IP
+The contents of the
+.B \[rs]X
+command are not interpreted by
+.IR @g@troff ,
+so vertical space for the graphic is not automatically added,
+and the
+.I width
+and
+.I height
+arguments are not allowed to have attached scaling indicators.
+.
+.
+.IP
+If the PostScript file complies with the Adobe Document Structuring
+Conventions and contains a
+.B %%Bounding\%Box
+comment,
+then the bounding box can be automatically extracted from within
+.I groff
+input by using the
+.B psbb
+request.
+.
+.
+.IP
+See
+.MR groff_tmac @MAN5EXT@
+for a description of the
+.B PSPIC
+macro which provides a convenient high-level interface for inclusion of
+PostScript graphics.
+.
+.
+.TP
+.B \[rs]X\[aq]ps: invis\[aq]
+.TQ
+.B \[rs]X\[aq]ps: endinvis\[aq]
+No output is generated for text and drawing commands
+that are bracketed with these
+.B \[rs]X
+commands.
+.
+These commands are intended for use when output from
+.I @g@troff
+is previewed before being processed with
+.IR grops ;
+if the previewer is unable to display certain characters
+or other constructs,
+then other substitute characters or constructs can be used for
+previewing by bracketing them with these
+.B \[rs]X
+commands.
+.
+.
+.RS
+.P
+For example,
+.I \%gxditview
+is not able to display a proper
+.B \[rs][em]
+character because the standard X11 fonts do not provide it;
+this problem can be overcome by executing the following
+request
+.
+.
+.IP
+.EX
+\&.char \[rs][em] \[rs]X\[aq]ps: invis\[aq]\[rs]
+\[rs]Z\[aq]\[rs]v\[aq]-.25m\[aq]\[rs]h\[aq].05m\[aq]\c
+\[rs]D\[aq]l .9m 0\[aq]\[rs]h\[aq].05m\[aq]\[aq]\[rs]
+\[rs]X\[aq]ps: endinvis\[aq]\[rs][em]
+.EE
+.
+.
+.P
+In this case,
+.I \%gxditview
+is unable to display the
+.B \[rs][em]
+character and draws the line,
+whereas
+.I grops
+prints the
+.B \[rs][em]
+character
+and ignores the line
+(this code is already in file
+.IR Xps.tmac ,
+which is loaded if a document intended for
+.I grops
+is previewed with
+.IR \%gxditview ).
+.RE
+.
+.
+.P
+If a PostScript procedure
+.B BPhook
+has been defined via a
+.RB \[lq] "ps: def" \[rq]
+or
+.RB \[lq] "ps: mdef" \[rq]
+device control command,
+it is executed at the beginning of every page
+(before anything is drawn or written by
+.IR groff ).
+.
+For example,
+to underlay the page contents with the word \[lq]DRAFT\[rq] in light
+gray,
+you might use
+.
+.
+.RS
+.P
+.EX
+\&.de XX
+ps: def
+/BPhook
+{ gsave .9 setgray clippath pathbbox exch 2 copy
+ .5 mul exch .5 mul translate atan rotate pop pop
+ /NewCenturySchlbk-Roman findfont 200 scalefont setfont
+ (DRAFT) dup stringwidth pop \-.5 mul \-70 moveto show
+ grestore }
+def
+\&..
+\&.devicem XX
+.EE
+.RE
+.
+.
+.P
+Or,
+to cause lines and polygons to be drawn with square linecaps and mitered
+linejoins instead of the round linecaps and linejoins normally used by
+.IR grops ,
+use
+.
+.RS
+.EX
+\&.de XX
+ps: def
+/BPhook { 2 setlinecap 0 setlinejoin } def
+\&..
+\&.devicem XX
+.EE
+.RE
+.
+(square linecaps,
+as opposed to butt linecaps
+.RB (\[lq] "0 setlinecap" \[rq]),
+give true corners in boxed tables even though the lines are drawn
+unconnected).
+.
+.
+.\" ====================================================================
+.SS "Encapsulated PostScript"
+.\" ====================================================================
+.
+.I grops
+itself doesn't emit bounding box information.
+.
+The following script,
+.IR groff2eps ,
+produces an EPS file.
+.
+.
+.RS
+.P
+.EX
+#! /bin/sh
+groff \-P\-b16 "$1" > "$1".ps
+gs \-dNOPAUSE \-sDEVICE=bbox \-\- "$1".ps 2> "$1".bbox
+sed \-e "/\[ha]%%Orientation/r $1.bbox" \[rs]
+ \-e "/\[ha]%!PS\-Adobe\-3.0/s/$/ EPSF\-3.0/" "$1".ps > "$1".eps
+rm "$1".ps "$1".bbox
+.EE
+.RE
+.
+.
+.P
+You can then use
+.RB \[lq] "groff2eps foo" \[rq]
+to convert file
+.I foo
+to
+.IR foo.eps .
+.
+.
+.\" ====================================================================
+.SS "TrueType and other font formats"
+.\" ====================================================================
+.
+TrueType fonts can be used with
+.I grops
+if converted first to Type\~42 format,
+a PostScript wrapper equivalent to the PFA format described in
+.MR pfbtops @MAN1EXT@ .
+.
+Several methods exist to generate a Type\~42 wrapper;
+some of them involve the use of a PostScript interpreter such as
+Ghostscript\[em]see
+.MR gs 1 .
+.
+.
+.P
+One approach is to use
+.UR https://fontforge.org/
+FontForge
+.UE ,
+a font editor that can convert most outline font formats.
+.
+Here's an example of using the Roboto Slab Serif font with
+.IR groff .
+.
+Several variables are used so that you can more easily adapt it into
+your own script.
+.
+.
+.RS 4
+.P
+.EX
+MAP=@FONTDIR@/devps/generate/text.map
+TTF=/usr/share/fonts/truetype/roboto/slab/RobotoSlab\-Regular.ttf
+BASE=$(basename \[dq]$TTF\[dq])
+INT=${BASE%.ttf}
+PFA=$INT.pfa
+AFM=$INT.afm
+GFN=RSR
+DIR=$HOME/.local/groff/font
+mkdir \-p \[dq]$DIR\[dq]/devps
+fontforge \-lang=ff \-c \[dq]Open(\[rs]\[dq]$TTF\[rs]\[dq]);\[rs]
+\tGenerate(\[rs]\[dq]$DIR/devps/$PFA\[rs]\[dq]);\[dq]
+afmtodit \[dq]$DIR/devps/$AFM\[dq] \[dq]$MAP\[dq] \
+\[dq]$DIR/devps/$GFN\[dq]
+printf \[dq]$BASE\[rs]t$PFA\[rs]n\[dq] >> \[dq]$DIR/devps/download\[dq]
+.EE
+.RE
+.
+.
+.P
+.I fontforge
+and
+.I afmtodit
+may generate warnings depending on the attributes of the font.
+.
+The test procedure is simple.
+.
+.
+.RS 4
+.P
+.EX
+printf \[dq].ft RSR\[rs]nHello, world!\[rs]n\[dq] | groff \-F \
+\[dq]$DIR\[dq] > hello.ps
+.EE
+.RE
+.
+.
+.P
+Once you're satisfied that the font works,
+you may want to generate any available related styles
+(for instance,
+Roboto Slab
+also has \[lq]Bold\[rq],
+\[lq]Light\[rq],
+and
+\[lq]Thin\[rq]
+styles)
+and set up
+.I GROFF_FONT_PATH
+in your environment to include the directory you keep the generated
+fonts in so that you don't have to use the
+.B \-F
+option.
+.
+.
+.\" ====================================================================
+.SH "Font installation"
+.\" ====================================================================
+.
+The following is a step-by-step font installation guide for
+.I grops.
+.
+.
+.IP \[bu] 2n
+Convert your font to something
+.I groff
+understands.
+.
+This is a PostScript Type\~1 font in PFA format or a PostScript
+Type\~42 font,
+together with an AFM file.
+.
+A PFA file begins as follows.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+%!PS\-AdobeFont\-1.0:
+.EE
+.RE
+.
+A PFB file contains this string as well,
+preceded by some non-printing bytes.
+.
+If your font is in PFB format,
+use
+.IR groff 's
+.MR pfbtops @MAN1EXT@
+program to convert it to PFA.
+.
+For TrueType and other font formats,
+we recommend
+.IR fontforge ,
+which can convert most outline font formats.
+.
+A Type\~42 font file begins as follows.
+.
+.RS
+.EX
+%!PS\-TrueTypeFont
+.EE
+.RE
+.
+This is a wrapper format for TrueType fonts.
+.
+Old PostScript printers might not support them
+(that is,
+they might not have a built-in TrueType font interpreter).
+.
+In the following steps,
+we will consider the use of CTAN's
+.UR https://\:ctan.org/\:tex\-archive/\:fonts/\:brushscr
+BrushScriptX-Italic
+.UE
+font in PFA format.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Convert the AFM file to a
+.I groff
+font description file with the
+.MR afmtodit @MAN1EXT@
+program.
+.
+For instance,
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+$ \c
+.B afmtodit BrushScriptX\-Italic.afm text.map BSI
+.EE
+.RE
+.
+converts the Adobe Font Metric file
+.I BrushScriptX\-Italic.afm
+to the
+.I groff
+font description file
+.IR BSI .
+.RE \" now restore left margin
+.
+.
+.IP
+If you have a font family which provides regular upright (roman),
+bold,
+italic,
+and
+bold-italic styles
+(where \[lq]italic\[rq] may be \[lq]oblique\[rq] or \[lq]slanted\[rq]),
+we recommend using the letters
+.BR R ,
+.BR B ,
+.BR I ,
+and
+.BR BI ,
+respectively,
+as suffixes to the
+.I groff
+font family name to enable
+.IR groff 's
+font family and style selection features.
+.
+An example is
+.IR groff 's
+built-in support for Times:
+the font family
+name is abbreviated as
+.BR T ,
+and the
+.I groff
+font names are therefore
+.BR TR ,
+.BR TB ,
+.BR TI ,
+and
+.BR TBI .
+.
+In our example,
+however,
+the BrushScriptX font is available in a single style only,
+italic.
+.
+.
+.IP \[bu]
+Install the
+.I groff
+font description file(s) in a
+.I devps
+subdirectory in the search path that
+.I groff
+uses for device and font file descriptions.
+.
+See the
+.I GROFF_FONT_PATH
+entry in section \[lq]Environment\[rq] of
+.MR @g@troff @MAN1EXT@
+for the current value of the font search path.
+.
+While
+.I groff
+doesn't directly use AFM files,
+it is a good idea to store them alongside its font description files.
+.
+.
+.IP \[bu]
+Register fonts in the
+.I devps/download
+file so they can be located for embedding in PostScript files
+.I grops
+generates.
+.
+Only the first
+.I download
+file encountered in the font search path is read.
+.
+If in doubt,
+copy the default
+.I download
+file
+(see section \[lq]Files\[rq] below)
+to the first directory in the font search path and add your fonts there.
+.
+The PostScript font name used by
+.I grops
+is stored in the
+.B internalname
+field in the
+.I groff
+font description file.
+.
+(This name does not necessarily resemble the font's file name.)
+.
+We add the following line to
+.IR download .
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+BrushScriptX\-Italic\[->]BrushScriptX\-Italic.pfa
+.EE
+.RE \" but only one to get back to it
+.
+A tab character,
+depicted as \[->],
+separates the fields.
+.RE \" now restore left margin
+.
+.
+.IP \[bu]
+Test the selection and embedding of the new font.
+.
+.RS
+.RS \" two RS calls to get inboard of IP indentation
+.EX
+printf "\[rs]\[rs]f[BSI]Hello, world!\[rs]n" \
+| groff \-T ps \-P \-e >hello.ps
+see hello.pdf
+.EE
+.RE
+.RE \" now restore left margin
+.
+.
+.\" ====================================================================
+.SH "Old fonts"
+.\" ====================================================================
+.
+.I groff
+versions 1.19.2 and earlier contained descriptions of a slightly
+different set of the base 35 PostScript level 2 fonts defined by Adobe.
+.
+The older set has 229 glyphs and a larger set of kerning pairs;
+the newer one has 314 glyphs and includes the Euro glyph.
+.
+For backwards compatibility,
+these old font descriptions are also installed in the
+.I @OLDFONTDIR@/\:\%devps
+directory.
+.
+.
+.P
+To use them,
+make sure that
+.I grops
+finds the fonts before the default system fonts
+(with the same names):
+either give
+.I grops
+the
+.B \-F
+command-line option,
+.
+.RS
+.EX
+$ \c
+.B groff \-Tps \-P\-F \-P@OLDFONTDIR@ \c
+\&.\|.\|.
+.EE
+.RE
+.
+or add the directory to
+.IR groff 's
+font and device description search path environment variable,
+.
+.RS
+.EX
+$ \c
+.B GROFF_FONT_PATH=\:@OLDFONTDIR@ \[rs]
+.RS
+.B groff \-Tps \c
+\&.\|.\|.
+.RE
+.EE
+.RE
+.
+when the command runs.
+.
+.
+.br
+.ne 3v
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROPS_PROLOGUE
+If this is set to
+.IR foo ,
+then
+.I grops
+uses the file
+.I foo
+(in the font path) instead of the default prologue file
+.IR prologue .
+.
+The option
+.B \-P
+overrides this environment variable.
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR ctime 3
+and recorded in a PostScript comment.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:DESC
+describes the
+.B ps
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devps/ F
+describes the font known
+.RI as\~ F
+on device
+.BR ps .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%download
+lists fonts available for embedding within the PostScript document
+(or download to the device).
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:\%prologue
+is the default PostScript prologue prefixed to every output file.
+.
+.
+.TP
+.I @FONTDIR@/\:\%devps/\:text.enc
+describes the encoding scheme used by most PostScript Type\~1 fonts;
+the
+.B \%encoding
+directive of
+font description files for the
+.B ps
+device refers to it.
+.
+.
+.TP
+.I @MACRODIR@/\:ps.tmac
+defines macros for use with the
+.B ps
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when the
+.B ps
+output device is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:pspic.tmac
+defines the
+.B PSPIC
+macro for embedding images in a document;
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+It is automatically loaded by
+.I troffrc.
+.
+.
+.TP
+.I @MACRODIR@/psold.tmac
+provides replacement glyphs for text fonts that lack complete coverage
+of the ISO Latin-1 character set;
+using it,
+.I groff
+can produce glyphs like eth (\[Sd]) and thorn (\[Tp]) that older
+PostScript printers do not natively support.
+.
+.
+.P
+.I grops
+creates temporary files using the template
+.RI \[lq] grops XXXXXX\[rq];
+see
+.MR groff @MAN1EXT@
+for details on their storage location.
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR http://\:partners\:.adobe\:.com/\:public/\:developer/\:en/\:ps/\
+\:5001\:.DSC_Spec\:.pdf
+PostScript Language Document Structuring Conventions Specification
+.UE
+.
+.
+.P
+.MR afmtodit @MAN1EXT@ ,
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR pfbtops @MAN1EXT@ ,
+.MR groff_char @MAN7EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_tmac @MAN5EXT@
+.
+.
+.\" Clean up.
+.rm FT
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grops_1_man_C]
+.do rr *groff_grops_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grops/grops.am b/src/devices/grops/grops.am
new file mode 100644
index 0000000..cb5532a
--- /dev/null
+++ b/src/devices/grops/grops.am
@@ -0,0 +1,38 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grops
+grops_SOURCES = \
+ src/devices/grops/ps.cpp \
+ src/devices/grops/psrm.cpp \
+ src/devices/grops/ps.h
+grops_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grops/grops.1
+EXTRA_DIST += \
+ src/devices/grops/grops.1.man \
+ src/devices/grops/psfig.diff \
+ src/devices/grops/TODO
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grops/ps.cpp b/src/devices/grops/ps.cpp
new file mode 100644
index 0000000..807945f
--- /dev/null
+++ b/src/devices/grops/ps.cpp
@@ -0,0 +1,1894 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+ * PostScript documentation:
+ * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
+ * http://partners.adobe.com/public/developer/en/ps/5001.DSC_Spec.pdf
+ */
+
+#include "lib.h" // PI
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "nonposix.h"
+#include "paper.h"
+#include "curtime.h"
+
+#include "ps.h"
+#include <time.h>
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+extern "C" const char *Version_string;
+
+// search path defaults to the current directory
+search_path include_search_path(0, 0, 0, 1);
+
+static int landscape_flag = 0;
+static int manual_feed_flag = 0;
+static int ncopies = 1;
+static int linewidth = -1;
+// Non-zero means generate PostScript code that guesses the paper
+// length using the imageable area.
+static int guess_flag = 0;
+static double user_paper_length = 0;
+static double user_paper_width = 0;
+
+// Non-zero if -b was specified on the command line.
+static int bflag = 0;
+unsigned broken_flags = 0;
+
+// Non-zero means we need the CMYK extension for PostScript Level 1
+static int cmyk_flag = 0;
+
+#define DEFAULT_LINEWIDTH 40 /* in ems/1000 */
+#define MAX_LINE_LENGTH 72
+#define FILL_MAX 1000
+
+const char *const dict_name = "grops";
+const char *const defs_dict_name = "DEFS";
+const int DEFS_DICT_SPARE = 50;
+
+double degrees(double r)
+{
+ return r*180.0/PI;
+}
+
+double radians(double d)
+{
+ return d*PI/180.0;
+}
+
+// This is used for testing whether a character should be output in the
+// PostScript file using \nnn, so we really want the character to be
+// less than 0200.
+
+inline int is_ascii(char c)
+{
+ return (unsigned char)c < 0200;
+}
+
+ps_output::ps_output(FILE *f, int n)
+: fp(f), col(0), max_line_length(n), need_space(0), fixed_point(0)
+{
+}
+
+ps_output &ps_output::set_file(FILE *f)
+{
+ fp = f;
+ col = 0;
+ return *this;
+}
+
+ps_output &ps_output::copy_file(FILE *infp)
+{
+ int c;
+ while ((c = getc(infp)) != EOF)
+ putc(c, fp);
+ return *this;
+}
+
+ps_output &ps_output::end_line()
+{
+ if (col != 0) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ return *this;
+}
+
+ps_output &ps_output::special(const char *s)
+{
+ if (s == 0 || *s == '\0')
+ return *this;
+ if (col != 0) {
+ putc('\n', fp);
+ col = 0;
+ }
+ fputs(s, fp);
+ if (strchr(s, '\0')[-1] != '\n')
+ putc('\n', fp);
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::simple_comment(const char *s)
+{
+ if (col != 0)
+ putc('\n', fp);
+ putc('%', fp);
+ putc('%', fp);
+ fputs(s, fp);
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::begin_comment(const char *s)
+{
+ if (col != 0)
+ putc('\n', fp);
+ putc('%', fp);
+ putc('%', fp);
+ fputs(s, fp);
+ col = 2 + strlen(s);
+ return *this;
+}
+
+ps_output &ps_output::end_comment()
+{
+ if (col != 0) {
+ putc('\n', fp);
+ col = 0;
+ }
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::comment_arg(const char *s)
+{
+ int len = strlen(s);
+ if (col + len + 1 > max_line_length) {
+ putc('\n', fp);
+ fputs("%%+", fp);
+ col = 3;
+ }
+ putc(' ', fp);
+ fputs(s, fp);
+ col += len + 1;
+ return *this;
+}
+
+ps_output &ps_output::set_fixed_point(int n)
+{
+ assert(n >= 0 && n <= 10);
+ fixed_point = n;
+ return *this;
+}
+
+ps_output &ps_output::put_delimiter(char c)
+{
+ if (col + 1 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc(c, fp);
+ col++;
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::put_string(const char *s, int n)
+{
+ int len = 0;
+ int i;
+ for (i = 0; i < n; i++) {
+ char c = s[i];
+ if (is_ascii(c) && csprint(c)) {
+ if (c == '(' || c == ')' || c == '\\')
+ len += 2;
+ else
+ len += 1;
+ }
+ else
+ len += 4;
+ }
+ if (len > n*2) {
+ if (col + n*2 + 2 > max_line_length && n*2 + 2 <= max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ if (col + 1 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc('<', fp);
+ col++;
+ for (i = 0; i < n; i++) {
+ if (col + 2 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ fprintf(fp, "%02x", s[i] & 0377);
+ col += 2;
+ }
+ putc('>', fp);
+ col++;
+ }
+ else {
+ if (col + len + 2 > max_line_length && len + 2 <= max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ if (col + 2 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc('(', fp);
+ col++;
+ for (i = 0; i < n; i++) {
+ char c = s[i];
+ if (is_ascii(c) && csprint(c)) {
+ if (c == '(' || c == ')' || c == '\\')
+ len = 2;
+ else
+ len = 1;
+ }
+ else
+ len = 4;
+ if (col + len + 1 > max_line_length) {
+ putc('\\', fp);
+ putc('\n', fp);
+ col = 0;
+ }
+ switch (len) {
+ case 1:
+ putc(c, fp);
+ break;
+ case 2:
+ putc('\\', fp);
+ putc(c, fp);
+ break;
+ case 4:
+ fprintf(fp, "\\%03o", c & 0377);
+ break;
+ default:
+ assert(0);
+ }
+ col += len;
+ }
+ putc(')', fp);
+ col++;
+ }
+ need_space = 0;
+ return *this;
+}
+
+ps_output &ps_output::put_number(int n)
+{
+ char buf[1 + INT_DIGITS + 1];
+ sprintf(buf, "%d", n);
+ int len = strlen(buf);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(buf, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_fix_number(int i)
+{
+ const char *p = if_to_a(i, fixed_point);
+ int len = strlen(p);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(p, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_float(double d)
+{
+ char buf[128];
+ sprintf(buf, "%.4f", d);
+ int last = strlen(buf) - 1;
+ while (buf[last] == '0')
+ last--;
+ if (buf[last] == '.')
+ last--;
+ buf[++last] = '\0';
+ if (col > 0 && col + last + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(buf, fp);
+ col += last;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_symbol(const char *s)
+{
+ int len = strlen(s);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(s, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_color(unsigned int c)
+{
+ char buf[128];
+ sprintf(buf, "%.3g", double(c) / double(color::MAX_COLOR_VAL));
+ int len = strlen(buf);
+ if (col > 0 && col + len + need_space > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ need_space = 0;
+ }
+ if (need_space) {
+ putc(' ', fp);
+ col++;
+ }
+ fputs(buf, fp);
+ col += len;
+ need_space = 1;
+ return *this;
+}
+
+ps_output &ps_output::put_literal_symbol(const char *s)
+{
+ int len = strlen(s);
+ if (col > 0 && col + len + 1 > max_line_length) {
+ putc('\n', fp);
+ col = 0;
+ }
+ putc('/', fp);
+ fputs(s, fp);
+ col += len + 1;
+ need_space = 1;
+ return *this;
+}
+
+class ps_font : public font {
+ ps_font(const char *);
+public:
+ int encoding_index;
+ char *encoding;
+ char *reencoded_name;
+ ~ps_font();
+ void handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+ static ps_font *load_ps_font(const char *);
+};
+
+ps_font *ps_font::load_ps_font(const char *s)
+{
+ ps_font *f = new ps_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ return f;
+}
+
+ps_font::ps_font(const char *nm)
+: font(nm), encoding_index(-1), encoding(0), reencoded_name(0)
+{
+}
+
+ps_font::~ps_font()
+{
+ free(encoding);
+ delete[] reencoded_name;
+}
+
+void ps_font::handle_unknown_font_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "encoding") == 0) {
+ if (arg == 0)
+ error_with_file_and_line(filename, lineno,
+ "'encoding' command requires an argument");
+ else
+ encoding = strsave(arg);
+ }
+}
+
+static void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "broken") == 0) {
+ if (arg == 0)
+ error_with_file_and_line(filename, lineno,
+ "'broken' command requires an argument");
+ else if (!bflag)
+ broken_flags = atoi(arg);
+ }
+}
+
+struct subencoding {
+ font *p;
+ unsigned int num;
+ int idx;
+ char *subfont;
+ const char *glyphs[256];
+ subencoding *next;
+
+ subencoding(font *, unsigned int, int, subencoding *);
+ ~subencoding();
+};
+
+subencoding::subencoding(font *f, unsigned int n, int ix, subencoding *s)
+: p(f), num(n), idx(ix), subfont(0), next(s)
+{
+ for (int i = 0; i < 256; i++)
+ glyphs[i] = 0;
+}
+
+subencoding::~subencoding()
+{
+ delete[] subfont;
+}
+
+struct style {
+ font *f;
+ subencoding *sub;
+ int point_size;
+ int height;
+ int slant;
+ style();
+ style(font *, subencoding *, int, int, int);
+ int operator==(const style &) const;
+ int operator!=(const style &) const;
+};
+
+style::style() : f(0)
+{
+}
+
+style::style(font *p, subencoding *s, int sz, int h, int sl)
+: f(p), sub(s), point_size(sz), height(h), slant(sl)
+{
+}
+
+int style::operator==(const style &s) const
+{
+ return (f == s.f
+ && sub == s.sub
+ && point_size == s.point_size
+ && height == s.height
+ && slant == s.slant);
+}
+
+int style::operator!=(const style &s) const
+{
+ return !(*this == s);
+}
+
+class ps_printer : public printer {
+ FILE *tempfp;
+ ps_output out;
+ int res;
+ glyph *space_glyph;
+ int pages_output;
+ int paper_length;
+ int equalise_spaces;
+ enum { SBUF_SIZE = 256 };
+ char sbuf[SBUF_SIZE];
+ int sbuf_len;
+ int sbuf_start_hpos;
+ int sbuf_vpos;
+ int sbuf_end_hpos;
+ int sbuf_space_width;
+ int sbuf_space_count;
+ int sbuf_space_diff_count;
+ int sbuf_space_code;
+ int sbuf_kern;
+ style sbuf_style;
+ color sbuf_color; // the current PS color
+ style output_style;
+ subencoding *subencodings;
+ int output_hpos;
+ int output_vpos;
+ int output_draw_point_size;
+ int line_thickness;
+ int output_line_thickness;
+ unsigned char output_space_code;
+ enum { MAX_DEFINED_STYLES = 50 };
+ style defined_styles[MAX_DEFINED_STYLES];
+ int ndefined_styles;
+ int next_encoding_index;
+ int next_subencoding_index;
+ string defs;
+ int ndefs;
+ resource_manager rm;
+ int invis_count;
+
+ void flush_sbuf();
+ void set_style(const style &);
+ void set_space_code(unsigned char);
+ int set_encoding_index(ps_font *);
+ subencoding *set_subencoding(font *, glyph *, unsigned char *);
+ char *get_subfont(subencoding *, const char *);
+ void do_exec(char *, const environment *);
+ void do_import(char *, const environment *);
+ void do_def(char *, const environment *);
+ void do_mdef(char *, const environment *);
+ void do_file(char *, const environment *);
+ void do_invis(char *, const environment *);
+ void do_endinvis(char *, const environment *);
+ void set_line_thickness_and_color(const environment *);
+ void fill_path(const environment *);
+ void encode_fonts();
+ void encode_subfont(subencoding *);
+ void define_encoding(const char *, int);
+ void reencode_font(ps_font *);
+ void set_color(color *, int = 0);
+
+ const char *media_name();
+ int media_width();
+ int media_height();
+ void media_set();
+
+public:
+ ps_printer(double);
+ ~ps_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *);
+ void draw(int, int *, int, const environment *);
+ void begin_page(int);
+ void end_page(int);
+ void special(char *, const environment *, char);
+ font *make_font(const char *);
+ void end_of_line();
+};
+
+// 'pl' is in inches
+ps_printer::ps_printer(double pl)
+: out(0, MAX_LINE_LENGTH),
+ pages_output(0),
+ sbuf_len(0),
+ subencodings(0),
+ output_hpos(-1),
+ output_vpos(-1),
+ line_thickness(-1),
+ ndefined_styles(0),
+ next_encoding_index(0),
+ next_subencoding_index(0),
+ ndefs(0),
+ invis_count(0)
+{
+ tempfp = xtmpfile();
+ out.set_file(tempfp);
+ if (linewidth < 0)
+ linewidth = DEFAULT_LINEWIDTH;
+ if (font::hor != 1)
+ fatal("device horizontal motion quantum must be 1, got %1",
+ font::hor);
+ if (font::vert != 1)
+ fatal("device vertical motion quantum must be 1, got %1",
+ font::vert);
+ if (font::res % (font::sizescale*72) != 0)
+ fatal("device resolution must be a multiple of 72*'sizescale', got"
+ " %1 ('sizescale'=%2)", font::res, font::sizescale);
+ int r = font::res;
+ int point = 0;
+ while (r % 10 == 0) {
+ r /= 10;
+ point++;
+ }
+ res = r;
+ out.set_fixed_point(point);
+ space_glyph = name_to_glyph("space");
+ if (pl == 0)
+ paper_length = font::paperlength;
+ else
+ paper_length = int(pl * font::res + 0.5);
+ if (paper_length == 0)
+ paper_length = 11 * font::res;
+ equalise_spaces = font::res >= 72000;
+}
+
+int ps_printer::set_encoding_index(ps_font *f)
+{
+ if (f->encoding_index >= 0)
+ return f->encoding_index;
+ for (font_pointer_list *p = font_list; p; p = p->next)
+ if (p->p != f) {
+ char *encoding = ((ps_font *)p->p)->encoding;
+ int encoding_index = ((ps_font *)p->p)->encoding_index;
+ if (encoding != 0 && encoding_index >= 0
+ && strcmp(f->encoding, encoding) == 0) {
+ return f->encoding_index = encoding_index;
+ }
+ }
+ return f->encoding_index = next_encoding_index++;
+}
+
+subencoding *ps_printer::set_subencoding(font *f, glyph *g,
+ unsigned char *codep)
+{
+ unsigned int idx = f->get_code(g);
+ *codep = idx % 256;
+ unsigned int num = idx >> 8;
+ if (num == 0)
+ return 0;
+ subencoding *p = 0;
+ for (p = subencodings; p; p = p->next)
+ if (p->p == f && p->num == num)
+ break;
+ if (p == 0)
+ p = subencodings = new subencoding(f, num, next_subencoding_index++,
+ subencodings);
+ p->glyphs[*codep] = f->get_special_device_encoding(g);
+ return p;
+}
+
+char *ps_printer::get_subfont(subencoding *sub, const char *stem)
+{
+ assert(sub != 0);
+ if (!sub->subfont) {
+ char *tem = new char[strlen(stem) + 2 + INT_DIGITS + 1];
+ sprintf(tem, "%s@@%d", stem, sub->idx);
+ sub->subfont = tem;
+ }
+ return sub->subfont;
+}
+
+void ps_printer::set_char(glyph *g, font *f, const environment *env, int w,
+ const char *)
+{
+ if (g == space_glyph || invis_count > 0)
+ return;
+ unsigned char code;
+ subencoding *sub = set_subencoding(f, g, &code);
+ style sty(f, sub, env->size, env->height, env->slant);
+ if (sty.slant != 0) {
+ if (sty.slant > 80 || sty.slant < -80) {
+ error("silly slant '%1' degrees", sty.slant);
+ sty.slant = 0;
+ }
+ }
+ if (sbuf_len > 0) {
+ if (sbuf_len < SBUF_SIZE
+ && sty == sbuf_style
+ && sbuf_vpos == env->vpos
+ && sbuf_color == *env->col) {
+ if (sbuf_end_hpos == env->hpos) {
+ sbuf[sbuf_len++] = code;
+ sbuf_end_hpos += w + sbuf_kern;
+ return;
+ }
+ if (sbuf_len == 1 && sbuf_kern == 0) {
+ sbuf_kern = env->hpos - sbuf_end_hpos;
+ sbuf_end_hpos = env->hpos + sbuf_kern + w;
+ sbuf[sbuf_len++] = code;
+ return;
+ }
+ /* If sbuf_end_hpos - sbuf_kern == env->hpos, we are better off
+ starting a new string. */
+ if (sbuf_len < SBUF_SIZE - 1 && env->hpos >= sbuf_end_hpos
+ && (sbuf_kern == 0 || sbuf_end_hpos - sbuf_kern != env->hpos)) {
+ if (sbuf_space_code < 0) {
+ if (f->contains(space_glyph) && !sub) {
+ sbuf_space_code = f->get_code(space_glyph);
+ sbuf_space_width = env->hpos - sbuf_end_hpos;
+ sbuf_end_hpos = env->hpos + w + sbuf_kern;
+ sbuf[sbuf_len++] = sbuf_space_code;
+ sbuf[sbuf_len++] = code;
+ sbuf_space_count++;
+ return;
+ }
+ }
+ else {
+ int diff = env->hpos - sbuf_end_hpos - sbuf_space_width;
+ if (diff == 0 || (equalise_spaces && (diff == 1 || diff == -1))) {
+ sbuf_end_hpos = env->hpos + w + sbuf_kern;
+ sbuf[sbuf_len++] = sbuf_space_code;
+ sbuf[sbuf_len++] = code;
+ sbuf_space_count++;
+ if (diff == 1)
+ sbuf_space_diff_count++;
+ else if (diff == -1)
+ sbuf_space_diff_count--;
+ return;
+ }
+ }
+ }
+ }
+ flush_sbuf();
+ }
+ sbuf_len = 1;
+ sbuf[0] = code;
+ sbuf_end_hpos = env->hpos + w;
+ sbuf_start_hpos = env->hpos;
+ sbuf_vpos = env->vpos;
+ sbuf_style = sty;
+ sbuf_space_code = -1;
+ sbuf_space_width = 0;
+ sbuf_space_count = sbuf_space_diff_count = 0;
+ sbuf_kern = 0;
+ if (sbuf_color != *env->col)
+ set_color(env->col);
+}
+
+static char *make_encoding_name(int encoding_index)
+{
+ static char buf[3 + INT_DIGITS + 1];
+ sprintf(buf, "ENC%d", encoding_index);
+ return buf;
+}
+
+static char *make_subencoding_name(int subencoding_index)
+{
+ static char buf[6 + INT_DIGITS + 1];
+ sprintf(buf, "SUBENC%d", subencoding_index);
+ return buf;
+}
+
+const char *const WS = " \t\n\r";
+
+void ps_printer::define_encoding(const char *encoding, int encoding_index)
+{
+ char *vec[256];
+ int i;
+ for (i = 0; i < 256; i++)
+ vec[i] = 0;
+ char *path;
+ FILE *fp = font::open_file(encoding, &path);
+ if (fp == 0)
+ fatal("can't open encoding file '%1'", encoding);
+ int lineno = 1;
+ const int BUFFER_SIZE = 512;
+ char buf[BUFFER_SIZE];
+ while (fgets(buf, BUFFER_SIZE, fp) != 0) {
+ char *p = buf;
+ while (csspace(*p))
+ p++;
+ if (*p != '#' && *p != '\0' && (p = strtok(buf, WS)) != 0) {
+ char *q = strtok(0, WS);
+ int n = 0; // pacify compiler
+ if (q == 0 || sscanf(q, "%d", &n) != 1 || n < 0 || n >= 256)
+ fatal_with_file_and_line(path, lineno, "bad second field");
+ vec[n] = new char[strlen(p) + 1];
+ strcpy(vec[n], p);
+ }
+ lineno++;
+ }
+ free(path);
+ out.put_literal_symbol(make_encoding_name(encoding_index))
+ .put_delimiter('[');
+ for (i = 0; i < 256; i++) {
+ if (vec[i] == 0)
+ out.put_literal_symbol(".notdef");
+ else {
+ out.put_literal_symbol(vec[i]);
+ delete[] vec[i];
+ }
+ }
+ out.put_delimiter(']')
+ .put_symbol("def");
+ fclose(fp);
+}
+
+void ps_printer::reencode_font(ps_font *f)
+{
+ out.put_literal_symbol(f->reencoded_name)
+ .put_symbol(make_encoding_name(f->encoding_index))
+ .put_literal_symbol(f->get_internal_name())
+ .put_symbol("RE");
+}
+
+void ps_printer::encode_fonts()
+{
+ if (next_encoding_index == 0)
+ return;
+ char *done_encoding = new char[next_encoding_index];
+ for (int i = 0; i < next_encoding_index; i++)
+ done_encoding[i] = 0;
+ for (font_pointer_list *f = font_list; f; f = f->next) {
+ int encoding_index = ((ps_font *)f->p)->encoding_index;
+ if (encoding_index >= 0) {
+ assert(encoding_index < next_encoding_index);
+ if (!done_encoding[encoding_index]) {
+ done_encoding[encoding_index] = 1;
+ define_encoding(((ps_font *)f->p)->encoding, encoding_index);
+ }
+ reencode_font((ps_font *)f->p);
+ }
+ }
+ delete[] done_encoding;
+}
+
+void ps_printer::encode_subfont(subencoding *sub)
+{
+ assert(sub != 0);
+ out.put_literal_symbol(make_subencoding_name(sub->idx))
+ .put_delimiter('[');
+ for (int i = 0; i < 256; i++)
+ {
+ if (sub->glyphs[i])
+ out.put_literal_symbol(sub->glyphs[i]);
+ else
+ out.put_literal_symbol(".notdef");
+ }
+ out.put_delimiter(']')
+ .put_symbol("def");
+}
+
+void ps_printer::set_style(const style &sty)
+{
+ char buf[1 + INT_DIGITS + 1];
+ for (int i = 0; i < ndefined_styles; i++)
+ if (sty == defined_styles[i]) {
+ sprintf(buf, "F%d", i);
+ out.put_symbol(buf);
+ return;
+ }
+ if (ndefined_styles >= MAX_DEFINED_STYLES)
+ ndefined_styles = 0;
+ sprintf(buf, "F%d", ndefined_styles);
+ out.put_literal_symbol(buf);
+ const char *psname = sty.f->get_internal_name();
+ if (psname == 0)
+ fatal("no internalname specified for font '%1'", sty.f->get_name());
+ char *encoding = ((ps_font *)sty.f)->encoding;
+ if (sty.sub == 0) {
+ if (encoding != 0) {
+ char *s = ((ps_font *)sty.f)->reencoded_name;
+ if (s == 0) {
+ int ei = set_encoding_index((ps_font *)sty.f);
+ char *tem = new char[strlen(psname) + 1 + INT_DIGITS + 1];
+ sprintf(tem, "%s@%d", psname, ei);
+ psname = tem;
+ ((ps_font *)sty.f)->reencoded_name = tem;
+ }
+ else
+ psname = s;
+ }
+ }
+ else
+ psname = get_subfont(sty.sub, psname);
+ out.put_fix_number((font::res/(72*font::sizescale))*sty.point_size);
+ if (sty.height != 0 || sty.slant != 0) {
+ int h = sty.height == 0 ? sty.point_size : sty.height;
+ h *= font::res/(72*font::sizescale);
+ int c = int(h*tan(radians(sty.slant)) + .5);
+ out.put_fix_number(c)
+ .put_fix_number(h)
+ .put_literal_symbol(psname)
+ .put_symbol("MF");
+ }
+ else {
+ out.put_literal_symbol(psname)
+ .put_symbol("SF");
+ }
+ defined_styles[ndefined_styles++] = sty;
+}
+
+void ps_printer::set_color(color *col, int fill)
+{
+ sbuf_color = *col;
+ unsigned int components[4];
+ char s[3];
+ color_scheme cs = col->get_components(components);
+ s[0] = fill ? 'F' : 'C';
+ s[2] = 0;
+ switch (cs) {
+ case DEFAULT: // black
+ out.put_symbol("0");
+ s[1] = 'g';
+ break;
+ case RGB:
+ out.put_color(Red)
+ .put_color(Green)
+ .put_color(Blue);
+ s[1] = 'r';
+ break;
+ case CMY:
+ col->get_cmyk(&Cyan, &Magenta, &Yellow, &Black);
+ // fall through
+ case CMYK:
+ out.put_color(Cyan)
+ .put_color(Magenta)
+ .put_color(Yellow)
+ .put_color(Black);
+ s[1] = 'k';
+ cmyk_flag = 1;
+ break;
+ case GRAY:
+ out.put_color(Gray);
+ s[1] = 'g';
+ break;
+ }
+ out.put_symbol(s);
+}
+
+void ps_printer::set_space_code(unsigned char c)
+{
+ out.put_literal_symbol("SC")
+ .put_number(c)
+ .put_symbol("def");
+}
+
+void ps_printer::end_of_line()
+{
+ flush_sbuf();
+ // this ensures that we do an absolute motion to the beginning of a line
+ output_vpos = output_hpos = -1;
+}
+
+void ps_printer::flush_sbuf()
+{
+ enum {
+ NONE,
+ RELATIVE_H,
+ RELATIVE_V,
+ RELATIVE_HV,
+ ABSOLUTE
+ } motion = NONE;
+ int space_flag = 0;
+ if (sbuf_len == 0)
+ return;
+ if (output_style != sbuf_style) {
+ set_style(sbuf_style);
+ output_style = sbuf_style;
+ }
+ int extra_space = 0;
+ if (output_hpos < 0 || output_vpos < 0)
+ motion = ABSOLUTE;
+ else {
+ if (output_hpos != sbuf_start_hpos)
+ motion = RELATIVE_H;
+ if (output_vpos != sbuf_vpos) {
+ if (motion != NONE)
+ motion = RELATIVE_HV;
+ else
+ motion = RELATIVE_V;
+ }
+ }
+ if (sbuf_space_code >= 0) {
+ int w = sbuf_style.f->get_width(space_glyph, sbuf_style.point_size);
+ if (w + sbuf_kern != sbuf_space_width) {
+ if (sbuf_space_code != output_space_code) {
+ set_space_code(sbuf_space_code);
+ output_space_code = sbuf_space_code;
+ }
+ space_flag = 1;
+ extra_space = sbuf_space_width - w - sbuf_kern;
+ if (sbuf_space_diff_count > sbuf_space_count/2)
+ extra_space++;
+ else if (sbuf_space_diff_count < -(sbuf_space_count/2))
+ extra_space--;
+ }
+ }
+ if (space_flag)
+ out.put_fix_number(extra_space);
+ if (sbuf_kern != 0)
+ out.put_fix_number(sbuf_kern);
+ out.put_string(sbuf, sbuf_len);
+ char command_array[] = {'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T'};
+ char sym[2];
+ sym[0] = command_array[motion*4 + space_flag + 2*(sbuf_kern != 0)];
+ sym[1] = '\0';
+ switch (motion) {
+ case NONE:
+ break;
+ case ABSOLUTE:
+ out.put_fix_number(sbuf_start_hpos)
+ .put_fix_number(sbuf_vpos);
+ break;
+ case RELATIVE_H:
+ out.put_fix_number(sbuf_start_hpos - output_hpos);
+ break;
+ case RELATIVE_V:
+ out.put_fix_number(sbuf_vpos - output_vpos);
+ break;
+ case RELATIVE_HV:
+ out.put_fix_number(sbuf_start_hpos - output_hpos)
+ .put_fix_number(sbuf_vpos - output_vpos);
+ break;
+ default:
+ assert(0);
+ }
+ out.put_symbol(sym);
+ output_hpos = sbuf_end_hpos;
+ output_vpos = sbuf_vpos;
+ sbuf_len = 0;
+}
+
+void ps_printer::set_line_thickness_and_color(const environment *env)
+{
+ if (line_thickness < 0) {
+ if (output_draw_point_size != env->size) {
+ // we ought to check for overflow here
+ int lw = ((font::res/(72*font::sizescale))*linewidth*env->size)/1000;
+ out.put_fix_number(lw)
+ .put_symbol("LW");
+ output_draw_point_size = env->size;
+ output_line_thickness = -1;
+ }
+ }
+ else {
+ if (output_line_thickness != line_thickness) {
+ out.put_fix_number(line_thickness)
+ .put_symbol("LW");
+ output_line_thickness = line_thickness;
+ output_draw_point_size = -1;
+ }
+ }
+ if (sbuf_color != *env->col)
+ set_color(env->col);
+}
+
+void ps_printer::fill_path(const environment *env)
+{
+ if (sbuf_color == *env->fill)
+ out.put_symbol("FL");
+ else
+ set_color(env->fill, 1);
+}
+
+void ps_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if (invis_count > 0)
+ return;
+ flush_sbuf();
+ int fill_flag = 0;
+ switch (code) {
+ case 'C':
+ fill_flag = 1;
+ // fall through
+ case 'c':
+ // troff adds an extra argument to C
+ if (np != 1 && !(code == 'C' && np == 2)) {
+ error("1 argument required for circle");
+ break;
+ }
+ out.put_fix_number(env->hpos + p[0]/2)
+ .put_fix_number(env->vpos)
+ .put_fix_number(p[0]/2)
+ .put_symbol("DC");
+ if (fill_flag)
+ fill_path(env);
+ else {
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ case 'l':
+ if (np != 2) {
+ error("2 arguments required for line");
+ break;
+ }
+ set_line_thickness_and_color(env);
+ out.put_fix_number(p[0] + env->hpos)
+ .put_fix_number(p[1] + env->vpos)
+ .put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("DL");
+ break;
+ case 'E':
+ fill_flag = 1;
+ // fall through
+ case 'e':
+ if (np != 2) {
+ error("2 arguments required for ellipse");
+ break;
+ }
+ out.put_fix_number(p[0])
+ .put_fix_number(p[1])
+ .put_fix_number(env->hpos + p[0]/2)
+ .put_fix_number(env->vpos)
+ .put_symbol("DE");
+ if (fill_flag)
+ fill_path(env);
+ else {
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ case 'P':
+ fill_flag = 1;
+ // fall through
+ case 'p':
+ {
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ break;
+ }
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("MT");
+ for (int i = 0; i < np; i += 2)
+ out.put_fix_number(p[i])
+ .put_fix_number(p[i+1])
+ .put_symbol("RL");
+ out.put_symbol("CL");
+ if (fill_flag)
+ fill_path(env);
+ else {
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ }
+ case '~':
+ {
+ if (np & 1) {
+ error("even number of arguments required for spline");
+ break;
+ }
+ if (np == 0) {
+ error("no arguments for spline");
+ break;
+ }
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("MT");
+ out.put_fix_number(p[0]/2)
+ .put_fix_number(p[1]/2)
+ .put_symbol("RL");
+ /* tnum/tden should be between 0 and 1; the closer it is to 1
+ the tighter the curve will be to the guiding lines; 2/3
+ is the standard value */
+ const int tnum = 2;
+ const int tden = 3;
+ for (int i = 0; i < np - 2; i += 2) {
+ out.put_fix_number((p[i]*tnum)/(2*tden))
+ .put_fix_number((p[i + 1]*tnum)/(2*tden))
+ .put_fix_number(p[i]/2 + (p[i + 2]*(tden - tnum))/(2*tden))
+ .put_fix_number(p[i + 1]/2 + (p[i + 3]*(tden - tnum))/(2*tden))
+ .put_fix_number((p[i] - p[i]/2) + p[i + 2]/2)
+ .put_fix_number((p[i + 1] - p[i + 1]/2) + p[i + 3]/2)
+ .put_symbol("RC");
+ }
+ out.put_fix_number(p[np - 2] - p[np - 2]/2)
+ .put_fix_number(p[np - 1] - p[np - 1]/2)
+ .put_symbol("RL");
+ set_line_thickness_and_color(env);
+ out.put_symbol("ST");
+ }
+ break;
+ case 'a':
+ {
+ if (np != 4) {
+ error("4 arguments required for arc");
+ break;
+ }
+ set_line_thickness_and_color(env);
+ double c[2];
+ if (adjust_arc_center(p, c))
+ out.put_fix_number(env->hpos + int(c[0]))
+ .put_fix_number(env->vpos + int(c[1]))
+ .put_fix_number(int(sqrt(c[0]*c[0] + c[1]*c[1])))
+ .put_float(degrees(atan2(-c[1], -c[0])))
+ .put_float(degrees(atan2(p[1] + p[3] - c[1], p[0] + p[2] - c[0])))
+ .put_symbol("DA");
+ else
+ out.put_fix_number(p[0] + p[2] + env->hpos)
+ .put_fix_number(p[1] + p[3] + env->vpos)
+ .put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("DL");
+ }
+ break;
+ case 't':
+ if (np == 0)
+ line_thickness = -1;
+ else {
+ // troff gratuitously adds an extra 0
+ if (np != 1 && np != 2) {
+ error("0 or 1 argument required for thickness");
+ break;
+ }
+ line_thickness = p[0];
+ }
+ break;
+ default:
+ error("unrecognised drawing command '%1'", char(code));
+ break;
+ }
+ output_hpos = output_vpos = -1;
+}
+
+const char *ps_printer::media_name()
+{
+ return "Default";
+}
+
+int ps_printer::media_width()
+{
+ /*
+ * NOTE:
+ * Although paper dimensions are defined as a pair of real numbers,
+ * it seems to be a common convention to round to the nearest
+ * PostScript unit. For example, A4 is really 595.276 by 841.89 but
+ * we use 595 by 842.
+ *
+ * This is probably a good compromise, especially since the
+ * PostScript definition specifies that media matching should be done
+ * within a tolerance of 5 units.
+ */
+ return int(user_paper_width ? user_paper_width*72.0 + 0.5
+ : font::paperwidth*72.0/font::res + 0.5);
+}
+
+int ps_printer::media_height()
+{
+ return int(user_paper_length ? user_paper_length*72.0 + 0.5
+ : paper_length*72.0/font::res + 0.5);
+}
+
+void ps_printer::media_set()
+{
+ /*
+ * The setpagedevice implies an erasepage and initgraphics, and
+ * must thus precede any descriptions for a particular page.
+ *
+ * NOTE:
+ * This does not work with ps2pdf when there are included eps
+ * segments that contain PageSize/setpagedevice.
+ * This might be a bug in ghostscript -- must be investigated.
+ * Using setpagedevice in an .eps is really the wrong concept, anyway.
+ *
+ * NOTE:
+ * For the future, this is really the place to insert other
+ * media selection features, like:
+ * MediaColor
+ * MediaPosition
+ * MediaType
+ * MediaWeight
+ * MediaClass
+ * TraySwitch
+ * ManualFeed
+ * InsertSheet
+ * Duplex
+ * Collate
+ * ProcessColorModel
+ * etc.
+ */
+ if (!(broken_flags & (USE_PS_ADOBE_2_0|NO_PAPERSIZE))) {
+ out.begin_comment("BeginFeature:")
+ .comment_arg("*PageSize")
+ .comment_arg(media_name())
+ .end_comment();
+ int w = media_width();
+ int h = media_height();
+ if (w > 0 && h > 0)
+ // warning to user is done elsewhere
+ fprintf(out.get_file(),
+ "<< /PageSize [ %d %d ] /ImagingBBox null >> setpagedevice\n",
+ w, h);
+ out.simple_comment("EndFeature");
+ }
+}
+
+void ps_printer::begin_page(int n)
+{
+ out.begin_comment("Page:")
+ .comment_arg(i_to_a(n));
+ out.comment_arg(i_to_a(++pages_output))
+ .end_comment();
+ output_style.f = 0;
+ output_space_code = 32;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ output_hpos = output_vpos = -1;
+ ndefined_styles = 0;
+ out.simple_comment("BeginPageSetup");
+
+#if 0
+ /*
+ * NOTE:
+ * may decide to do this once per page
+ */
+ media_set();
+#endif
+
+ out.put_symbol("BP")
+ .simple_comment("EndPageSetup");
+ if (sbuf_color != default_color)
+ set_color(&sbuf_color);
+}
+
+void ps_printer::end_page(int)
+{
+ flush_sbuf();
+ set_color(&default_color);
+ out.put_symbol("EP");
+ if (invis_count != 0) {
+ error("missing 'endinvis' command");
+ invis_count = 0;
+ }
+}
+
+font *ps_printer::make_font(const char *nm)
+{
+ return ps_font::load_ps_font(nm);
+}
+
+ps_printer::~ps_printer()
+{
+ out.simple_comment("Trailer")
+ .put_symbol("end")
+ .simple_comment("EOF");
+ if (fseek(tempfp, 0L, 0) < 0)
+ fatal("fseek on temporary file failed");
+ fputs("%!PS-Adobe-", stdout);
+ fputs((broken_flags & USE_PS_ADOBE_2_0) ? "2.0" : "3.0", stdout);
+ putchar('\n');
+ out.set_file(stdout);
+ if (cmyk_flag)
+ out.begin_comment("Extensions:")
+ .comment_arg("CMYK")
+ .end_comment();
+ out.begin_comment("Creator:")
+ .comment_arg("groff")
+ .comment_arg("version")
+ .comment_arg(Version_string)
+ .end_comment();
+ {
+ fputs("%%CreationDate: ", out.get_file());
+#ifdef LONG_FOR_TIME_T
+ long
+#else
+ time_t
+#endif
+ t = current_time();
+ fputs(ctime(&t), out.get_file());
+ }
+ for (font_pointer_list *f = font_list; f; f = f->next) {
+ ps_font *psf = (ps_font *)(f->p);
+ rm.need_font(psf->get_internal_name());
+ }
+ rm.print_header_comments(out);
+ out.begin_comment("Pages:")
+ .comment_arg(i_to_a(pages_output))
+ .end_comment();
+ out.begin_comment("PageOrder:")
+ .comment_arg("Ascend")
+ .end_comment();
+ if (!(broken_flags & NO_PAPERSIZE)) {
+ int w = media_width();
+ int h = media_height();
+ if (w > 0 && h > 0)
+ fprintf(out.get_file(),
+ "%%%%DocumentMedia: %s %d %d %d %s %s\n",
+ media_name(), // tag name of media
+ w, // media width
+ h, // media height
+ 0, // weight in g/m2
+ "()", // paper color, e.g. white
+ "()" // preprinted form type
+ );
+ else {
+ if (h <= 0)
+ // see ps_printer::ps_printer
+ warning("bad paper height, defaulting to 11i");
+ if (w <= 0)
+ warning("bad paper width");
+ }
+ }
+ out.begin_comment("Orientation:")
+ .comment_arg(landscape_flag ? "Landscape" : "Portrait")
+ .end_comment();
+ if (ncopies != 1) {
+ out.end_line();
+ fprintf(out.get_file(), "%%%%Requirements: numcopies(%d)\n", ncopies);
+ }
+ out.simple_comment("EndComments");
+ if (!(broken_flags & NO_PAPERSIZE)) {
+ /* gv works fine without this one, but it really should be there. */
+ out.simple_comment("BeginDefaults");
+ fprintf(out.get_file(), "%%%%PageMedia: %s\n", media_name());
+ out.simple_comment("EndDefaults");
+ }
+ out.simple_comment("BeginProlog");
+ rm.output_prolog(out);
+ if (!(broken_flags & NO_SETUP_SECTION)) {
+ out.simple_comment("EndProlog");
+ out.simple_comment("BeginSetup");
+ }
+#if 1
+ /*
+ * Define paper (i.e., media) size for entire document here.
+ * This allows ps2pdf to correctly determine page size, for instance.
+ */
+ media_set();
+#endif
+ rm.document_setup(out);
+ out.put_symbol(dict_name)
+ .put_symbol("begin");
+ if (ndefs > 0)
+ ndefs += DEFS_DICT_SPARE;
+ out.put_literal_symbol(defs_dict_name)
+ .put_number(ndefs + 1)
+ .put_symbol("dict")
+ .put_symbol("def");
+ out.put_symbol(defs_dict_name)
+ .put_symbol("begin");
+ out.put_literal_symbol("u")
+ .put_delimiter('{')
+ .put_fix_number(1)
+ .put_symbol("mul")
+ .put_delimiter('}')
+ .put_symbol("bind")
+ .put_symbol("def");
+ defs += '\0';
+ out.special(defs.contents());
+ out.put_symbol("end");
+ if (ncopies != 1)
+ out.put_literal_symbol("#copies")
+ .put_number(ncopies)
+ .put_symbol("def");
+ out.put_literal_symbol("RES")
+ .put_number(res)
+ .put_symbol("def");
+ out.put_literal_symbol("PL");
+ if (guess_flag)
+ out.put_symbol("PLG");
+ else
+ out.put_fix_number(paper_length);
+ out.put_symbol("def");
+ out.put_literal_symbol("LS")
+ .put_symbol(landscape_flag ? "true" : "false")
+ .put_symbol("def");
+ if (manual_feed_flag) {
+ out.begin_comment("BeginFeature:")
+ .comment_arg("*ManualFeed")
+ .comment_arg("True")
+ .end_comment()
+ .put_symbol("MANUAL")
+ .simple_comment("EndFeature");
+ }
+ encode_fonts();
+ while (subencodings) {
+ subencoding *tem = subencodings;
+ subencodings = subencodings->next;
+ encode_subfont(tem);
+ out.put_literal_symbol(tem->subfont)
+ .put_symbol(make_subencoding_name(tem->idx))
+ .put_literal_symbol(tem->p->get_internal_name())
+ .put_symbol("RE");
+ delete tem;
+ }
+ out.simple_comment((broken_flags & NO_SETUP_SECTION)
+ ? "EndProlog"
+ : "EndSetup");
+ out.end_line();
+ out.copy_file(tempfp);
+ fclose(tempfp);
+}
+
+void ps_printer::special(char *arg, const environment *env, char type)
+{
+ if (type != 'p')
+ return;
+ typedef void (ps_printer::*SPECIAL_PROCP)(char *, const environment *);
+ static const struct {
+ const char *name;
+ SPECIAL_PROCP proc;
+ } proc_table[] = {
+ { "exec", &ps_printer::do_exec },
+ { "def", &ps_printer::do_def },
+ { "mdef", &ps_printer::do_mdef },
+ { "import", &ps_printer::do_import },
+ { "file", &ps_printer::do_file },
+ { "invis", &ps_printer::do_invis },
+ { "endinvis", &ps_printer::do_endinvis },
+ };
+ char *p;
+ for (p = arg; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *tag = p;
+ for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*p == '\0' || strncmp(tag, "ps", p - tag) != 0) {
+ error("X command without 'ps:' tag ignored");
+ return;
+ }
+ p++;
+ for (; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *command = p;
+ for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*command == '\0') {
+ error("empty X command ignored");
+ return;
+ }
+ for (unsigned int i = 0; i < sizeof(proc_table)/sizeof(proc_table[0]); i++)
+ if (strncmp(command, proc_table[i].name, p - command) == 0) {
+ flush_sbuf();
+ if (sbuf_color != *env->col)
+ set_color(env->col);
+ (this->*(proc_table[i].proc))(p, env);
+ return;
+ }
+ error("X command '%1' not recognised", command);
+}
+
+// A conforming PostScript document must not have lines longer
+// than 255 characters (excluding line termination characters).
+
+static int check_line_lengths(const char *p)
+{
+ for (;;) {
+ const char *end = strchr(p, '\n');
+ if (end == 0)
+ end = strchr(p, '\0');
+ if (end - p > 255)
+ return 0;
+ if (*end == '\0')
+ break;
+ p = end + 1;
+ }
+ return 1;
+}
+
+void ps_printer::do_exec(char *arg, const environment *env)
+{
+ while (csspace(*arg))
+ arg++;
+ if (*arg == '\0') {
+ error("missing argument to X exec command");
+ return;
+ }
+ if (!check_line_lengths(arg))
+ warning("lines in X exec command should"
+ " not be more than 255 characters long");
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("EBEGIN")
+ .special(arg)
+ .put_symbol("EEND");
+ output_hpos = output_vpos = -1;
+ output_style.f = 0;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ ndefined_styles = 0;
+ if (!ndefs)
+ ndefs = 1;
+}
+
+void ps_printer::do_file(char *arg, const environment *env)
+{
+ while (csspace(*arg))
+ arg++;
+ if (*arg == '\0') {
+ error("missing argument to X file command");
+ return;
+ }
+ const char *filename = arg;
+ do {
+ ++arg;
+ } while (*arg != '\0' && *arg != ' ' && *arg != '\n');
+ out.put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("EBEGIN");
+ rm.import_file(filename, out);
+ out.put_symbol("EEND");
+ output_hpos = output_vpos = -1;
+ output_style.f = 0;
+ output_draw_point_size = -1;
+ output_line_thickness = -1;
+ ndefined_styles = 0;
+ if (!ndefs)
+ ndefs = 1;
+}
+
+void ps_printer::do_def(char *arg, const environment *)
+{
+ while (csspace(*arg))
+ arg++;
+ if (!check_line_lengths(arg))
+ warning("lines in X def command should"
+ " not be more than 255 characters long");
+ defs += arg;
+ if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
+ defs += '\n';
+ ndefs++;
+}
+
+// Like def, but the first argument says how many definitions it contains.
+
+void ps_printer::do_mdef(char *arg, const environment *)
+{
+ char *p;
+ int n = (int)strtol(arg, &p, 10);
+ if (n == 0 && p == arg) {
+ error("first argument to X mdef must be an integer");
+ return;
+ }
+ if (n < 0) {
+ error("out of range argument '%1' to X mdef command", int(n));
+ return;
+ }
+ arg = p;
+ while (csspace(*arg))
+ arg++;
+ if (!check_line_lengths(arg))
+ warning("lines in X mdef command should"
+ " not be more than 255 characters long");
+ defs += arg;
+ if (*arg != '\0' && strchr(arg, '\0')[-1] != '\n')
+ defs += '\n';
+ ndefs += n;
+}
+
+void ps_printer::do_import(char *arg, const environment *env)
+{
+ while (*arg == ' ' || *arg == '\n')
+ arg++;
+ char *p;
+ for (p = arg; *p != '\0' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*p != '\0')
+ *p++ = '\0';
+ int parms[6];
+ int nparms = 0;
+ while (nparms < 6) {
+ char *end;
+ long n = strtol(p, &end, 10);
+ if (n == 0 && end == p)
+ break;
+ parms[nparms++] = int(n);
+ p = end;
+ }
+ if (csalpha(*p) && (p[1] == '\0' || p[1] == ' ' || p[1] == '\n')) {
+ error("scaling units not allowed in arguments for X import command");
+ return;
+ }
+ while (*p == ' ' || *p == '\n')
+ p++;
+ if (nparms < 5) {
+ if (*p == '\0')
+ error("too few arguments for X import command");
+ else
+ error("invalid argument '%1' for X import command", p);
+ return;
+ }
+ if (*p != '\0') {
+ error("superfluous argument '%1' for X import command", p);
+ return;
+ }
+ int llx = parms[0];
+ int lly = parms[1];
+ int urx = parms[2];
+ int ury = parms[3];
+ int desired_width = parms[4];
+ int desired_height = parms[5];
+ if (desired_width <= 0) {
+ error("bad width argument '%1' for X import command: must be > 0",
+ desired_width);
+ return;
+ }
+ if (nparms == 6 && desired_height <= 0) {
+ error("bad height argument '%1' for X import command: must be > 0",
+ desired_height);
+ return;
+ }
+ if (llx == urx) {
+ error("llx and urx arguments for X import command must not be equal");
+ return;
+ }
+ if (lly == ury) {
+ error("lly and ury arguments for X import command must not be equal");
+ return;
+ }
+ if (nparms == 5) {
+ int old_wid = urx - llx;
+ int old_ht = ury - lly;
+ if (old_wid < 0)
+ old_wid = -old_wid;
+ if (old_ht < 0)
+ old_ht = -old_ht;
+ desired_height = int(desired_width*(double(old_ht)/double(old_wid)) + .5);
+ }
+ if (env->vpos - desired_height < 0)
+ warning("top of imported graphic is above the top of the page");
+ out.put_number(llx)
+ .put_number(lly)
+ .put_fix_number(desired_width)
+ .put_number(urx - llx)
+ .put_fix_number(-desired_height)
+ .put_number(ury - lly)
+ .put_fix_number(env->hpos)
+ .put_fix_number(env->vpos)
+ .put_symbol("PBEGIN");
+ rm.import_file(arg, out);
+ // do this here just in case application defines PEND
+ out.put_symbol("end")
+ .put_symbol("PEND");
+}
+
+void ps_printer::do_invis(char *, const environment *)
+{
+ invis_count++;
+}
+
+void ps_printer::do_endinvis(char *, const environment *)
+{
+ if (invis_count == 0)
+ error("unbalanced 'endinvis' command");
+ else
+ --invis_count;
+}
+
+printer *make_printer()
+{
+ return new ps_printer(user_paper_length);
+}
+
+static void usage(FILE *stream);
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_NUMERIC, "C");
+ program_name = argv[0];
+ string env;
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "b:c:F:gI:lmp:P:vw:", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'b':
+ // XXX check this
+ broken_flags = atoi(optarg);
+ bflag = 1;
+ break;
+ case 'c':
+ if (sscanf(optarg, "%d", &ncopies) != 1 || ncopies <= 0) {
+ error("bad number of copies '%1'", optarg);
+ ncopies = 1;
+ }
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'g':
+ guess_flag = 1;
+ break;
+ case 'I':
+ include_search_path.command_line_dir(optarg);
+ break;
+ case 'l':
+ landscape_flag = 1;
+ break;
+ case 'm':
+ manual_feed_flag = 1;
+ break;
+ case 'p':
+ if (!font::scan_papersize(optarg, 0,
+ &user_paper_length, &user_paper_width))
+ error("ignoring invalid custom paper format '%1'", optarg);
+ break;
+ case 'P':
+ env = "GROPS_PROLOGUE";
+ env += '=';
+ env += optarg;
+ env += '\0';
+ if (putenv(strsave(env.contents())))
+ fatal("putenv failed");
+ break;
+ case 'v':
+ printf("GNU grops (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ case 'w':
+ if (sscanf(optarg, "%d", &linewidth) != 1 || linewidth < 0) {
+ error("invalid line width '%1' ignored", optarg);
+ linewidth = -1;
+ }
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr);
+ exit(1);
+ break;
+ default:
+ assert(0);
+ }
+ font::set_unknown_desc_command_handler(handle_unknown_desc_command);
+ SET_BINARY(fileno(stdout));
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-glm] [-b brokenness-flags] [-c num-copies]"
+" [-F font-directory] [-I inclusion-directory] [-p paper-format]"
+" [-P prologue-file] [-w rule-thickness] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+ if (stdout == stream)
+ fputs(
+"\n"
+"Translate the output of troff(1) into PostScript. See the grops(1)\n"
+"manual page.\n",
+ stream);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grops/ps.h b/src/devices/grops/ps.h
new file mode 100644
index 0000000..5cef694
--- /dev/null
+++ b/src/devices/grops/ps.h
@@ -0,0 +1,129 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class ps_output {
+public:
+ ps_output(FILE *, int max_line_length);
+ ps_output &put_string(const char *, int);
+ ps_output &put_number(int);
+ ps_output &put_fix_number(int);
+ ps_output &put_float(double);
+ ps_output &put_symbol(const char *);
+ ps_output &put_color(unsigned int);
+ ps_output &put_literal_symbol(const char *);
+ ps_output &set_fixed_point(int);
+ ps_output &simple_comment(const char *);
+ ps_output &begin_comment(const char *);
+ ps_output &comment_arg(const char *);
+ ps_output &end_comment();
+ ps_output &set_file(FILE *);
+ ps_output &include_file(FILE *);
+ ps_output &copy_file(FILE *);
+ ps_output &end_line();
+ ps_output &put_delimiter(char);
+ ps_output &special(const char *);
+ FILE *get_file();
+private:
+ FILE *fp;
+ int col;
+ int max_line_length; // not including newline
+ int need_space;
+ int fixed_point;
+};
+
+inline FILE *ps_output::get_file()
+{
+ return fp;
+}
+
+// this must stay in sync with 'resource_table' in 'psrm.cpp'
+enum resource_type {
+ RESOURCE_FONT,
+ RESOURCE_FONTSET,
+ RESOURCE_PROCSET,
+ RESOURCE_FILE,
+ RESOURCE_ENCODING,
+ RESOURCE_FORM,
+ RESOURCE_PATTERN
+};
+
+struct resource;
+
+extern string an_empty_string;
+
+class resource_manager {
+public:
+ resource_manager();
+ ~resource_manager();
+ void import_file(const char *filename, ps_output &);
+ void need_font(const char *name);
+ void print_header_comments(ps_output &);
+ void document_setup(ps_output &);
+ void output_prolog(ps_output &);
+private:
+ unsigned extensions;
+ unsigned language_level;
+ resource *procset_resource;
+ resource *resource_list;
+ resource *lookup_resource(resource_type type, string &name,
+ string &version = an_empty_string,
+ unsigned revision = 0);
+ resource *lookup_font(const char *name);
+ void read_download_file();
+ void supply_resource(resource *r, int rank, FILE *outfp,
+ int is_document = 0);
+ void process_file(int rank, FILE *fp, const char *filename, FILE *outfp);
+ resource *read_file_arg(const char **);
+ resource *read_procset_arg(const char **);
+ resource *read_font_arg(const char **);
+ resource *read_resource_arg(const char **);
+ void print_resources_comment(unsigned flag, FILE *outfp);
+ void print_extensions_comment(FILE *outfp);
+ void print_language_level_comment(FILE *outfp);
+ int do_begin_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_document(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_document(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_procset(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_procset(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_font(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_font(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_file(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_include_file(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int change_to_end_resource(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_preview(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_data(const char *ptr, int rank, FILE *fp, FILE *outfp);
+ int do_begin_binary(const char *ptr, int rank, FILE *fp, FILE *outfp);
+};
+
+extern unsigned broken_flags;
+
+// broken_flags is ored from these
+
+enum {
+ NO_SETUP_SECTION = 01,
+ STRIP_PERCENT_BANG = 02,
+ STRIP_STRUCTURE_COMMENTS = 04,
+ USE_PS_ADOBE_2_0 = 010,
+ NO_PAPERSIZE = 020
+};
+
+#include "searchpath.h"
+
+extern search_path include_search_path;
diff --git a/src/devices/grops/psfig.diff b/src/devices/grops/psfig.diff
new file mode 100644
index 0000000..4e0d1f9
--- /dev/null
+++ b/src/devices/grops/psfig.diff
@@ -0,0 +1,106 @@
+These are patches to makes psfig work with groff. They apply to the
+version of psfig in comp.sources.unix/Volume11. After applying them,
+psfig should be recompiled with -DGROFF. The resulting psfig will
+work only with groff, so you might want to install it under a
+different name. The output of this psfig must be processed using the
+macros in the file ../tmac/tmac.psfig. These will automatically add
+the necessary PostScript code to the prologue output by grops. Use of
+the 'global' feature in psfig will result in non-conformant PostScript
+which will fail if processed by a page reversal program. Note that
+psfig is unsupported by me (I'm not interested in hearing about psfig
+problems.) For new documents, I recommend using the PostScript
+inclusion features provided by grops.
+
+James Clark
+jjc@jclark.com
+
+*** cmds.c.~1~ Thu Feb 14 16:09:45 1991
+--- cmds.c Mon Mar 4 12:49:26 1991
+***************
+*** 245,253 ****
+--- 245,261 ----
+ (void) sprintf(x, "%.2fp", fx);
+ (void) sprintf(y, "%.2fp", fy);
+ } else if (!*x) {
++ #ifndef GROFF
+ (void) sprintf(x,"(%.2fp*%s/%.2fp)", fx, y, fy);
++ #else /* GROFF */
++ (void) sprintf(x,"(%.0fu*%s/%.0fu)", fx, y, fy);
++ #endif /* GROFF */
+ } else if (!*y) {
++ #ifndef GROFF
+ (void) sprintf(y,"(%.2fp*%s/%.2fp)", fy, x, fx);
++ #else /* GROFF */
++ (void) sprintf(y,"(%.0fu*%s/%.0fu)", fy, x, fx);
++ #endif /* GROFF */
+ }
+
+ /*
+*** troff.c.~1~ Thu Feb 14 16:09:48 1991
+--- troff.c Mon Mar 4 12:48:46 1991
+***************
+*** 26,32 ****
+--- 26,36 ----
+ }
+
+
++ #ifndef GROFF
+ char incl_file_s[] = "\\X'f%s'";
++ #else /* GROFF */
++ char incl_file_s[] = "\\X'ps: file %s'";
++ #endif /* GROFF */
+ includeFile(filenm)
+ char *filenm; {
+ printf(incl_file_s, filenm);
+***************
+*** 40,52 ****
+--- 44,64 ----
+ error("buffer overflow");
+ }
+
++ #ifndef GROFF
+ char endfig_s[] = "\\X'pendFig'";
++ #else /* GROFF */
++ char endfig_s[] = "\\X'ps: exec psfigend'";
++ #endif /* GROFF */
+ endfig() {
+ printf(endfig_s);
+ }
+
+ char startfig_s[] =
++ #ifndef GROFF
+ "\\X'p\\w@\\h@%s@@'\\X'p\\w@\\h@%s@@'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'p%.2f'\\X'pstartFig'";
++ #else /* GROFF */
++ "\\X'ps: exec \\w@\\h@%s@@ \\w@\\h@%s@@ %.2f %.2f %.2f %.2f psfigstart'";
++ #endif /* GROFF */
+
+ startfig(x, y, llx, lly, urx, ury)
+ char *x, *y;
+***************
+*** 57,63 ****
+--- 69,79 ----
+ }
+
+ emitDoClip() {
++ #ifndef GROFF
+ printf("\\X'pdoclip'");
++ #else /* GROFF */
++ printf("\\X'ps: exec psfigclip'");
++ #endif /* GROFF */
+ }
+
+ flushX()
+***************
+*** 116,122 ****
+--- 132,142 ----
+
+ #define isWhite(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n')
+
++ #ifndef GROFF
+ char literal_s[] = "\\X'p%s'";
++ #else /* GROFF */
++ char literal_s[] = "\\X'ps: exec %s'";
++ #endif /* GROFF */
+ emitLiteral(text)
+ char *text; {
+ static char litbuf[BUFSZ];
diff --git a/src/devices/grops/psrm.cpp b/src/devices/grops/psrm.cpp
new file mode 100644
index 0000000..3c9a8b7
--- /dev/null
+++ b/src/devices/grops/psrm.cpp
@@ -0,0 +1,1189 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "stringclass.h"
+#include "cset.h"
+
+#include "ps.h"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+#define GROPS_PROLOGUE "prologue"
+
+static void print_ps_string(const string &s, FILE *outfp);
+
+cset white_space("\n\r \t\f");
+string an_empty_string;
+
+char valid_input_table[256]= {
+#ifndef IS_EBCDIC_HOST
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+#else
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+#endif
+};
+
+const char *extension_table[] = {
+ "DPS",
+ "CMYK",
+ "Composite",
+ "FileSystem",
+};
+
+const int NEXTENSIONS = sizeof(extension_table)/sizeof(extension_table[0]);
+
+// this must stay in sync with 'resource_type' in 'ps.h'
+const char *resource_table[] = {
+ "font",
+ "fontset",
+ "procset",
+ "file",
+ "encoding",
+ "form",
+ "pattern",
+};
+
+const int NRESOURCES = sizeof(resource_table)/sizeof(resource_table[0]);
+
+static int read_uint_arg(const char **pp, unsigned *res)
+{
+ while (white_space(**pp))
+ *pp += 1;
+ if (**pp == '\0') {
+ error("missing argument");
+ return 0;
+ }
+ const char *start = *pp;
+ // XXX use strtoul
+ long n = strtol(start, (char **)pp, 10);
+ if (n == 0 && *pp == start) {
+ error("not an integer");
+ return 0;
+ }
+ if (n < 0) {
+ error("argument must not be negative");
+ return 0;
+ }
+ *res = unsigned(n);
+ return 1;
+}
+
+struct resource {
+ resource *next;
+ resource_type type;
+ string name;
+ enum { NEEDED = 01, SUPPLIED = 02, FONT_NEEDED = 04, BUSY = 010 };
+ unsigned flags;
+ string version;
+ unsigned revision;
+ char *filename;
+ int rank;
+ resource(resource_type, string &, string & = an_empty_string, unsigned = 0);
+ ~resource();
+ void print_type_and_name(FILE *outfp);
+};
+
+resource::resource(resource_type t, string &n, string &v, unsigned r)
+: next(0), type(t), flags(0), revision(r), filename(0), rank(-1)
+{
+ name.move(n);
+ version.move(v);
+ if (type == RESOURCE_FILE) {
+ if (name.search('\0') >= 0)
+ error("filename contains a character with code 0");
+ filename = name.extract();
+ }
+}
+
+resource::~resource()
+{
+ free(filename);
+}
+
+void resource::print_type_and_name(FILE *outfp)
+{
+ fputs(resource_table[type], outfp);
+ putc(' ', outfp);
+ print_ps_string(name, outfp);
+ if (type == RESOURCE_PROCSET) {
+ putc(' ', outfp);
+ print_ps_string(version, outfp);
+ fprintf(outfp, " %u", revision);
+ }
+}
+
+resource_manager::resource_manager()
+: extensions(0), language_level(0), resource_list(0)
+{
+ read_download_file();
+ string procset_name("grops");
+ extern const char *version_string;
+ extern const char *revision_string;
+ unsigned revision_uint;
+ if (!read_uint_arg(&revision_string, &revision_uint))
+ revision_uint = 0;
+ string procset_version(version_string);
+ procset_resource = lookup_resource(RESOURCE_PROCSET, procset_name,
+ procset_version, revision_uint);
+ procset_resource->flags |= resource::SUPPLIED;
+}
+
+resource_manager::~resource_manager()
+{
+ while (resource_list) {
+ resource *tem = resource_list;
+ resource_list = resource_list->next;
+ delete tem;
+ }
+}
+
+resource *resource_manager::lookup_resource(resource_type type,
+ string &name,
+ string &version,
+ unsigned revision)
+{
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->type == type
+ && r->name == name
+ && r->version == version
+ && r->revision == revision)
+ return r;
+ r = new resource(type, name, version, revision);
+ r->next = resource_list;
+ resource_list = r;
+ return r;
+}
+
+// Just a specialized version of lookup_resource().
+
+resource *resource_manager::lookup_font(const char *name)
+{
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT
+ && strlen(name) == (size_t)r->name.length()
+ && memcmp(name, r->name.contents(), r->name.length()) == 0)
+ return r;
+ string s(name);
+ r = new resource(RESOURCE_FONT, s);
+ r->next = resource_list;
+ resource_list = r;
+ return r;
+}
+
+void resource_manager::need_font(const char *name)
+{
+ lookup_font(name)->flags |= resource::FONT_NEEDED;
+}
+
+typedef resource *Presource; // Work around g++ bug.
+
+void resource_manager::document_setup(ps_output &out)
+{
+ int nranks = 0;
+ resource *r;
+ for (r = resource_list; r; r = r->next)
+ if (r->rank >= nranks)
+ nranks = r->rank + 1;
+ if (nranks > 0) {
+ // Sort resource_list in reverse order of rank.
+ Presource *head = new Presource[nranks + 1];
+ Presource **tail = new Presource *[nranks + 1];
+ int i;
+ for (i = 0; i < nranks + 1; i++) {
+ head[i] = 0;
+ tail[i] = &head[i];
+ }
+ for (r = resource_list; r; r = r->next) {
+ i = r->rank < 0 ? 0 : r->rank + 1;
+ *tail[i] = r;
+ tail[i] = &(*tail[i])->next;
+ }
+ resource_list = 0;
+ for (i = 0; i < nranks + 1; i++)
+ if (head[i]) {
+ *tail[i] = resource_list;
+ resource_list = head[i];
+ }
+ delete[] head;
+ delete[] tail;
+ // check it
+ for (r = resource_list; r; r = r->next)
+ if (r->next)
+ assert(r->rank >= r->next->rank);
+ for (r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT && r->rank >= 0)
+ supply_resource(r, -1, out.get_file());
+ }
+}
+
+void resource_manager::print_resources_comment(unsigned flag,
+ FILE *outfp)
+{
+ int continued = 0;
+ for (resource *r = resource_list; r; r = r->next)
+ if (r->flags & flag) {
+ if (continued)
+ fputs("%%+ ", outfp);
+ else {
+ fputs(flag == resource::NEEDED
+ ? "%%DocumentNeededResources: "
+ : "%%DocumentSuppliedResources: ",
+ outfp);
+ continued = 1;
+ }
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+}
+
+void resource_manager::print_header_comments(ps_output &out)
+{
+ for (resource *r = resource_list; r; r = r->next)
+ if (r->type == RESOURCE_FONT && (r->flags & resource::FONT_NEEDED))
+ supply_resource(r, 0, 0);
+ print_resources_comment(resource::NEEDED, out.get_file());
+ print_resources_comment(resource::SUPPLIED, out.get_file());
+ print_language_level_comment(out.get_file());
+ print_extensions_comment(out.get_file());
+}
+
+void resource_manager::output_prolog(ps_output &out)
+{
+ FILE *outfp = out.get_file();
+ out.end_line();
+ char *path;
+ if (!getenv("GROPS_PROLOGUE")) {
+ string e = "GROPS_PROLOGUE";
+ e += '=';
+ e += GROPS_PROLOGUE;
+ e += '\0';
+ if (putenv(strsave(e.contents())))
+ fatal("putenv failed");
+ }
+ char *prologue = getenv("GROPS_PROLOGUE");
+ FILE *fp = font::open_file(prologue, &path);
+ if (!fp)
+ fatal("failed to open PostScript prologue '%1': %2", prologue,
+ strerror(errno));
+ fputs("%%BeginResource: ", outfp);
+ procset_resource->print_type_and_name(outfp);
+ putc('\n', outfp);
+ process_file(-1, fp, path, outfp);
+ fclose(fp);
+ free(path);
+ fputs("%%EndResource\n", outfp);
+}
+
+void resource_manager::import_file(const char *filename, ps_output &out)
+{
+ out.end_line();
+ string name(filename);
+ resource *r = lookup_resource(RESOURCE_FILE, name);
+ supply_resource(r, -1, out.get_file(), 1);
+}
+
+void resource_manager::supply_resource(resource *r, int rank,
+ FILE *outfp, int is_document)
+{
+ if (r->flags & resource::BUSY) {
+ r->name += '\0';
+ fatal("loop detected in dependency graph for %1 '%2'",
+ resource_table[r->type],
+ r->name.contents());
+ }
+ r->flags |= resource::BUSY;
+ if (rank > r->rank)
+ r->rank = rank;
+ char *path = 0 /* nullptr */;
+ FILE *fp = 0 /* nullptr */;
+ if (r->filename != 0 /* nullptr */) {
+ if (r->type == RESOURCE_FONT) {
+ fp = font::open_file(r->filename, &path);
+ if (!fp) {
+ error("failed to open PostScript resource '%1': %2",
+ r->filename, strerror(errno));
+ delete[] r->filename;
+ r->filename = 0 /* nullptr */;
+ }
+ }
+ else {
+ errno = 0;
+ fp = include_search_path.open_file_cautious(r->filename);
+ if (!fp) {
+ error("can't open '%1': %2", r->filename, strerror(errno));
+ delete[] r->filename;
+ r->filename = 0 /* nullptr */;
+ }
+ else
+ path = r->filename;
+ }
+ }
+ if (fp) {
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document) {
+ fputs("%%BeginDocument: ", outfp);
+ print_ps_string(r->name, outfp);
+ putc('\n', outfp);
+ }
+ else {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ process_file(rank, fp, path, outfp);
+ fclose(fp);
+ if (r->type == RESOURCE_FONT)
+ free(path);
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document)
+ fputs("%%EndDocument\n", outfp);
+ else
+ fputs("%%EndResource\n", outfp);
+ }
+ r->flags |= resource::SUPPLIED;
+ }
+ else {
+ if (outfp) {
+ if (r->type == RESOURCE_FILE && is_document) {
+ fputs("%%IncludeDocument: ", outfp);
+ print_ps_string(r->name, outfp);
+ putc('\n', outfp);
+ }
+ else {
+ fputs("%%IncludeResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ r->flags |= resource::NEEDED;
+ }
+ r->flags &= ~resource::BUSY;
+}
+
+#define PS_MAGIC "%!PS-Adobe-"
+
+static int ps_get_line(string &buf, FILE *fp)
+{
+ buf.clear();
+ int c = getc(fp);
+ if (c == EOF)
+ return 0;
+ current_lineno++;
+ while (c != '\r' && c != '\n' && c != EOF) {
+ if (!valid_input_table[c])
+ error("invalid input character code %1", int(c));
+ buf += c;
+ c = getc(fp);
+ }
+ buf += '\n';
+ buf += '\0';
+ if (c == '\r') {
+ c = getc(fp);
+ if (c != EOF && c != '\n')
+ ungetc(c, fp);
+ }
+ return 1;
+}
+
+static int read_text_arg(const char **pp, string &res)
+{
+ res.clear();
+ while (white_space(**pp))
+ *pp += 1;
+ if (**pp == '\0') {
+ error("missing argument");
+ return 0;
+ }
+ if (**pp != '(') {
+ for (; **pp != '\0' && !white_space(**pp); *pp += 1)
+ res += **pp;
+ return 1;
+ }
+ *pp += 1;
+ res.clear();
+ int level = 0;
+ for (;;) {
+ if (**pp == '\0' || **pp == '\r' || **pp == '\n') {
+ error("missing ')'");
+ return 0;
+ }
+ if (**pp == ')') {
+ if (level == 0) {
+ *pp += 1;
+ break;
+ }
+ res += **pp;
+ level--;
+ }
+ else if (**pp == '(') {
+ level++;
+ res += **pp;
+ }
+ else if (**pp == '\\') {
+ *pp += 1;
+ switch (**pp) {
+ case 'n':
+ res += '\n';
+ break;
+ case 'r':
+ res += '\n';
+ break;
+ case 't':
+ res += '\t';
+ break;
+ case 'b':
+ res += '\b';
+ break;
+ case 'f':
+ res += '\f';
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ int val = **pp - '0';
+ if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
+ *pp += 1;
+ val = val*8 + (**pp - '0');
+ if ((*pp)[1] >= '0' && (*pp)[1] <= '7') {
+ *pp += 1;
+ val = val*8 + (**pp - '0');
+ }
+ }
+ }
+ break;
+ default:
+ res += **pp;
+ break;
+ }
+ }
+ else
+ res += **pp;
+ *pp += 1;
+ }
+ return 1;
+}
+
+resource *resource_manager::read_file_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(RESOURCE_FILE, arg);
+}
+
+resource *resource_manager::read_font_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(RESOURCE_FONT, arg);
+}
+
+resource *resource_manager::read_procset_arg(const char **ptr)
+{
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ string version;
+ if (!read_text_arg(ptr, version))
+ return 0;
+ unsigned revision;
+ if (!read_uint_arg(ptr, &revision))
+ return 0;
+ return lookup_resource(RESOURCE_PROCSET, arg, version, revision);
+}
+
+resource *resource_manager::read_resource_arg(const char **ptr)
+{
+ while (white_space(**ptr))
+ *ptr += 1;
+ const char *name = *ptr;
+ while (**ptr != '\0' && !white_space(**ptr))
+ *ptr += 1;
+ if (name == *ptr) {
+ error("missing resource type");
+ return 0;
+ }
+ int ri;
+ for (ri = 0; ri < NRESOURCES; ri++)
+ if (strlen(resource_table[ri]) == size_t(*ptr - name)
+ && strncasecmp(resource_table[ri], name, *ptr - name) == 0)
+ break;
+ if (ri >= NRESOURCES) {
+ error("unknown resource type");
+ return 0;
+ }
+ if (ri == RESOURCE_PROCSET)
+ return read_procset_arg(ptr);
+ string arg;
+ if (!read_text_arg(ptr, arg))
+ return 0;
+ return lookup_resource(resource_type(ri), arg);
+}
+
+static const char *matches_comment(string &buf, const char *comment)
+{
+ if ((size_t)buf.length() < strlen(comment) + 3)
+ return 0;
+ if (buf[0] != '%' || buf[1] != '%')
+ return 0;
+ const char *bufp = buf.contents() + 2;
+ for (; *comment; comment++, bufp++)
+ if (*bufp != *comment)
+ return 0;
+ if (comment[-1] == ':')
+ return bufp;
+ if (*bufp == '\0' || white_space(*bufp))
+ return bufp;
+ return 0;
+}
+
+// Return 1 if the line should be copied out.
+
+int resource_manager::do_begin_resource(const char *ptr, int, FILE *,
+ FILE *)
+{
+ resource *r = read_resource_arg(&ptr);
+ if (r)
+ r->flags |= resource::SUPPLIED;
+ return 1;
+}
+
+int resource_manager::do_include_resource(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_resource_arg(&ptr);
+ if (r) {
+ if (r->type == RESOURCE_FONT) {
+ if (rank >= 0)
+ supply_resource(r, rank + 1, outfp);
+ else
+ r->flags |= resource::FONT_NEEDED;
+ }
+ else
+ supply_resource(r, rank, outfp);
+ }
+ return 0;
+}
+
+int resource_manager::do_begin_document(const char *ptr, int, FILE *,
+ FILE *)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ r->flags |= resource::SUPPLIED;
+ return 1;
+}
+
+int resource_manager::do_include_document(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp, 1);
+ return 0;
+}
+
+int resource_manager::do_begin_procset(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_procset_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_procset(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_procset_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_file(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_file(const char *ptr, int rank,
+ FILE *, FILE *outfp)
+{
+ resource *r = read_file_arg(&ptr);
+ if (r)
+ supply_resource(r, rank, outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_font(const char *ptr, int, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_font_arg(&ptr);
+ if (r) {
+ r->flags |= resource::SUPPLIED;
+ if (outfp) {
+ fputs("%%BeginResource: ", outfp);
+ r->print_type_and_name(outfp);
+ putc('\n', outfp);
+ }
+ }
+ return 0;
+}
+
+int resource_manager::do_include_font(const char *ptr, int rank, FILE *,
+ FILE *outfp)
+{
+ resource *r = read_font_arg(&ptr);
+ if (r) {
+ if (rank >= 0)
+ supply_resource(r, rank + 1, outfp);
+ else
+ r->flags |= resource::FONT_NEEDED;
+ }
+ return 0;
+}
+
+int resource_manager::change_to_end_resource(const char *, int, FILE *,
+ FILE *outfp)
+{
+ if (outfp)
+ fputs("%%EndResource\n", outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_preview(const char *, int, FILE *fp,
+ FILE *)
+{
+ string buf;
+ do {
+ if (!ps_get_line(buf, fp)) {
+ error("end of file in preview section");
+ break;
+ }
+ } while (!matches_comment(buf, "EndPreview"));
+ return 0;
+}
+
+int read_one_of(const char **ptr, const char **s, int n)
+{
+ while (white_space(**ptr))
+ *ptr += 1;
+ if (**ptr == '\0')
+ return -1;
+ const char *start = *ptr;
+ do {
+ ++(*ptr);
+ } while (**ptr != '\0' && !white_space(**ptr));
+ for (int i = 0; i < n; i++)
+ if (strlen(s[i]) == size_t(*ptr - start)
+ && memcmp(s[i], start, *ptr - start) == 0)
+ return i;
+ return -1;
+}
+
+void skip_possible_newline(FILE *fp, FILE *outfp)
+{
+ int c = getc(fp);
+ if (c == '\r') {
+ current_lineno++;
+ if (outfp)
+ putc(c, outfp);
+ int cc = getc(fp);
+ if (cc != '\n') {
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else {
+ if (outfp)
+ putc(cc, outfp);
+ }
+ }
+ else if (c == '\n') {
+ current_lineno++;
+ if (outfp)
+ putc(c, outfp);
+ }
+ else if (c != EOF)
+ ungetc(c, fp);
+}
+
+int resource_manager::do_begin_data(const char *ptr, int, FILE *fp,
+ FILE *outfp)
+{
+ while (white_space(*ptr))
+ ptr++;
+ const char *start = ptr;
+ unsigned numberof;
+ if (!read_uint_arg(&ptr, &numberof))
+ return 0;
+ static const char *types[] = { "Binary", "Hex", "ASCII" };
+ const int Binary = 0;
+ int type = 0;
+ static const char *units[] = { "Bytes", "Lines" };
+ const int Bytes = 0;
+ int unit = Bytes;
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr != '\0') {
+ type = read_one_of(&ptr, types, 3);
+ if (type < 0) {
+ error("bad data type");
+ return 0;
+ }
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr != '\0') {
+ unit = read_one_of(&ptr, units, 2);
+ if (unit < 0) {
+ error("expected 'Bytes' or 'Lines'");
+ return 0;
+ }
+ }
+ }
+ if (type != Binary)
+ return 1;
+ if (outfp) {
+ fputs("%%BeginData: ", outfp);
+ fputs(start, outfp);
+ }
+ if (numberof > 0) {
+ unsigned bytecount = 0;
+ unsigned linecount = 0;
+ do {
+ int c = getc(fp);
+ if (c == EOF) {
+ error("end of file within data section");
+ return 0;
+ }
+ if (outfp)
+ putc(c, outfp);
+ bytecount++;
+ if (c == '\r') {
+ int cc = getc(fp);
+ if (cc != '\n') {
+ linecount++;
+ current_lineno++;
+ }
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else if (c == '\n') {
+ linecount++;
+ current_lineno++;
+ }
+ } while ((unit == Bytes ? bytecount : linecount) < numberof);
+ }
+ skip_possible_newline(fp, outfp);
+ string buf;
+ if (!ps_get_line(buf, fp)) {
+ error("missing %%%%EndData line");
+ return 0;
+ }
+ if (!matches_comment(buf, "EndData"))
+ error("bad %%%%EndData line");
+ if (outfp)
+ fputs(buf.contents(), outfp);
+ return 0;
+}
+
+int resource_manager::do_begin_binary(const char *ptr, int, FILE *fp,
+ FILE *outfp)
+{
+ if (!outfp)
+ return 0;
+ unsigned count;
+ if (!read_uint_arg(&ptr, &count))
+ return 0;
+ if (outfp)
+ fprintf(outfp, "%%%%BeginData: %u Binary Bytes\n", count);
+ while (count != 0) {
+ int c = getc(fp);
+ if (c == EOF) {
+ error("end of file within binary section");
+ return 0;
+ }
+ if (outfp)
+ putc(c, outfp);
+ --count;
+ if (c == '\r') {
+ int cc = getc(fp);
+ if (cc != '\n')
+ current_lineno++;
+ if (cc != EOF)
+ ungetc(cc, fp);
+ }
+ else if (c == '\n')
+ current_lineno++;
+ }
+ skip_possible_newline(fp, outfp);
+ string buf;
+ if (!ps_get_line(buf, fp)) {
+ error("missing %%%%EndBinary line");
+ return 0;
+ }
+ if (!matches_comment(buf, "EndBinary")) {
+ error("bad %%%%EndBinary line");
+ if (outfp)
+ fputs(buf.contents(), outfp);
+ }
+ else if (outfp)
+ fputs("%%EndData\n", outfp);
+ return 0;
+}
+
+static unsigned parse_extensions(const char *ptr)
+{
+ unsigned flags = 0;
+ for (;;) {
+ while (white_space(*ptr))
+ ptr++;
+ if (*ptr == '\0')
+ break;
+ const char *name = ptr;
+ do {
+ ++ptr;
+ } while (*ptr != '\0' && !white_space(*ptr));
+ int i;
+ for (i = 0; i < NEXTENSIONS; i++)
+ if (strlen(extension_table[i]) == size_t(ptr - name)
+ && memcmp(extension_table[i], name, ptr - name) == 0) {
+ flags |= (1 << i);
+ break;
+ }
+ if (i >= NEXTENSIONS) {
+ string s(name, ptr - name);
+ s += '\0';
+ error("unknown extension '%1'", s.contents());
+ }
+ }
+ return flags;
+}
+
+// XXX if it has not been surrounded with {Begin,End}Document need to
+// strip out Page: Trailer {Begin,End}Prolog {Begin,End}Setup sections.
+
+// XXX Perhaps the decision whether to use BeginDocument or
+// BeginResource: file should be postponed till we have seen
+// the first line of the file.
+
+void resource_manager::process_file(int rank, FILE *fp,
+ const char *filename, FILE *outfp)
+{
+ // If none of these comments appear in the header section, and we are
+ // just analyzing the file (i.e., outfp is 0), then we can return
+ // immediately.
+ static const char *header_comment_table[] = {
+ "DocumentNeededResources:",
+ "DocumentSuppliedResources:",
+ "DocumentNeededFonts:",
+ "DocumentSuppliedFonts:",
+ "DocumentNeededProcSets:",
+ "DocumentSuppliedProcSets:",
+ "DocumentNeededFiles:",
+ "DocumentSuppliedFiles:",
+ };
+
+ const int NHEADER_COMMENTS = sizeof(header_comment_table)
+ / sizeof(header_comment_table[0]);
+ struct comment_info {
+ const char *name;
+ int (resource_manager::*proc)(const char *, int, FILE *, FILE *);
+ };
+
+ static const comment_info comment_table[] = {
+ { "BeginResource:", &resource_manager::do_begin_resource },
+ { "IncludeResource:", &resource_manager::do_include_resource },
+ { "BeginDocument:", &resource_manager::do_begin_document },
+ { "IncludeDocument:", &resource_manager::do_include_document },
+ { "BeginProcSet:", &resource_manager::do_begin_procset },
+ { "IncludeProcSet:", &resource_manager::do_include_procset },
+ { "BeginFont:", &resource_manager::do_begin_font },
+ { "IncludeFont:", &resource_manager::do_include_font },
+ { "BeginFile:", &resource_manager::do_begin_file },
+ { "IncludeFile:", &resource_manager::do_include_file },
+ { "EndProcSet", &resource_manager::change_to_end_resource },
+ { "EndFont", &resource_manager::change_to_end_resource },
+ { "EndFile", &resource_manager::change_to_end_resource },
+ { "BeginPreview:", &resource_manager::do_begin_preview },
+ { "BeginData:", &resource_manager::do_begin_data },
+ { "BeginBinary:", &resource_manager::do_begin_binary },
+ };
+
+ const int NCOMMENTS = sizeof(comment_table)/sizeof(comment_table[0]);
+ string buf;
+ int saved_lineno = current_lineno;
+ const char *saved_filename = current_filename;
+ current_filename = filename;
+ current_lineno = 0;
+ if (!ps_get_line(buf, fp)) {
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+ return;
+ }
+ if ((size_t)buf.length() < sizeof(PS_MAGIC)
+ || memcmp(buf.contents(), PS_MAGIC, sizeof(PS_MAGIC) - 1) != 0) {
+ if (outfp) {
+ do {
+ if (!(broken_flags & STRIP_PERCENT_BANG)
+ || buf[0] != '%' || buf[1] != '!')
+ fputs(buf.contents(), outfp);
+ } while (ps_get_line(buf, fp));
+ }
+ }
+ else {
+ if (!(broken_flags & STRIP_PERCENT_BANG) && outfp)
+ fputs(buf.contents(), outfp);
+ int in_header = 1;
+ int interesting = 0;
+ int had_extensions_comment = 0;
+ int had_language_level_comment = 0;
+ for (;;) {
+ if (!ps_get_line(buf, fp))
+ break;
+ int copy_this_line = 1;
+ if (buf[0] == '%') {
+ if (buf[1] == '%') {
+ const char *ptr;
+ int i;
+ for (i = 0; i < NCOMMENTS; i++)
+ if ((ptr = matches_comment(buf, comment_table[i].name))) {
+ copy_this_line
+ = (this->*(comment_table[i].proc))(ptr, rank, fp, outfp);
+ break;
+ }
+ if (i >= NCOMMENTS && in_header) {
+ if ((ptr = matches_comment(buf, "EndComments")))
+ in_header = 0;
+ else if (!had_extensions_comment
+ && (ptr = matches_comment(buf, "Extensions:"))) {
+ extensions |= parse_extensions(ptr);
+ // XXX handle possibility that next line is %%+
+ had_extensions_comment = 1;
+ }
+ else if (!had_language_level_comment
+ && (ptr = matches_comment(buf, "LanguageLevel:"))) {
+ unsigned ll;
+ if (read_uint_arg(&ptr, &ll) && ll > language_level)
+ language_level = ll;
+ had_language_level_comment = 1;
+ }
+ else {
+ for (i = 0; i < NHEADER_COMMENTS; i++)
+ if (matches_comment(buf, header_comment_table[i])) {
+ interesting = 1;
+ break;
+ }
+ }
+ }
+ if ((broken_flags & STRIP_STRUCTURE_COMMENTS)
+ && (matches_comment(buf, "EndProlog")
+ || matches_comment(buf, "Page:")
+ || matches_comment(buf, "Trailer")))
+ copy_this_line = 0;
+ }
+ else if (buf[1] == '!') {
+ if (broken_flags & STRIP_PERCENT_BANG)
+ copy_this_line = 0;
+ }
+ }
+ else
+ in_header = 0;
+ if (!outfp && !in_header && !interesting)
+ break;
+ if (copy_this_line && outfp)
+ fputs(buf.contents(), outfp);
+ }
+ }
+ current_filename = saved_filename;
+ current_lineno = saved_lineno;
+}
+
+void resource_manager::read_download_file()
+{
+ char *path = 0 /* nullptr */;
+ FILE *fp = font::open_file("download", &path);
+ if (0 /* nullptr */ == fp)
+ fatal("failed to open 'download' file: %1", strerror(errno));
+ char buf[512];
+ int lineno = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ lineno++;
+ char *p = strtok(buf, " \t\r\n");
+ if (p == 0 /* nullptr */ || *p == '#')
+ continue;
+ char *q = strtok(0 /* nullptr */, " \t\r\n");
+ if (!q)
+ fatal_with_file_and_line(path, lineno, "file name missing for"
+ " font '%1'", p);
+ lookup_font(p)->filename = strsave(q);
+ }
+ free(path);
+ fclose(fp);
+}
+
+// XXX Can we share some code with ps_output::put_string()?
+
+static void print_ps_string(const string &s, FILE *outfp)
+{
+ int len = s.length();
+ const char *str = s.contents();
+ int funny = 0;
+ if (str[0] == '(')
+ funny = 1;
+ else {
+ for (int i = 0; i < len; i++)
+ if (str[i] <= 040 || str[i] > 0176) {
+ funny = 1;
+ break;
+ }
+ }
+ if (!funny) {
+ put_string(s, outfp);
+ return;
+ }
+ int level = 0;
+ int i;
+ for (i = 0; i < len; i++)
+ if (str[i] == '(')
+ level++;
+ else if (str[i] == ')' && --level < 0)
+ break;
+ putc('(', outfp);
+ for (i = 0; i < len; i++)
+ switch (str[i]) {
+ case '(':
+ case ')':
+ if (level != 0)
+ putc('\\', outfp);
+ putc(str[i], outfp);
+ break;
+ case '\\':
+ fputs("\\\\", outfp);
+ break;
+ case '\n':
+ fputs("\\n", outfp);
+ break;
+ case '\r':
+ fputs("\\r", outfp);
+ break;
+ case '\t':
+ fputs("\\t", outfp);
+ break;
+ case '\b':
+ fputs("\\b", outfp);
+ break;
+ case '\f':
+ fputs("\\f", outfp);
+ break;
+ default:
+ if (str[i] < 040 || str[i] > 0176)
+ fprintf(outfp, "\\%03o", str[i] & 0377);
+ else
+ putc(str[i], outfp);
+ break;
+ }
+ putc(')', outfp);
+}
+
+void resource_manager::print_extensions_comment(FILE *outfp)
+{
+ if (extensions) {
+ fputs("%%Extensions:", outfp);
+ for (int i = 0; i < NEXTENSIONS; i++)
+ if (extensions & (1 << i)) {
+ putc(' ', outfp);
+ fputs(extension_table[i], outfp);
+ }
+ putc('\n', outfp);
+ }
+}
+
+void resource_manager::print_language_level_comment(FILE *outfp)
+{
+ if (language_level)
+ fprintf(outfp, "%%%%LanguageLevel: %u\n", language_level);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/grotty/TODO b/src/devices/grotty/TODO
new file mode 100644
index 0000000..3f23dc3
--- /dev/null
+++ b/src/devices/grotty/TODO
@@ -0,0 +1,3 @@
+Document font and device description file usage of grotty.
+
+With -h avoid using a tab when a single space will do.
diff --git a/src/devices/grotty/grotty.1.man b/src/devices/grotty/grotty.1.man
new file mode 100644
index 0000000..3dcafae
--- /dev/null
+++ b/src/devices/grotty/grotty.1.man
@@ -0,0 +1,810 @@
+.TH grotty @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+grotty \-
+.I groff
+output driver for typewriter-like (terminal) devices
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_grotty_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY grotty
+.RB [ \-dfho ]
+.RB [ \-i | \-r ]
+.RB [ \-F\~\c
+.IR dir ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY "grotty \-c"
+.RB [ \-bBdfhouU ]
+.RB [ \-F\~\c
+.IR dir ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY grotty
+.B \-\-help
+.YS
+.
+.
+.SY grotty
+.B \-v
+.
+.SY grotty
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+The GNU
+.I roff
+TTY
+(\[lq]Teletype\[rq])
+output driver translates the output of
+.MR @g@troff @MAN1EXT@
+into a form suitable for typewriter-like devices,
+including terminal emulators.
+.
+Normally,
+.I grotty
+is invoked by
+.MR groff @MAN1EXT@
+when the latter is given one of the
+.RB \[lq] \-T\~ascii \[rq],
+.RB \[lq] \-T\~latin1 \[rq],
+.BR \-Tlatin1 ,
+or
+.RB \[lq] \-T\~utf8 \[rq]
+options on systems using ISO character encoding standards,
+or with
+.RB \[lq] \-T\~cp1047 \[rq]
+or
+.RB \[lq] \-T\~utf8 \[rq]
+on EBCDIC-based hosts.
+.
+(In this installation,
+.B @DEVICE@
+is the default output device.)
+.
+Use
+.IR groff 's
+.B \-P
+option to pass any options shown above to
+.IR grotty .
+.
+If no
+.I file
+arguments are given,
+or if
+.I file
+is \[lq]\-\[rq],
+.I grotty
+reads the standard input stream.
+.
+Output is written to the standard output stream.
+.
+.
+.P
+By default,
+.I grotty
+emits SGR escape sequences
+(from ISO\~6429,
+popularly called \[lq]ANSI escapes\[rq])
+to change text attributes
+(bold,
+italic,
+underline,
+reverse video
+.\" ECMA-48, 2nd edition (1979) calls it "negative image".
+[\[lq]negative image\[rq]]
+and colors).
+.
+Devices supporting the appropriate sequences can view
+.I roff
+documents using eight different background and foreground colors.
+.
+Following ISO\~6429,
+the following colors are defined in
+.IR tty.tmac :
+black,
+white,
+red,
+green,
+blue,
+yellow,
+magenta,
+and cyan.
+.
+Unrecognized colors are mapped to the default color,
+which is dependent on the settings of the terminal.
+.
+OSC\~8 hyperlinks are produced for these devices.
+.
+.
+.P
+In keeping with long-standing practice and the rarity of terminals
+(and emulators)
+that support oblique or italic fonts,
+italicized text is represented with underlining by default\[em]but see
+the
+.B \-i
+option below.
+.
+.
+.\" ====================================================================
+.SS "SGR and OSC support in pagers"
+.\" ====================================================================
+.
+When paging
+.IR grotty 's
+output with
+.MR less 1 ,
+the latter program must be instructed to pass SGR and OSC sequences
+through to the device;
+its
+.B \-R
+option is one way to achieve this
+.RI ( less
+version 566 or later is required for OSC\~8 support).
+.
+Consequently,
+programs like
+.MR man 1
+that page
+.I roff
+documents with
+.I less
+must call it with an appropriate option.
+.
+.
+.\" ====================================================================
+.SS "Legacy output format"
+.\" ====================================================================
+.
+The
+.B \-c
+option tells
+.I grotty
+to use an output format compatible with paper terminals,
+like the Teletype machines for which
+.I roff
+and
+.I nroff
+were first developed but which are no longer in wide use.
+.
+SGR escape sequences are not emitted;
+bold,
+italic,
+and underlining character attributes are thus not manipulated.
+.
+Instead,
+.I grotty
+overstrikes,
+representing a bold character
+.I c
+with the sequence
+.RI \[lq] c\~\c
+BACKSPACE\~\c
+.IR c \[rq],
+an italic character
+.I c
+with the sequence
+.RB \[lq] _\~\c
+BACKSPACE\~\c
+.IR c \[rq],
+and bold italics with
+.RB \[lq] _\~\c
+BACKSPACE\~\c
+.I c
+BACKSPACE\~\c
+.IR c \[rq].
+.
+This rendering is inherently ambiguous when the character
+.I c
+is itself the underscore.
+.
+.
+.P
+The legacy output format can be rendered on a video terminal
+(or emulator)
+by piping
+.IR grotty 's
+output through
+.MR ul 1 ,
+.\" from bsdmainutils 11.1.2+b1 (on Debian Buster)
+which may render bold italics as reverse video.
+.
+.\" 'more' from util-linux 2.33.1 (on Debian Buster) neither renders
+.\" double-struck characters as bold nor supports -b, but does render
+.\" SGR sequences (including color) with no flags required.
+Some implementations of
+.MR more 1
+are also able to display these sequences;
+you may wish to experiment with that command's
+.B \-b
+option.
+.
+.\" Version 487 of...
+.I less
+renders legacy bold and italics without requiring options.
+.
+In contrast to the terminal output drivers of some other
+.I roff
+implementations,
+.I grotty
+never outputs reverse line feeds.
+.
+There is therefore no need to filter its output through
+.MR col 1 .
+.
+.
+.\" ====================================================================
+.SS "Device control commands"
+.\" ====================================================================
+.
+.I grotty
+understands one device control function produced by the
+.I roff
+.B \[rs]X
+escape sequence in a document.
+.
+.
+.TP
+.BR "\[rs]X\[aq]tty: link " [\c
+.IR uri \~[ key\c
+.BI = value\c
+] \|.\|.\|.\|]\c
+.B \[aq]
+.
+Embed a hyperlink using the OSC 8 terminal escape sequence.
+.
+Specifying
+.I uri
+starts hyperlinked text,
+and omitting it ends the hyperlink.
+.
+When
+.I uri
+is present,
+any number of additional key/value pairs can be specified;
+their interpretation is the responsibility of the pager or terminal.
+.
+Spaces or tabs cannot appear literally in
+.IR uri ,
+.IR key ,
+or
+.IR value ;
+they must be represented in an alternate form.
+.
+.
+.\" ====================================================================
+.SS "Device description files"
+.\" ====================================================================
+.
+If the
+.I DESC
+file for the character encoding contains the
+.RB \[lq] unicode \[rq]
+directive,
+.I grotty
+emits Unicode characters in UTF-8 encoding.
+.
+Otherwise,
+it emits characters in a single-byte encoding depending on the data in
+the font description files.
+.
+See
+.MR groff_font @MAN5EXT@ .
+.
+.
+.P
+A font description file may contain a directive
+.RB \[lq] internalname\~\c
+.IR n \[rq]
+where
+.I n
+is a decimal integer.
+.
+If the 01 bit in
+.I n
+is set,
+then the font is treated as an italic font;
+if the 02 bit is set,
+then it is treated as a bold font.
+.
+.\" The following seems to say nothing that is not true of font
+.\" description files in general; if so, it belongs in groff_font(5).
+.\"The code field in the font description field gives the code which is
+.\"used to output the character.
+.\".
+.\"This code can also be used in the
+.\".I groff
+.\".B \[rs]N
+.\"escape sequence in a document.
+.
+.
+.\" ====================================================================
+.SS Typefaces
+.\" ====================================================================
+.
+.I grotty
+supports the standard four styles:
+.B R
+(roman),
+.B I
+.RI ( italic ),
+.B B
+.RB ( bold ),
+and
+.B BI
+(\f[BI]bold-italic\f[]).
+.
+Because the output driver operates in
+.I nroff
+mode,
+attempts to set or change the font family or type size are ignored.
+.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-\-help
+displays a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-b
+Suppress the use of overstriking for bold characters in legacy output
+format.
+.
+.
+.TP
+.B \-B
+Use only overstriking for bold-italic characters in legacy output
+format.
+.
+.
+.TP
+.B \-c
+Use
+.IR grotty 's
+legacy output format
+(see subsection \[lq]Legacy output format\[rq] above).
+.
+SGR and OSC escape sequences are not emitted.
+.
+.
+.TP
+.B \-d
+Ignore all
+.B \[rs]D
+drawing escape sequences in the input.
+.
+By default,
+.I grotty
+renders
+.BR \[rs]D\[aq]l \|.\|.\|.\& \[aq]
+escape sequences that have at least one zero argument
+(and so are either horizontal or vertical)
+using Unicode box drawing characters
+(for the
+.B utf8
+device)
+or the
+.BR \- ,
+.BR | ,
+and
+.B +
+characters
+(for all other devices).
+.
+.I grotty
+handles
+.BR \[rs]D\[aq]p \|.\|.\|.\& \[aq]
+escape sequences that consist entirely of horizontal and vertical
+lines similarly.
+.
+.
+.TP
+.B \-f
+Emit a form feed at the end of each page having no output on its last
+line.
+.
+.
+.TP
+.BI \-F\~ dir
+Prepend directory
+.RI dir /dev name
+to the search path for font and device description files;
+.I name
+describes the output device's character encoding,
+one of
+.BR ascii ,
+.BR latin1 ,
+.BR utf8 ,
+or
+.BR cp1047 .
+.
+.
+.TP
+.B \-h
+Use literal horizontal tab characters in the output.
+.
+Tabs are assumed to be set every 8 columns.
+.
+.
+.TP
+.B \-i
+Render oblique-styled fonts
+.RB ( I
+and
+.BR BI )
+with the SGR attribute for italic text
+rather than underlined text.
+.
+Many terminals don't support this attribute;
+however,
+.MR xterm 1 ,
+since patch\~#314 (2014-12-28),
+does.
+.
+Ignored if
+.B \-c
+is also specified.
+.
+.
+.TP
+.B \-o
+Suppress overstriking
+(other than for bold and/or underlined characters when the legacy output
+format is in use).
+.
+.
+.TP
+.B \-r
+Render oblique-styled fonts
+.RB ( I
+and
+.BR BI )
+with the SGR attribute for reverse video text
+rather than underlined text.
+.
+Ignored if
+.B \-c
+or
+.B \-i
+is also specified.
+.
+.
+.TP
+.B \-u
+Suppress the use of underlining for italic characters in legacy output
+format.
+.
+.
+.TP
+.B \-U
+Use only underlining for bold-italic characters in legacy output format.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROFF_NO_SGR
+If set,
+.IR grotty 's
+legacy output format is used just as if the
+.B \-c
+option were specified;
+see subsection \[lq]Legacy output format\[rq] above.
+.
+.
+.br
+.ne 3v \" Keep section heading and paragraph tag together.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @FONTDIR@/\:\%devascii/\:DESC
+describes the
+.B ascii
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devascii/ F
+describes the font known
+.RI as\~ F
+on device
+.BR ascii .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devcp1047/\:DESC
+describes the
+.B cp1047
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devcp1047/ F
+describes the font known
+.RI as\~ F
+on device
+.BR cp1047 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devlatin1/\:DESC
+describes the
+.B latin1
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devlatin1/ F
+describes the font known
+.RI as\~ F
+on device
+.BR latin1 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devutf8/\:DESC
+describes the
+.B utf8
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devutf8/ F
+describes the font known
+.RI as\~ F
+on device
+.BR utf8 .
+.
+.
+.TP
+.I @MACRODIR@/\:tty\:.tmac
+defines macros for use with the
+.BR ascii ,
+.BR cp1047 ,
+.BR latin1 ,
+and
+.B utf8
+output devices.
+.
+It is automatically loaded by
+.I troffrc
+when any of those output devices is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:tty\-char\:.tmac
+defines fallback characters for use with
+.I grotty.
+.
+See
+.MR nroff @MAN1EXT@ .
+.
+.
+.\" XXX: The following no longer seems to be true; an inspection of the
+.\" font/*/dev*.am files suggests no evidence of it, at any rate.
+.\".P
+.\"Note that on EBCDIC hosts,
+.\"only files for the
+.\".B cp1047
+.\"device are installed.
+.
+.
+.\" ====================================================================
+.SH Limitations
+.\" ====================================================================
+.
+.I grotty
+is intended only for simple documents.
+.
+.
+.IP \[bu] 2n
+There is no support for fractional horizontal or vertical motions.
+.
+.
+.IP \[bu]
+.I roff
+.B \[rs]D
+escape sequences producing anything other than horizontal and vertical
+lines are not supported.
+.
+.
+.IP \[bu]
+Characters above the first line
+(that is,
+with a vertical drawing position of\~0)
+cannot be rendered.
+.
+.
+.IP \[bu]
+Color handling differs from other output drivers.
+.
+The
+.I groff
+requests and escape sequences that set the stroke and fill colors
+instead set the foreground and background character cell colors,
+respectively.
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+The following
+.I groff
+document exercises several features for which output device support
+varies:
+(1)\~bold style;
+(2)\~italic (underline) style;
+(3)\~bold-italic style;
+(4)\~character composition by overstriking (\[lq]co\[:o]perate\[rq]);
+(5)\~foreground color;
+(6)\~background color;
+and
+(7)\~horizontal and vertical line-drawing.
+.
+.
+.P
+.RS
+.EX
+You might see \ef[B]bold\ef[] and \ef[I]italic\ef[].
+Some people see \ef[BI]both\ef[].
+If the output device does (not) co\ez\e[ad]operate,
+you might see \em[red]red\em[].
+Black on cyan can have a \eM[cyan]\em[black]prominent\em[]\eM[]
+\eD\[aq]l 1i 0\[aq]\eD\[aq]l 0 2i\[aq]\eD\[aq]l 1i 0\[aq] look.
+\&.\e" If in nroff mode, end page now.
+\&.if n .pl \en[nl]u
+.EE
+.RE
+.
+.
+.P
+Given the foregoing input,
+compare and contrast the output of the following.
+.
+.
+.P
+.RS
+.EX
+$ \c
+.B groff \-T ascii \c
+.I file
+$ \c
+.B groff \-T utf8 \-P \-i \c
+.I file
+$ \c
+.B groff \-T utf8 \-P \-c \c
+.I file \c
+.B | ul
+.EE
+.RE
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+\[lq]Control Functions for Coded Character Sets\[rq]
+(ECMA-48)
+5th\~edition,
+Ecma International,
+June\~1991.
+.
+A gratis version of ISO\~6429,
+this document includes a normative description of SGR escape sequences.
+.
+Available at
+.UR http://\:www\:.ecma\-international\:.org/\:publications/\:files/\:\
+ECMA\-ST/\:Ecma\-048\:.pdf
+.UE .
+.
+.
+.P
+.UR https://\:gist\:.github\:.com/\:egmontkob/\:\
+eb114294efbcd5ad\:b1944c9f3cb5feda
+\[lq]Hyperlinks in Terminal Emulators\[rq]
+.UE ,
+Egmont Koblinger.
+.
+.
+.P
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@ ,
+.MR groff_font @MAN5EXT@ ,
+.MR groff_char @MAN7EXT@ ,
+.MR ul 1 ,
+.MR more 1 ,
+.MR less 1 ,
+.MR man 1
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_grotty_1_man_C]
+.do rr *groff_grotty_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/grotty/grotty.am b/src/devices/grotty/grotty.am
new file mode 100644
index 0000000..14921c5
--- /dev/null
+++ b/src/devices/grotty/grotty.am
@@ -0,0 +1,39 @@
+# Copyright (C) 2014-2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += grotty
+grotty_SOURCES = src/devices/grotty/tty.cpp
+grotty_LDADD = $(LIBM) \
+ libdriver.a \
+ libgroff.a \
+ lib/libgnu.a
+man1_MANS += src/devices/grotty/grotty.1
+EXTRA_DIST += \
+ src/devices/grotty/grotty.1.man \
+ src/devices/grotty/TODO
+
+grotty_TESTS = \
+ src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh \
+ src/devices/grotty/tests/osc8_works.sh
+TESTS += $(grotty_TESTS)
+EXTRA_DIST += $(grotty_TESTS)
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh b/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh
new file mode 100755
index 0000000..92552d2
--- /dev/null
+++ b/src/devices/grotty/tests/basic_latin_glyphs_map_correctly.sh
@@ -0,0 +1,205 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+grotty="${abs_top_builddir:-.}/grotty"
+
+fail=
+
+wail () {
+ printf "FAILED " >&2
+ fail=YES
+}
+
+# Ensure that characters are mapped to glyphs normatively.
+
+input='x T @DEVICE@
+x res 240 24 40
+x init
+p1
+x font 1 R
+f1
+s10
+V40
+H0
+md
+DFd
+t!#$%&()*+,./0123456789:;<=>?@
+n40 0
+V80
+H0
+tABCDEFGHIJKLMNOPQRSTUVWXYZ[]_
+n40 0
+V120
+H0
+tabcdefghijklmnopqrstuvwxyz{|}
+n40 0
+V160
+H0
+tneutral
+wh24
+tdouble
+wh24
+tquote:
+wh24
+Cdq
+h24
+n40 0
+V200
+H0
+tclosing
+wh24
+tsingle
+wh24
+tquote:
+wh24
+t'"'"'
+n40 0
+V240
+H0
+thyphen:
+wh24
+t-
+n40 0
+V280
+H0
+tbackslash:
+wh24
+Crs
+h24
+n40 0
+V320
+H0
+tmodifier
+wh24
+tcircumflex:
+wh24
+t^
+n40 0
+V360
+H0
+topening
+wh24
+tsingle
+wh24
+tquote:
+wh24
+t`
+n40 0
+V400
+H0
+tmodifier
+wh24
+ttilde:
+wh24
+t~
+n40 0
+x trailer
+V2640
+x stop'
+
+# TODO: Test cp1047 when we have access to a host environment using it.
+
+for D in ascii latin1 utf8
+do
+ if [ "$D" = "utf8" ]
+ then
+ # We can't test UTF-8 if the environment doesn't support it.
+ if [ "$(locale charmap)" != UTF-8 ]
+ then
+ # If we've already seen a failure case, report it.
+ if [ -n "$fail" ]
+ then
+ exit 1 # fail
+ else
+ exit 77 # skip
+ fi
+ fi
+ fi
+
+ printf 'checking "%s" output device...' $D >&2
+ output=$(echo "$input" | sed s/@DEVICE@/$D/ \
+ | "$grotty" -F font -F build/font)
+ printf 'group1 ' >&2
+ echo "$output" | grep -Fqx '!#$%&()*+,./0123456789:;<=>?@' || wail
+ printf 'group2 ' >&2
+ echo "$output" | grep -Fqx 'ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_' || wail
+ printf 'group3 ' >&2
+ echo "$output" | grep -Fqx 'abcdefghijklmnopqrstuvwxyz{|}' || wail
+ printf '" ' >&2
+ echo "$output" | grep -Fqx 'neutral double quote: "' || wail
+ printf '\\ ' >&2
+ echo "$output" | grep -Fqx 'backslash: \' || wail
+ case $D in
+ (utf8)
+# Expected:
+#0000000 ! # $ % & ( ) * + , . / 0 1 2 3
+#0000020 4 5 6 7 8 9 : ; < = > ? @ \n A B
+#0000040 C D E F G H I J K L M N O P Q R
+#0000060 S T U V W X Y Z [ ] _ \n a b c d
+#0000100 e f g h i j k l m n o p q r s t
+#0000120 u v w x y z { | } \n n e u t r a
+#0000140 l d o u b l e q u o t e :
+#0000160 " \n c l o s i n g s i n g l e
+#0000200 q u o t e : 342 200 231 \n h y p h
+#0000220 e n : 342 200 220 \n b a c k s l a s
+#0000240 h : \ \n m o d i f i e r c i
+#0000260 r c u m f l e x : 313 206 \n o p e
+#0000300 n i n g s i n g l e q u o t
+#0000320 e : 342 200 230 \n m o d i f i e r
+#0000340 t i l d e : 313 234 \n
+#0000352
+ output_od=$(echo "$output" | LC_ALL=C od -t c)
+ printf "' " >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000200 +q +u +o +t +e +: +342 +200 +231' \
+ || wail
+ printf '` ' >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000320 +e +: +342 +200 +230' || wail
+ printf "%s " '-' >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000220 +e +n +: +342 +200 +220' || wail
+ printf '^ ' >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000260 +r +c +u +m +f +l +e +x +: +313 +206' \
+ || wail
+ printf "~ " >&2
+ printf '%s\n' "$output_od" \
+ | grep -Eq '0000340 +t +i +l +d +e +: +313 +234' || wail
+ ;;
+ (*)
+ printf '` ' >&2
+ echo "$output" | grep -Fqx 'opening single quote: `' || wail
+ printf "' " >&2
+ echo "$output" | grep -Fqx "closing single quote: '" || wail
+ printf "%s " '-' >&2
+ echo "$output" | grep -Fqx "hyphen: -" || wail
+ printf '^ ' >&2
+ echo "$output" | grep -Fqx "modifier circumflex: ^" || wail
+ printf "~ " >&2
+ echo "$output" | grep -Fqx "modifier tilde: ~" || wail
+ ;;
+ esac
+ echo >&2
+done
+
+test -z "$fail"
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/devices/grotty/tests/osc8_works.sh b/src/devices/grotty/tests/osc8_works.sh
new file mode 100755
index 0000000..db6480d
--- /dev/null
+++ b/src/devices/grotty/tests/osc8_works.sh
@@ -0,0 +1,119 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+set -e
+
+grotty="${abs_top_builddir:-.}/grotty"
+
+input="x T utf8
+x res 240 24 40
+x init
+p1
+x font 1 R
+f1
+s10
+V40
+H0
+md
+DFd
+tA
+n40 0
+x X tty: link
+x X tty: link h
+x X tty: link http://example.com/1
+x X tty: link
+x X tty: link http://example.com/2
+tB
+x X tty: link
+x X tty: link mailto:g.branden.robinson@gmail.com
+tBranden
+x X tty: link
+x trailer
+V2640
+x stop"
+
+# We expect diagnostics from the first few "x X tty: link" lines. The
+# first should complain about a link ending without having been started.
+# The second is bogus ("h") but it's not grotty's job to validate the
+# structure of a URI. The third should draw complaint because we didn't
+# end the (bogus) URI that we started with the second.
+
+# The remaining input is well-formed. The URI ending in "1" is
+# effectively hidden because no character cells are drawn while it is
+# active.
+echo "expect two diagnostic messages regarding ill-formed links" >&2
+output=$(echo "$input" | "$grotty" -F font -F build/font | od -t c)
+
+# Expected:
+#0000000 A 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h 033 \
+#0000020 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t t p
+#0000040 : / / e x a m p l e . c o m / 1
+#0000060 033 \ 033 ] 8 ; ; 033 \ 033 ] 8 ; ; h t
+#0000100 t p : / / e x a m p l e . c o m
+#0000120 / 2 033 \ B 033 ] 8 ; ; 033 \ 033 ] 8 ;
+#0000140 ; m a i l t o : g . b r a n d e
+#0000160 n . r o b i n s o n @ g m a i l
+#0000200 . c o m 033 \ B r a n d e n 033 ] 8
+#0000220 ; ; 033 \ \n \n \n \n \n \n \n \n \n \n \n \n
+#0000240 \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
+#*
+#0000320 \n \n \n \n \n \n
+#0000326
+
+echo "testing for URI that corresponds to no character cells" >&2
+echo "$output" | grep -Eq 'A 033 +] +8 +; +; +033 +\\'
+
+echo "testing http URI (1)" >&2
+echo "$output" | grep -Eq '0000020 +.*033 +] +8 +; +; +h + t +t +p'
+
+echo "testing http URI (2)" >&2
+echo "$output" | grep -Eq '0000040 +: +/ +/ +e +x +a +m +p +l +e +\. +c'
+
+echo "testing http URI (3)" >&2
+echo "$output" | grep -Eq '0000040.* +o +m +/ +1'
+
+echo "testing http URI (4)" >&2
+echo "$output" | grep -Eq '0000060 +033 +\\'
+
+echo "testing mailto URI (1)" >&2
+echo "$output" | grep -Eq '0000120 +.* +033 +] +8 +;$'
+
+echo "testing mailto URI (2)" >&2
+echo "$output" | grep -Eq '0000140 +; +m +a +i +l +t +o +: +g +\. +b'
+
+echo "testing mailto URI (3)" >&2
+echo "$output" | grep -Eq '0000140.* +r +a +n +d +e$'
+
+echo "testing mailto URI (4)" >&2
+echo "$output" | grep -Eq '0000160 +n +\. +r +o +b +i +n +s +o +n +@'
+
+echo "testing mailto URI (5)" >&2
+echo "$output" | grep -Eq '0000160.* +g +m +a +i +l$'
+
+echo "testing mailto URI (6)" >&2
+echo "$output" | grep -Eq '0000200 +\. +c +o +m +033 +\\ +B +r +a +n +d'
+
+echo "testing mailto URI (7)" >&2
+echo "$output" | grep -Eq '0000200.* +e +n +033 +] +8$'
+
+echo "testing mailto URI (8)" >&2
+echo "$output" | grep -Eq '0000220 +; +; +033 +\\'
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/devices/grotty/tty.cpp b/src/devices/grotty/tty.cpp
new file mode 100644
index 0000000..45926f6
--- /dev/null
+++ b/src/devices/grotty/tty.cpp
@@ -0,0 +1,1043 @@
+/* Copyright (C) 1989-2021 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.com)
+ OSC 8 support by G. Branden Robinson
+
+This file is part of groff.
+
+groff is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "driver.h"
+#include "device.h"
+#include "ptable.h"
+
+typedef signed char schar;
+
+declare_ptable(schar)
+implement_ptable(schar)
+
+extern "C" const char *Version_string;
+
+#define putstring(s) fputs(s, stdout)
+
+#ifndef SHRT_MIN
+#define SHRT_MIN (-32768)
+#endif
+
+#ifndef SHRT_MAX
+#define SHRT_MAX 32767
+#endif
+
+#define TAB_WIDTH 8
+
+// A character of the output device fits in a 32-bit word.
+typedef unsigned int output_character;
+
+static bool want_horizontal_tabs = false;
+static bool want_form_feeds = false;
+static bool want_emboldening_by_overstriking = true;
+static bool do_bold;
+static bool want_italics_by_underlining = true;
+static bool do_underline;
+static bool want_glyph_composition_by_overstriking = true;
+static bool allow_drawing_commands = true;
+static bool want_sgr_italics = false;
+static bool do_sgr_italics;
+static bool want_reverse_video_for_italics = false;
+static bool do_reverse_video;
+static bool use_overstriking_drawing_scheme = false;
+
+static void update_options();
+static void usage(FILE *stream);
+
+static int hline_char = '-';
+static int vline_char = '|';
+
+enum {
+ UNDERLINE_MODE = 0x01,
+ BOLD_MODE = 0x02,
+ VDRAW_MODE = 0x04,
+ HDRAW_MODE = 0x08,
+ CU_MODE = 0x10,
+ COLOR_CHANGE = 0x20,
+ START_LINE = 0x40,
+ END_LINE = 0x80
+};
+
+// Mode to use for bold-underlining.
+static unsigned char bold_underline_mode_option = BOLD_MODE|UNDERLINE_MODE;
+static unsigned char bold_underline_mode;
+
+#ifndef IS_EBCDIC_HOST
+#define CSI "\033["
+#define OSC8 "\033]8"
+#define ST "\033\\"
+#else
+#define CSI "\047["
+#define OSC8 "\047]8"
+#define ST "\047\\"
+#endif
+
+// SGR handling (ISO 6429)
+#define SGR_BOLD CSI "1m"
+#define SGR_NO_BOLD CSI "22m"
+#define SGR_ITALIC CSI "3m"
+#define SGR_NO_ITALIC CSI "23m"
+#define SGR_UNDERLINE CSI "4m"
+#define SGR_NO_UNDERLINE CSI "24m"
+#define SGR_REVERSE CSI "7m"
+#define SGR_NO_REVERSE CSI "27m"
+// many terminals can't handle 'CSI 39 m' and 'CSI 49 m' to reset
+// the foreground and background color, respectively; we thus use
+// 'CSI 0 m' exclusively
+#define SGR_DEFAULT CSI "0m"
+
+#define DEFAULT_COLOR_IDX -1
+
+class tty_font : public font {
+ tty_font(const char *);
+ unsigned char mode;
+public:
+ ~tty_font();
+ unsigned char get_mode() { return mode; }
+#if 0
+ void handle_x_command(int argc, const char **argv);
+#endif
+ static tty_font *load_tty_font(const char *);
+};
+
+tty_font *tty_font::load_tty_font(const char *s)
+{
+ tty_font *f = new tty_font(s);
+ if (!f->load()) {
+ delete f;
+ return 0;
+ }
+ const char *num = f->get_internal_name();
+ long n;
+ if (num != 0 && (n = strtol(num, 0, 0)) != 0)
+ f->mode = (unsigned char)(n & (BOLD_MODE|UNDERLINE_MODE));
+ if (!do_underline)
+ f->mode &= ~UNDERLINE_MODE;
+ if (!do_bold)
+ f->mode &= ~BOLD_MODE;
+ if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
+ f->mode = (unsigned char)((f->mode & ~(BOLD_MODE|UNDERLINE_MODE))
+ | bold_underline_mode);
+ return f;
+}
+
+tty_font::tty_font(const char *nm)
+: font(nm), mode(0)
+{
+}
+
+tty_font::~tty_font()
+{
+}
+
+#if 0
+void tty_font::handle_x_command(int argc, const char **argv)
+{
+ if (argc >= 1 && strcmp(argv[0], "bold") == 0)
+ mode |= BOLD_MODE;
+ else if (argc >= 1 && strcmp(argv[0], "underline") == 0)
+ mode |= UNDERLINE_MODE;
+}
+#endif
+
+class tty_glyph {
+public:
+ tty_glyph *next;
+ int w;
+ int hpos;
+ unsigned int code;
+ unsigned char mode;
+ schar back_color_idx;
+ schar fore_color_idx;
+ inline int draw_mode() { return mode & (VDRAW_MODE|HDRAW_MODE); }
+ inline int order() {
+ return mode & (VDRAW_MODE|HDRAW_MODE|CU_MODE|COLOR_CHANGE); }
+};
+
+
+class tty_printer : public printer {
+ tty_glyph **lines;
+ int nlines;
+ int cached_v;
+ int cached_vpos;
+ schar curr_fore_idx;
+ schar curr_back_idx;
+ bool is_underlining;
+ bool is_boldfacing;
+ bool is_continuously_underlining;
+ PTABLE(schar) tty_colors;
+ void make_underline(int);
+ void make_bold(output_character, int);
+ schar color_to_idx(color *);
+ void add_char(output_character, int, int, int, color *, color *,
+ unsigned char);
+ void simple_add_char(const output_character, const environment *);
+ char *make_rgb_string(unsigned int, unsigned int, unsigned int);
+ bool tty_color(unsigned int, unsigned int, unsigned int, schar *,
+ schar = DEFAULT_COLOR_IDX);
+ void line(int, int, int, int, color *, color *);
+ void draw_line(int *, int, const environment *);
+ void draw_polygon(int *, int, const environment *);
+ void special_link(const char *, const environment *);
+public:
+ tty_printer();
+ ~tty_printer();
+ void set_char(glyph *, font *, const environment *, int, const char *);
+ void draw(int, int *, int, const environment *);
+ void special(char *, const environment *, char);
+ void change_color(const environment * const);
+ void change_fill_color(const environment * const);
+ void put_char(output_character);
+ void put_color(schar, int);
+ void begin_page(int) { }
+ void end_page(int);
+ font *make_font(const char *);
+};
+
+char *tty_printer::make_rgb_string(unsigned int r,
+ unsigned int g,
+ unsigned int b)
+{
+ char *s = new char[8];
+ s[0] = char(r >> 8);
+ s[1] = char(r & 0xff);
+ s[2] = char(g >> 8);
+ s[3] = char(g & 0xff);
+ s[4] = char(b >> 8);
+ s[5] = char(b & 0xff);
+ s[6] = char(0x80);
+ s[7] = 0;
+ // avoid null-bytes in string
+ for (int i = 0; i < 6; i++)
+ if (!s[i]) {
+ s[i] = 1;
+ s[6] |= 1 << i;
+ }
+ return s;
+}
+
+bool tty_printer::tty_color(unsigned int r,
+ unsigned int g,
+ unsigned int b, schar *idx, schar value)
+{
+ bool is_known_color = true;
+ char *s = make_rgb_string(r, g, b);
+ schar *i = tty_colors.lookup(s);
+ if (!i) {
+ is_known_color = false;
+ i = new schar[1];
+ *i = value;
+ tty_colors.define(s, i);
+ }
+ *idx = *i;
+ delete[] s;
+ return is_known_color;
+}
+
+tty_printer::tty_printer() : cached_v(0)
+{
+ if (font::is_unicode) {
+ hline_char = 0x2500;
+ vline_char = 0x2502;
+ }
+ schar dummy;
+ // black, white
+ (void)tty_color(0, 0, 0, &dummy, 0);
+ (void)tty_color(color::MAX_COLOR_VAL,
+ color::MAX_COLOR_VAL,
+ color::MAX_COLOR_VAL, &dummy, 7);
+ // red, green, blue
+ (void)tty_color(color::MAX_COLOR_VAL, 0, 0, &dummy, 1);
+ (void)tty_color(0, color::MAX_COLOR_VAL, 0, &dummy, 2);
+ (void)tty_color(0, 0, color::MAX_COLOR_VAL, &dummy, 4);
+ // yellow, magenta, cyan
+ (void)tty_color(color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, 0, &dummy, 3);
+ (void)tty_color(color::MAX_COLOR_VAL, 0, color::MAX_COLOR_VAL, &dummy, 5);
+ (void)tty_color(0, color::MAX_COLOR_VAL, color::MAX_COLOR_VAL, &dummy, 6);
+ nlines = 66;
+ lines = new tty_glyph *[nlines];
+ for (int i = 0; i < nlines; i++)
+ lines[i] = 0;
+ is_continuously_underlining = false;
+}
+
+tty_printer::~tty_printer()
+{
+ delete[] lines;
+}
+
+void tty_printer::make_underline(int w)
+{
+ if (use_overstriking_drawing_scheme) {
+ if (!w)
+ warning("can't underline zero-width character");
+ else {
+ putchar('_');
+ putchar('\b');
+ }
+ }
+ else {
+ if (!is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_REVERSE);
+ else
+ putstring(SGR_UNDERLINE);
+ }
+ is_underlining = true;
+ }
+}
+
+void tty_printer::make_bold(output_character c, int w)
+{
+ if (use_overstriking_drawing_scheme) {
+ if (!w)
+ warning("can't print zero-width character in bold");
+ else {
+ put_char(c);
+ putchar('\b');
+ }
+ }
+ else {
+ if (!is_boldfacing)
+ putstring(SGR_BOLD);
+ is_boldfacing = true;
+ }
+}
+
+schar tty_printer::color_to_idx(color *col)
+{
+ if (col->is_default())
+ return DEFAULT_COLOR_IDX;
+ unsigned int r, g, b;
+ col->get_rgb(&r, &g, &b);
+ schar idx;
+ if (!tty_color(r, g, b, &idx)) {
+ char *s = col->print_color();
+ error("unrecognized color '%1' mapped to default", s);
+ delete[] s;
+ }
+ return idx;
+}
+
+void tty_printer::set_char(glyph *g, font *f, const environment *env,
+ int w, const char *)
+{
+ if (w % font::hor != 0)
+ fatal("glyph width is not a multiple of horizontal motion quantum");
+ add_char(f->get_code(g), w,
+ env->hpos, env->vpos,
+ env->col, env->fill,
+ ((tty_font *)f)->get_mode());
+}
+
+void tty_printer::add_char(output_character c, int w,
+ int h, int v,
+ color *fore, color *back,
+ unsigned char mode)
+{
+#if 0
+ // This is too expensive.
+ if (h % font::hor != 0)
+ fatal("horizontal position not a multiple of horizontal motion quantum");
+#endif
+ int hpos = h / font::hor;
+ if (hpos < SHRT_MIN || hpos > SHRT_MAX) {
+ error("character with ridiculous horizontal position discarded");
+ return;
+ }
+ int vpos;
+ if (v == cached_v && cached_v != 0)
+ vpos = cached_vpos;
+ else {
+ if (v % font::vert != 0)
+ fatal("vertical position not a multiple of vertical motion"
+ " quantum");
+ vpos = v / font::vert;
+ if (vpos > nlines) {
+ tty_glyph **old_lines = lines;
+ lines = new tty_glyph *[vpos + 1];
+ memcpy(lines, old_lines, nlines * sizeof(tty_glyph *));
+ for (int i = nlines; i <= vpos; i++)
+ lines[i] = 0;
+ delete[] old_lines;
+ nlines = vpos + 1;
+ }
+ // Note that the first output line corresponds to groff
+ // position font::vert.
+ if (vpos <= 0) {
+ error("output above first line discarded");
+ return;
+ }
+ cached_v = v;
+ cached_vpos = vpos;
+ }
+ tty_glyph *g = new tty_glyph;
+ g->w = w;
+ g->hpos = hpos;
+ g->code = c;
+ g->fore_color_idx = color_to_idx(fore);
+ g->back_color_idx = color_to_idx(back);
+ g->mode = mode;
+
+ // The list will be reversed later. After reversal, it must be in
+ // increasing order of hpos, with COLOR_CHANGE and CU specials before
+ // HDRAW characters before VDRAW characters before normal characters
+ // at each hpos, and otherwise in order of occurrence.
+
+ tty_glyph **pp;
+ for (pp = lines + (vpos - 1); *pp; pp = &(*pp)->next)
+ if ((*pp)->hpos < hpos
+ || ((*pp)->hpos == hpos && (*pp)->order() >= g->order()))
+ break;
+ g->next = *pp;
+ *pp = g;
+}
+
+void tty_printer::simple_add_char(const output_character c,
+ const environment *env)
+{
+ add_char(c, 0, env->hpos, env->vpos, env->col, env->fill, 0);
+}
+
+void tty_printer::special(char *arg, const environment *env, char type)
+{
+ if (type == 'u') {
+ add_char(*arg - '0', 0, env->hpos, env->vpos, env->col, env->fill,
+ CU_MODE);
+ return;
+ }
+ if (type != 'p')
+ return;
+ char *p;
+ for (p = arg; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *tag = p;
+ for (; *p != '\0' && *p != ':' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*p == '\0' || strncmp(tag, "tty", p - tag) != 0) {
+ error("X command without 'tty:' tag ignored");
+ return;
+ }
+ p++;
+ for (; *p == ' ' || *p == '\n'; p++)
+ ;
+ char *command = p;
+ for (; *p != '\0' && *p != ' ' && *p != '\n'; p++)
+ ;
+ if (*command == '\0') {
+ error("empty X command ignored");
+ return;
+ }
+ if (strncmp(command, "link", p - command) == 0)
+ special_link(p, env);
+ else
+ warning("unrecognized X command '%1' ignored", command);
+}
+
+// Produce an OSC 8 hyperlink. Given ditroff input of the form:
+// x X tty: link [URI[ KEY=VALUE] ...]
+// produce "OSC 8 [;KEY=VALUE];[URI] ST". KEY/VALUE pairs can be
+// repeated arbitrarily and are separated by colons. Omission of the
+// URI ends the hyperlink that was begun by specifying it. See
+// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>.
+void tty_printer::special_link(const char *arg, const environment *env)
+{
+ static bool is_link_active = false;
+ if (use_overstriking_drawing_scheme)
+ return;
+ for (const char *s = OSC8; *s != '\0'; s++)
+ simple_add_char(*s, env);
+ simple_add_char(';', env);
+ char c = *arg;
+ if ('\0' == c || '\n' == c) {
+ simple_add_char(';', env);
+ if (!is_link_active)
+ warning("ending hyperlink when none was started");
+ is_link_active = false;
+ }
+ else {
+ // Our caller ensures that we see white space after 'link'.
+ assert(c == ' ' || c == '\t');
+ if (is_link_active) {
+ warning("new hyperlink started without ending previous one;"
+ " recovering");
+ simple_add_char(';', env);
+ for (const char *s = ST OSC8; *s != '\0'; s++)
+ simple_add_char(*s, env);
+ simple_add_char(';', env);
+ }
+ is_link_active = true;
+ do
+ c = *arg++;
+ while (c == ' ' || c == '\t');
+ arg--;
+ // The first argument is the URI.
+ const char *uri = arg;
+ do
+ c = *arg++;
+ while (c != '\0' && c != ' ' && c != '\t');
+ arg--;
+ ptrdiff_t uri_len = arg - uri;
+ // Any remaining arguments are "key=value" pairs.
+ const char *pair = 0;
+ bool done = false;
+ do {
+ if (pair != 0)
+ simple_add_char(':', env);
+ pair = arg;
+ bool in_pair = true;
+ do {
+ c = *arg++;
+ if ('\0' == c)
+ done = true;
+ else if (' ' == c || '\t' == c)
+ in_pair = false;
+ else
+ simple_add_char(c, env);
+ } while (!done && in_pair);
+ } while (!done);
+ simple_add_char(';', env);
+ for (size_t i = uri_len; i > 0; i--)
+ simple_add_char(*uri++, env);
+ }
+ for (const char *s = ST; *s != '\0'; s++)
+ simple_add_char(*s, env);
+}
+
+void tty_printer::change_color(const environment * const env)
+{
+ add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
+}
+
+void tty_printer::change_fill_color(const environment * const env)
+{
+ add_char(0, 0, env->hpos, env->vpos, env->col, env->fill, COLOR_CHANGE);
+}
+
+void tty_printer::draw(int code, int *p, int np, const environment *env)
+{
+ if (!allow_drawing_commands)
+ return;
+ if (code == 'l')
+ draw_line(p, np, env);
+ else if (code == 'p')
+ draw_polygon(p, np, env);
+ else
+ warning("ignoring unsupported drawing command '%1'", char(code));
+}
+
+void tty_printer::draw_polygon(int *p, int np, const environment *env)
+{
+ if (np & 1) {
+ error("even number of arguments required for polygon");
+ return;
+ }
+ if (np == 0) {
+ error("no arguments for polygon");
+ return;
+ }
+ // We only draw polygons which consist entirely of horizontal and
+ // vertical lines.
+ int hpos = 0;
+ int vpos = 0;
+ for (int i = 0; i < np; i += 2) {
+ if (!(p[i] == 0 || p[i + 1] == 0))
+ return;
+ hpos += p[i];
+ vpos += p[i + 1];
+ }
+ if (!(hpos == 0 || vpos == 0))
+ return;
+ int start_hpos = env->hpos;
+ int start_vpos = env->vpos;
+ hpos = start_hpos;
+ vpos = start_vpos;
+ for (int i = 0; i < np; i += 2) {
+ line(hpos, vpos, p[i], p[i + 1], env->col, env->fill);
+ hpos += p[i];
+ vpos += p[i + 1];
+ }
+ line(hpos, vpos, start_hpos - hpos, start_vpos - vpos,
+ env->col, env->fill);
+}
+
+void tty_printer::draw_line(int *p, int np, const environment *env)
+{
+ if (np != 2) {
+ error("2 arguments required for line");
+ return;
+ }
+ line(env->hpos, env->vpos, p[0], p[1], env->col, env->fill);
+}
+
+void tty_printer::line(int hpos, int vpos, int dx, int dy,
+ color *col, color *fill)
+{
+ // XXX: zero-length lines get drawn as '+' crossings in nroff, even
+ // when there is no crossing, but they nevertheless occur frequently
+ // in input. Does tbl produce them?
+#if 0
+ if (0 == dx)
+ fatal("cannot draw zero-length horizontal line");
+ if (0 == dy)
+ fatal("cannot draw zero-length vertical line");
+#endif
+ if ((dx != 0) && (dy != 0))
+ warning("cannot draw diagonal line");
+ if (dx % font::hor != 0)
+ fatal("length of horizontal line %1 is not a multiple of horizontal"
+ " motion quantum %2", dx, font::hor);
+ if (dy % font::vert != 0)
+ fatal("length of vertical line %1 is not a multiple of vertical"
+ " motion quantum %2", dy, font::vert);
+ if (dx == 0) {
+ // vertical line
+ int v = vpos;
+ int len = dy;
+ if (len < 0) {
+ v += len;
+ len = -len;
+ }
+ if (len == 0)
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|START_LINE|END_LINE);
+ else {
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|START_LINE);
+ len -= font::vert;
+ v += font::vert;
+ while (len > 0) {
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|START_LINE|END_LINE);
+ len -= font::vert;
+ v += font::vert;
+ }
+ add_char(vline_char, font::hor, hpos, v, col, fill,
+ VDRAW_MODE|END_LINE);
+ }
+ }
+ if (dy == 0) {
+ // horizontal line
+ int h = hpos;
+ int len = dx;
+ if (len < 0) {
+ h += len;
+ len = -len;
+ }
+ if (len == 0)
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|START_LINE|END_LINE);
+ else {
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|START_LINE);
+ len -= font::hor;
+ h += font::hor;
+ while (len > 0) {
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|START_LINE|END_LINE);
+ len -= font::hor;
+ h += font::hor;
+ }
+ add_char(hline_char, font::hor, h, vpos, col, fill,
+ HDRAW_MODE|END_LINE);
+ }
+ }
+}
+
+void tty_printer::put_char(output_character wc)
+{
+ if (font::is_unicode && wc >= 0x80) {
+ char buf[6 + 1];
+ int count;
+ char *p = buf;
+ if (wc < 0x800)
+ count = 1, *p = (unsigned char)((wc >> 6) | 0xc0);
+ else if (wc < 0x10000)
+ count = 2, *p = (unsigned char)((wc >> 12) | 0xe0);
+ else if (wc < 0x200000)
+ count = 3, *p = (unsigned char)((wc >> 18) | 0xf0);
+ else if (wc < 0x4000000)
+ count = 4, *p = (unsigned char)((wc >> 24) | 0xf8);
+ else if (wc <= 0x7fffffff)
+ count = 5, *p = (unsigned char)((wc >> 30) | 0xfC);
+ else
+ return;
+ do *++p = (unsigned char)(((wc >> (6 * --count)) & 0x3f) | 0x80);
+ while (count > 0);
+ *++p = '\0';
+ putstring(buf);
+ }
+ else
+ putchar(wc);
+}
+
+void tty_printer::put_color(schar color_index, int back)
+{
+ if (color_index == DEFAULT_COLOR_IDX) {
+ putstring(SGR_DEFAULT);
+ // set bold and underline again
+ if (is_boldfacing)
+ putstring(SGR_BOLD);
+ if (is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_REVERSE);
+ else
+ putstring(SGR_UNDERLINE);
+ }
+ // set other color again
+ back = !back;
+ color_index = back ? curr_back_idx : curr_fore_idx;
+ }
+ if (color_index != DEFAULT_COLOR_IDX) {
+ putstring(CSI);
+ if (back)
+ putchar('4');
+ else
+ putchar('3');
+ putchar(color_index + '0');
+ putchar('m');
+ }
+}
+
+// The possible Unicode combinations for crossing characters.
+//
+// ' ' = 0, ' -' = 4, '- ' = 8, '--' = 12,
+//
+// ' ' = 0, ' ' = 1, '|' = 2, '|' = 3
+// | |
+
+static output_character crossings[4*4] = {
+ 0x0000, 0x2577, 0x2575, 0x2502,
+ 0x2576, 0x250C, 0x2514, 0x251C,
+ 0x2574, 0x2510, 0x2518, 0x2524,
+ 0x2500, 0x252C, 0x2534, 0x253C
+};
+
+void tty_printer::end_page(int page_length)
+{
+ if (page_length % font::vert != 0)
+ error("vertical position at end of page not multiple of vertical"
+ " motion quantum");
+ int lines_per_page = page_length / font::vert;
+ int last_line;
+ for (last_line = nlines; last_line > 0; last_line--)
+ if (lines[last_line - 1])
+ break;
+#if 0
+ if (last_line > lines_per_page) {
+ error("characters past last line discarded");
+ do {
+ --last_line;
+ while (lines[last_line]) {
+ tty_glyph *tem = lines[last_line];
+ lines[last_line] = tem->next;
+ delete tem;
+ }
+ } while (last_line > lines_per_page);
+ }
+#endif
+ for (int i = 0; i < last_line; i++) {
+ tty_glyph *p = lines[i];
+ lines[i] = 0;
+ tty_glyph *g = 0;
+ while (p) {
+ tty_glyph *tem = p->next;
+ p->next = g;
+ g = p;
+ p = tem;
+ }
+ int hpos = 0;
+ tty_glyph *nextp;
+ curr_fore_idx = DEFAULT_COLOR_IDX;
+ curr_back_idx = DEFAULT_COLOR_IDX;
+ is_underlining = false;
+ is_boldfacing = false;
+ for (p = g; p; delete p, p = nextp) {
+ nextp = p->next;
+ if (p->mode & CU_MODE) {
+ is_continuously_underlining = (p->code != 0);
+ continue;
+ }
+ if (nextp && p->hpos == nextp->hpos) {
+ if (p->draw_mode() == HDRAW_MODE &&
+ nextp->draw_mode() == VDRAW_MODE) {
+ if (font::is_unicode)
+ nextp->code =
+ crossings[((p->mode & (START_LINE|END_LINE)) >> 4)
+ + ((nextp->mode & (START_LINE|END_LINE)) >> 6)];
+ else
+ nextp->code = '+';
+ continue;
+ }
+ if (p->draw_mode() != 0 && p->draw_mode() == nextp->draw_mode()) {
+ nextp->code = p->code;
+ continue;
+ }
+ if (!want_glyph_composition_by_overstriking)
+ continue;
+ }
+ if (hpos > p->hpos) {
+ do {
+ putchar('\b');
+ hpos--;
+ } while (hpos > p->hpos);
+ }
+ else {
+ if (want_horizontal_tabs) {
+ for (;;) {
+ int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH;
+ if (next_tab_pos > p->hpos)
+ break;
+ if (is_continuously_underlining)
+ make_underline(p->w);
+ else if (!use_overstriking_drawing_scheme
+ && is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_NO_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_NO_REVERSE);
+ else
+ putstring(SGR_NO_UNDERLINE);
+ is_underlining = false;
+ }
+ putchar('\t');
+ hpos = next_tab_pos;
+ }
+ }
+ for (; hpos < p->hpos; hpos++) {
+ if (is_continuously_underlining)
+ make_underline(p->w);
+ else if (!use_overstriking_drawing_scheme && is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_NO_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_NO_REVERSE);
+ else
+ putstring(SGR_NO_UNDERLINE);
+ is_underlining = false;
+ }
+ putchar(' ');
+ }
+ }
+ assert(hpos == p->hpos);
+ if (p->mode & COLOR_CHANGE) {
+ if (!use_overstriking_drawing_scheme) {
+ if (p->fore_color_idx != curr_fore_idx) {
+ put_color(p->fore_color_idx, 0);
+ curr_fore_idx = p->fore_color_idx;
+ }
+ if (p->back_color_idx != curr_back_idx) {
+ put_color(p->back_color_idx, 1);
+ curr_back_idx = p->back_color_idx;
+ }
+ }
+ continue;
+ }
+ if (p->mode & UNDERLINE_MODE)
+ make_underline(p->w);
+ else if (!use_overstriking_drawing_scheme && is_underlining) {
+ if (do_sgr_italics)
+ putstring(SGR_NO_ITALIC);
+ else if (do_reverse_video)
+ putstring(SGR_NO_REVERSE);
+ else
+ putstring(SGR_NO_UNDERLINE);
+ is_underlining = false;
+ }
+ if (p->mode & BOLD_MODE)
+ make_bold(p->code, p->w);
+ else if (!use_overstriking_drawing_scheme && is_boldfacing) {
+ putstring(SGR_NO_BOLD);
+ is_boldfacing = false;
+ }
+ if (!use_overstriking_drawing_scheme) {
+ if (p->fore_color_idx != curr_fore_idx) {
+ put_color(p->fore_color_idx, 0);
+ curr_fore_idx = p->fore_color_idx;
+ }
+ if (p->back_color_idx != curr_back_idx) {
+ put_color(p->back_color_idx, 1);
+ curr_back_idx = p->back_color_idx;
+ }
+ }
+ put_char(p->code);
+ hpos += p->w / font::hor;
+ }
+ if (!use_overstriking_drawing_scheme
+ && (is_boldfacing || is_underlining
+ || curr_fore_idx != DEFAULT_COLOR_IDX
+ || curr_back_idx != DEFAULT_COLOR_IDX))
+ putstring(SGR_DEFAULT);
+ putchar('\n');
+ }
+ if (want_form_feeds) {
+ if (last_line < lines_per_page)
+ putchar('\f');
+ }
+ else {
+ for (; last_line < lines_per_page; last_line++)
+ putchar('\n');
+ }
+}
+
+font *tty_printer::make_font(const char *nm)
+{
+ return tty_font::load_tty_font(nm);
+}
+
+printer *make_printer()
+{
+ return new tty_printer();
+}
+
+static void update_options()
+{
+ if (use_overstriking_drawing_scheme) {
+ do_sgr_italics = false;
+ do_reverse_video = false;
+ bold_underline_mode = bold_underline_mode_option;
+ do_bold = want_emboldening_by_overstriking;
+ do_underline = want_italics_by_underlining;
+ }
+ else {
+ do_sgr_italics = want_sgr_italics;
+ do_reverse_video = want_reverse_video_for_italics;
+ bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;
+ do_bold = true;
+ do_underline = true;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ if (getenv("GROFF_NO_SGR"))
+ use_overstriking_drawing_scheme = true;
+ setbuf(stderr, stderr_buf);
+ setlocale(LC_CTYPE, "");
+ int c;
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((c = getopt_long(argc, argv, "bBcdfF:hiI:oruUv", long_options, NULL))
+ != EOF)
+ switch(c) {
+ case 'v':
+ printf("GNU grotty (groff) version %s\n", Version_string);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'i':
+ // Use italic font instead of underlining.
+ want_sgr_italics = true;
+ break;
+ case 'I':
+ // ignore include search path
+ break;
+ case 'b':
+ // Do not embolden by overstriking.
+ want_emboldening_by_overstriking = false;
+ break;
+ case 'c':
+ // Use old scheme for emboldening and underline.
+ use_overstriking_drawing_scheme = true;
+ break;
+ case 'u':
+ // Do not underline.
+ want_italics_by_underlining = false;
+ break;
+ case 'o':
+ // Do not overstrike (other than emboldening and underlining).
+ want_glyph_composition_by_overstriking = false;
+ break;
+ case 'r':
+ // Use reverse mode instead of underlining.
+ want_reverse_video_for_italics = true;
+ break;
+ case 'B':
+ // Do bold-underlining as bold.
+ bold_underline_mode_option = BOLD_MODE;
+ break;
+ case 'U':
+ // Do bold-underlining as underlining.
+ bold_underline_mode_option = UNDERLINE_MODE;
+ break;
+ case 'h':
+ // Use horizontal tabs.
+ want_horizontal_tabs = true;
+ break;
+ case 'f':
+ want_form_feeds = true;
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'd':
+ // Ignore \D commands.
+ allow_drawing_commands = false;
+ break;
+ case CHAR_MAX + 1: // --help
+ usage(stdout);
+ break;
+ case '?':
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "unhandled getopt_long return value");
+ }
+ update_options();
+ if (optind >= argc)
+ do_file("-");
+ else {
+ for (int i = optind; i < argc; i++)
+ do_file(argv[i]);
+ }
+ return 0;
+}
+
+static void usage(FILE *stream)
+{
+ fprintf(stream,
+"usage: %s [-bBcdfhioruU] [-F font-directory] [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s --help\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"Translate the output of troff(1) into a form suitable for\n"
+"typewriterâ€like devices, including terminal emulators. See the\n"
+"grotty(1) manual page.\n",
+ stream);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/devices/xditview/ChangeLog b/src/devices/xditview/ChangeLog
new file mode 100644
index 0000000..a33297b
--- /dev/null
+++ b/src/devices/xditview/ChangeLog
@@ -0,0 +1,556 @@
+2004-05-29 Werner LEMBERG <wl@gnu.org>
+
+ gxditview and xtotroff have been integrated into the normal groff
+ directory structure; future changes are logged in the main
+ ChangeLog file.
+
+2004-05-13 Werner LEMBERG <wl@gnu.org>
+
+Version 1.19.1 released
+=======================
+
+2004-04-17 Werner LEMBERG <wl@gnu.org>
+
+ * device.c (scale_round): Round correctly for negative values
+ (this is the same function as in src/libs/libgroff/font.c).
+ Found by Paul Eggert.
+
+2003-11-10 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in: s/@top_srcdir@/@abs_top_srcdir@/,
+ s/@groff_top_builddir@/@abs_top_builddir@/.
+
+Version 1.19 released
+=====================
+
+2003-03-03 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in (extraclean): Added gxditview._man.
+
+2003-01-28 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in (SEP): New variable; set to @PATH_SEPARATOR@.
+ (GROFF_FONTPATH): Use it.
+
+2003-01-07 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Add `sqrt'.
+
+2003-01-06 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Add `integral'.
+
+2002-12-29 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Remove `ap' and `eq'.
+
+2002-12-20 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Don't include `or'.
+ * draw.c (AdjustCharDeltas): Apply correction only if nadj > 1.
+ (DoCharacter): Call FlushCharCache if font size and font number
+ differ.
+ Reset `dw->dvi.cache.adjustable' properly.
+
+2002-12-09 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Use `tno' symbol instead of `no'.
+
+2002-12-01 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in: Use `InstallAppDefaultsLong' instead of
+ `InstallAppDefaults' to make it work if build directory isn't
+ $srcdir.
+
+2002-11-24 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (Adobe_Symbol_map): Add glyph `braceex'.
+
+2002-11-14 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Don't include `or'.
+
+Version 1.18.1 released
+=======================
+
+2002-09-16 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in (GROFF_LOCALFONTDIR): New variable.
+ (GROFF_FONTPATH): Use it.
+ Remove /usr/local/lib/font.
+
+Version 1.18.0 released
+=======================
+
+2002-06-22 Werner LEMBERG <wl@gnu.org>
+
+ * gxditview.c (main): Handle `-help' and `--help' correctly.
+
+2002-06-17 Colin Watson <cjwatson@debian.org>
+
+ * Imakefile.in: s/@top_builddir@/@groff_top_builddir@/.
+
+2002-04-06 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map, Adobe_Symbol_map): Remove all
+ characters > 0x80.
+ * parse.c (ParseInput): Ignore `m' command.
+ (ParseDrawFunction): Don't move for unknown drawing functions.
+ Don't move for `f' drawing function.
+
+2002-03-25 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Use `t+-', `tmu', and `tdi' symbols
+ instead of `+-', `mu', and `di', respectively.
+
+2002-02-23 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c (ISO_8859_1_map): Add `mc' symbol.
+
+2001-09-22 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile.in: Redefine `ProgramTargetHelper' as
+ `ProgramTargetHelperNoMan' and add a call to `InstallManPageLong'
+ to make the `install.man' target work if the build directory isn't
+ $srcdir.
+
+Version 1.17.2 released
+=======================
+
+Version 1.17.1 released
+=======================
+
+2001-04-21 Albert Chin-A-Young <china@thewrittenword.com>
+
+ * Imakefile.in: Add support for recent HP architectures.
+
+Version 1.17 released
+=====================
+
+2001-01-04 Rob Daasch <daasch@ece.pdx.edu>
+
+ * parse.c (ParseInput): Added 'F' to command switch to swallow
+ filename strings as ignored comments.
+
+2000-12-02 Werner LEMBERG <wl@gnu.org>
+
+ * device.c (find_file): Remove home directory in search path.
+
+2000-11-14 Werner LEMBERG <wl@gnu.org>
+
+ * device.c (open_device_file): Remove `path' parameter.
+ (find_file): Construct font path similar to groff: First the contents
+ of GROFF_FONT_PATH, then the home directory, and finally the default
+ font path.
+ * Imakefile.in: Fix GROFF_DATAPROGRAMDIR and GROFF_FONTPATH.
+
+2000-10-23 Werner LEMBERG <wl@gnu.org>
+
+ Change installation structure for data files from .../groff/... to
+ .../groff/<version><revision>/... to be conform with other GNU
+ programs.
+
+ * Imakefile.in: Implement it.
+
+Version 1.16.1 released
+=======================
+
+Version 1.16 released
+=====================
+
+2000-05-18 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c: Adding `cq' as an alias for "'" in latin-1 map.
+
+2000-05-03 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c: Adding `dq' as an alias for `"' in latin-1 map.
+
+2000-04-28 Werner LEMBERG <wl@gnu.org>
+
+ * DviChar.c: Replacing `md' glyph name with `pc' in latin-1 map to
+ make it distinct from the `md' glyph in the symbol font.
+
+2000-03-03 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile replaced with Imakefile.in which will be configured by
+ the main configure script of groff. This will set the correct font
+ path, and it will make it possible to build xditview in a directory
+ different from $srcdir.
+
+2000-03-01 Colin Phipps <crp22@cam.ac.uk>
+
+ * Dvi.c (OpenFile): Use tmpdir() for security reasons.
+ * xtotroff.c (MapFont): Avoid race while opening file.
+
+2000-02-06 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile: Adapted to new directory structure.
+
+ * README: Updated.
+
+Version 1.15 released
+=====================
+
+1999-12-21 Werner LEMBERG <wl@gnu.org>
+
+ * README: Fixed ftp GNU address.
+
+1999-12-13 Werner LEMBERG <wl@gnu.org>
+
+ * device.c: Use extern declarations of strtok(), strchr(), and
+ getenv() only if not defined as macros.
+
+1999-11-18 Larry Jones <larry.jones@sdrc.com>
+
+ * xditview.c: Add fallback_resources to allow running without
+ access to the app-defaults file.
+
+ * Imakefile: Added rule to create app-defaults to a C header file.
+
+ * GXditview-ad.h: New file containing fallback default resources.
+
+ * ad2c: New file to do the app-defaults -> C header file
+ conversion.
+
+1999-10-27 Larry Jones <larry.jones@sdrc.com>
+
+ * font.c (DisposeFontSizes): If there's a problem loading a font,
+ xditview will fall-back and use the default font, but it hasn't
+ checked before unloading fonts which could result in unloading the
+ default font (possibly multiple times) and then X errors.
+
+1999-09-13 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile (extraclean): Added Makefile.
+
+ * xditview.c (main, MakePrompt): Fixing compilation warnings.
+
+ * TODO: Imakefile should be replaced with a configure script.
+
+1999-09-13 Werner LEMBERG <wl@gnu.org>
+
+ * Makefile: Removed.
+
+1999-09-12 Werner LEMBERG <wl@gnu.org>
+
+ * Imakefile (GROFF_FONTPATH): Another addition.
+
+ * device.c (FONTPATH): Update to match current groff version.
+
+1999-09-11 Larry Jones <larry.jones@sdrc.com>
+
+ * Imakefile (GROFF_LIBDIR, GROFF_FONTPATH): Update to match
+ current groff version.
+
+ * Dvi.c (Realize, Destroy), DviP.h, draw.c (setFillGC), gray*.bm:
+ Allow 8 levels of gray rather than just 1.
+
+ * draw.c (DrawFilledCircle, DrawFilledEllipse, DrawFilledPolygon):
+ Draw outlines to prevent gaps between abutting figures.
+
+1999-05-27 Werner LEMBERG <wl@gnu.org>
+
+ * xtotroff.c (usage): Fixed typo.
+
+Mon Sep 11 10:40:33 1995 James Clark <jjc@jclark.com>
+
+ * device.c (INT_MIN, INT_MAX): Don't define if already defined.
+
+Mon Aug 8 11:14:11 1994 James Clark (jjc@jclark.com)
+
+ * DviChar.c (Adobe_Symbol_map): Use \(nb for notsubset.
+
+Tue Apr 19 04:41:16 1994 James Clark (jjc@jclark.com)
+
+ * Dvi.c (resources): Change default for background and foreground
+ to "XtDefaultBackground" and "XtDefaultForeground".
+
+Sat Feb 12 10:38:47 1994 James Clark (jjc@jclark.com)
+
+ * DviChar.c (Adobe_Symbol_map): Rename radicalex to rn.
+
+Thu May 27 20:30:12 1993 James Clark (jjc@jclark.com)
+
+ * device.c (isascii): Define if necessary.
+ (canonicalize_name): Cast argument to isdigit() to unsigned char.
+
+Thu Apr 29 18:36:57 1993 James Clark (jjc at jclark.com)
+
+ * xditview.c: Include <X11/Xos.h>.
+ (NewFile): Don't declare rindex(). Use strrchr() rather than
+ rindex().
+
+Tue Mar 30 15:12:09 1993 James Clark (jjc at jclark)
+
+ * draw.c (charExists): Check that fi->per_char is not NULL.
+
+Sat Dec 12 17:42:40 1992 James Clark (jjc at jclark)
+
+ * Dvi.c (SetGeometry): Cast XtMakeGeometryRequest arguments.
+
+ * draw.c (DrawPolygon, DrawFilledPolygon): Cast Xtfree argument.
+
+ * font.c (DisposeFontSizes): Add declaration.
+
+ * draw.c (FakeCharacter): Add declaration.
+
+Wed Oct 28 13:24:00 1992 James Clark (jjc at jclark)
+
+ * Imakefile (install.dev): Deleted.
+ (fonts): New target.
+
+Mon Oct 12 10:50:44 1992 James Clark (jjc at jclark)
+
+ * Imakefile (install.dev): Say when we're installing devX*-12.
+
+ * Imakefile (install.dev): Depends on DESC and FontMap.
+
+Thu Oct 1 20:03:45 1992 James Clark (jjc at jclark)
+
+ * xditview.c (Syntax): Mention -filename option.
+
+Sat Aug 15 12:56:39 1992 James Clark (jjc at jclark)
+
+ * GXditview.ad: Bind space and return to NextPage. Bind backspace
+ and delete to previous page.
+
+ * DviChar.c (Adobe_Symbol_map): Add `an'.
+
+ * DviChar.c (Adobe_Symbol_map): Add arrowvertex, arrowverttp, and
+ arrowvertbt.
+
+Mon Aug 10 11:54:27 1992 James Clark (jjc at jclark)
+
+ * FontMap: Add m/p fields to the fonts names.
+
+Sat Aug 8 12:00:28 1992 James Clark (jjc at jclark)
+
+ * DESC: Leave font positions 5-9 blank.
+
+Tue Jul 28 11:37:05 1992 James Clark (jjc at jclark)
+
+ * Imakefile: Don't use gendef. Pass definition of FONTPATH using
+ DEFINES.
+ (path.h): Deleted.
+ (device.c): Don't include path.h. Provide default definition of
+ FONTPATH.
+
+Mon Jul 6 14:06:53 1992 James Clark (jjc at jclark)
+
+ * Imakefile: Don't install tmac.X and tmac.Xps.
+ * tmac.X, tmac.Xps: Moved to ../macros.
+
+ * Imakefile: Don't install eqnchar.
+ * eqnchar: Deleted.
+
+Sun Jun 14 12:55:02 1992 James Clark (jjc@jclark)
+
+ * tmac.Xps: Handle OE, oe, lq, rq.
+ * draw.c (FakeCharacter): Don't handle these.
+
+ * draw.c (FakeCharacter): Don't handle f/.
+
+Mon Jun 8 11:46:37 1992 James Clark (jjc@jclark)
+
+ * tmac.X: Translate char160 to space.
+
+Sun Jun 7 14:39:53 1992 James Clark (jjc@jclark)
+
+ * tmac.X: Do `mso tmac.psic' before restoring compatibility mode.
+
+ * tmac.X: Add \(OE, \(oe, \(ah, \(ao, \(ho.
+
+ * tmac.Xps: Make it work in compatibility mode.
+ Redo existing character definitions with .Xps-char.
+ Add more character definitions.
+ (Xps-char): New macro.
+
+Sat Jun 6 21:46:03 1992 James Clark (jjc@jclark)
+
+ * DviChar.c (Adobe_Symbol_map): Add +h, +f, +p, Fn, lz.
+ * tmac.X: Add \(bq, \(Bq, \(aq.
+ * tmac.Xps: Handle \(aq, \(bq, \(Bq, \(Fn.
+
+Wed Jun 3 11:11:15 1992 James Clark (jjc@jclark)
+
+ * DviChar.c (Adobe_Symbol_map): Add wp.
+
+Tue Apr 21 09:21:59 1992 James Clark (jjc at jclark)
+
+ * GXditview.ad: Bind n, p, q keys to NextPage, PreviousPage and
+ Quit actions.
+
+ * xditview.c (RerasterizeAction): New function.
+ (xditview_actions): Add RerasterizeAction.
+ * GXditview.ad: Bind r key to Rerasterize action.
+
+Fri Apr 17 08:25:36 1992 James Clark (jjc at jclark)
+
+ * xditview.c: Add -filename option.
+ (main): Copy any -filename argument into current_file_name.
+
+Mon Mar 16 10:21:58 1992 James Clark (jjc at jclark)
+
+ * tmac.X: Load tmac.pspic.
+
+Sun Mar 8 11:27:19 1992 James Clark (jjc at jclark)
+
+ * Lex.c (GetLine, GetWord, GetNumber): Rewrite.
+
+Sat Oct 12 22:58:52 1991 James Clark (jjc at jclark)
+
+ * Dvi.c (SetDevice): If the size change request is refused but a
+ larger geometry is offered, request that.
+
+Wed Oct 9 12:27:48 1991 James Clark (jjc at jclark)
+
+ * font.c (InstallFontSizes): Ignore FontNameAverageWidth component.
+
+ * Dvi.c (default_font_map): Add `adobe' to font names to avoid
+ ambiguity.
+
+ * FontMap: New file.
+ * FontMap.X100, FontMap.X75: Deleted.
+ * xtotroff.c (main, usage): Add -s and -r options.
+ (MapFont): Change the font pattern to have the selected resolution and
+ size.
+ * Imakefile (install.dev): Use FontMap and supply appropriate -s
+ and -r options.
+
+ * xtotroff.c (MapFont): Check for ambiguity by comparing canonicalized
+ font names.
+
+ * DviP.h (DviFontList): Add initialized and scalable members.
+ (font.c): Add support for scalable fonts based on R5 xditview.
+
+ * DviChar.c: Use xmalloc rather than malloc.
+ * xditview.c (xmalloc): New function.
+ * xtotroff.c (xmalloc): New function.
+ * other files: Use XtMalloc and XtFree instead of malloc and free.
+
+Thu Aug 29 20:15:31 1991 James Clark (jjc at jclark)
+
+ * draw.c (setGC): Do multiplication in floating point to avoid
+ overflow.
+
+Tue Aug 13 12:04:41 1991 James Clark (jjc at jclark)
+
+ * draw.c (FakeCharacter): Remove casts in definition of pack2.
+
+Tue Jul 30 11:42:39 1991 James Clark (jjc at jclark)
+
+ * tmac.Xps: New file.
+ * Imakefile (install): Install tmac.Xps.
+
+Tue Jul 2 09:31:37 1991 James Clark (jjc at jclark)
+
+ * xtotroff.c (main): Pass argv[0] to usage().
+
+Sun Jun 30 12:34:06 1991 James Clark (jjc at jclark)
+
+ * xtotroff.c (MapFont): Handle the case where XLoadQueryFont
+ returns NULL.
+
+Sat Jun 29 12:32:52 1991 James Clark (jjc at jclark)
+
+ * Imakefile: Use ../gendef to generate path.h.
+
+Sun Jun 16 13:26:34 1991 James Clark (jjc at jclark)
+
+ * Imakefile (depend.o): Change to device.o.
+
+Sun Jun 2 12:17:56 1991 James Clark (jjc at jclark)
+
+ * Imakefile: Remove spaces from the beginning of variable
+ assignment lines.
+
+Sun May 26 14:14:01 1991 James Clark (jjc at jclark)
+
+ * xditview.c (Syntax): Update.
+
+ * Dvi.c (DviSaveToFile, SaveToFile): New functions.
+ (FindPage): Check that we're not readingTmp before checking for
+ end of file of normal input file.
+ (ClassPartInitialize): New function.
+ * Dvi.h: Add declaration of DviSaveToFile.
+ * DviP.h: Add save method to DviClassPart. Declare
+ InheritSaveToFile.
+ * xditview.c (DoPrint, Print, PrintAction): New functions.
+ * xditview.c: Add print menu entry.
+ * xditview.c: Provide printCommand application resource.
+ * lex.c: Don't output EOF to temporary file.
+
+ * Dvi.c (QueryGeometry): Check request->request_mode.
+
+ * Dvi.c (SetDevice): New function.
+ (SetDeviceResolution): Deleted.
+
+ * Dvi.c: Add resolution resource.
+ * DviP.h: Add definitions of XtNResolution and XtCResolution.
+ * xditview.c: Add -resolution argument.
+ * GXditview.ad: Add default for GXditview.height.
+ * Dvi.c (Initialize, SetDevice): Use default_resolution.
+
+ * Dvi.c: Make MY_HEIGHT and MY_WIDTH use the paperlength and
+ paperwidth commands in the DESC file.
+
+ * Dvi.c: Add SS font to default font map.
+
+ * draw.c: Rewritten so as not to assume device and display
+ resolution is the same.
+ * DviP.h: Include device.h. Add device_font member to DviFontList.
+ Add adjustable array to DviCharCache. Add text_x_width,
+ text_device_width, word_flag, device_font, device_font_number,
+ device, native, device_resolution, display_resolution,
+ paperlength, paperwidth, scale_factor, sizescale members.
+ * Dvi.c (Initialize): Initialize new variable used by draw.c.
+ (Destroy): Call device_destroy.
+ * font.c (MaxFontPosition): New function.
+ (LookupFontSizeBySize): Handle sizescale.
+ (InstallFont): Load the device font.
+ (ForgetFonts): New function.
+ (QueryDeviceFont): New function.
+ * parse.c (ParseInput): Handle t and u commands. Split off
+ character output into draw.c.
+ (ParseDeviceControl): Ignore res command. Use the device argument
+ to the T command.
+
+ * font.c (MapXNameToDviName): Ifdefed out.
+
+ * path.h: New file.
+ * device.c, device.h: New files.
+
+ * DviChar.c: Add entries for lB, rB, oq, lC, rC, md.
+
+ * INSTALL: New file.
+
+ * libxdvi: Merged into main directory.
+ * xtotroff.c, xditview.c: Change includes accordingly.
+
+ * devX75, devX100: Merged into main directory.
+ * xditview.man: Renamed to gxditview.man.
+
+ * Xditview.ad: Renamed to GXditview.ad.
+ * xditview.c (main): Use class of GXditview rather than xditview.
+
+ * Imakefile: New file.
+ * Makefile: Deleted.
+
+ * xtotroff.c (MapFont): Unlink output file before opening it.
+
+ * Started separate ChangeLog.
+
+________________________________________________________________________
+
+Copyright 1991-2020 Free Software Foundation, Inc.
+
+Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.
+
+Local Variables:
+version-control: never
+mode: change-log
+coding: latin-1
+End:
diff --git a/src/devices/xditview/DESC.in b/src/devices/xditview/DESC.in
new file mode 100644
index 0000000..172170c
--- /dev/null
+++ b/src/devices/xditview/DESC.in
@@ -0,0 +1,9 @@
+styles R I B BI
+fonts 6 0 0 0 0 0 S
+sizes 8 10 12 14 18 24 0
+res 75
+X11
+hor 1
+vert 1
+unitwidth 10
+postpro gxditview
diff --git a/src/devices/xditview/Dvi.c b/src/devices/xditview/Dvi.c
new file mode 100644
index 0000000..3520676
--- /dev/null
+++ b/src/devices/xditview/Dvi.c
@@ -0,0 +1,603 @@
+#include <config.h>
+
+#ifndef SABER
+#ifndef lint
+static char Xrcsid[] = "$XConsortium: Dvi.c,v 1.9 89/12/10 16:12:25 rws Exp $";
+#endif /* lint */
+#endif /* SABER */
+
+/*
+ * Dvi.c - Dvi display widget
+ *
+ */
+
+#define XtStrlen(s) ((s) ? strlen(s) : 0)
+
+ /* The following are defined for the reader's convenience. Any
+ Xt..Field macro in this code just refers to some field in
+ one of the substructures of the WidgetRec. */
+
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <X11/Xmu/Converters.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "DviP.h"
+#include "font.h"
+#include "page.h"
+#include "parse.h"
+
+/****************************************************************
+ *
+ * Full class record constant
+ *
+ ****************************************************************/
+
+/* Private Data */
+
+static char default_font_map_1[] = "\
+TR -adobe-times-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+TI -adobe-times-medium-i-normal--*-100-*-*-*-*-iso8859-1\n\
+TB -adobe-times-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+TBI -adobe-times-bold-i-normal--*-100-*-*-*-*-iso8859-1\n\
+CR -adobe-courier-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+CI -adobe-courier-medium-o-normal--*-100-*-*-*-*-iso8859-1\n\
+CB -adobe-courier-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+CBI -adobe-courier-bold-o-normal--*-100-*-*-*-*-iso8859-1\n\
+";
+static char default_font_map_2[] = "\
+HR -adobe-helvetica-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+HI -adobe-helvetica-medium-o-normal--*-100-*-*-*-*-iso8859-1\n\
+HB -adobe-helvetica-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+HBI -adobe-helvetica-bold-o-normal--*-100-*-*-*-*-iso8859-1\n\
+";
+static char default_font_map_3[] = "\
+NR -adobe-new century schoolbook-medium-r-normal--*-100-*-*-*-*-iso8859-1\n\
+NI -adobe-new century schoolbook-medium-i-normal--*-100-*-*-*-*-iso8859-1\n\
+NB -adobe-new century schoolbook-bold-r-normal--*-100-*-*-*-*-iso8859-1\n\
+NBI -adobe-new century schoolbook-bold-i-normal--*-100-*-*-*-*-iso8859-1\n\
+S -adobe-symbol-medium-r-normal--*-100-*-*-*-*-adobe-fontspecific\n\
+SS -adobe-symbol-medium-r-normal--*-100-*-*-*-*-adobe-fontspecific\n\
+";
+
+#define offset(field) XtOffset(DviWidget, field)
+
+#define MY_WIDTH(dw) ((int)(dw->dvi.paperwidth * dw->dvi.scale_factor + .5))
+#define MY_HEIGHT(dw) ((int)(dw->dvi.paperlength * dw->dvi.scale_factor + .5))
+
+static XtResource resources[] = {
+ {(String)XtNfontMap, (String)XtCFontMap, (String)XtRString,
+ sizeof (char *), offset(dvi.font_map_string),
+ (String)XtRString, NULL /* set in code */},
+ {(String)XtNforeground, (String)XtCForeground, (String)XtRPixel,
+ sizeof (unsigned long), offset(dvi.foreground),
+ (String)XtRString, (XtPointer)"XtDefaultForeground"},
+ {(String)XtNbackground, (String)XtCBackground, (String)XtRPixel,
+ sizeof (unsigned long), offset(dvi.background),
+ (String)XtRString, (XtPointer)"XtDefaultBackground"},
+ {(String)XtNpageNumber, (String)XtCPageNumber, (String)XtRInt,
+ sizeof (int), offset(dvi.requested_page),
+ (String)XtRString, (XtPointer)"1"},
+ {(String)XtNlastPageNumber, (String)XtCLastPageNumber, (String)XtRInt,
+ sizeof (int), offset (dvi.last_page),
+ (String)XtRString, (XtPointer)"0"},
+ {(String)XtNfile, (String)XtCFile, (String)XtRFile,
+ sizeof (FILE *), offset (dvi.file),
+ (String)XtRFile, (XtPointer)0},
+ {(String)XtNseek, (String)XtCSeek, (String)XtRBoolean,
+ sizeof (Boolean), offset(dvi.seek),
+ (String)XtRString, (XtPointer)"false"},
+ {(String)XtNfont, (String)XtCFont, (String)XtRFontStruct,
+ sizeof (XFontStruct *), offset(dvi.default_font),
+ (String)XtRString, (XtPointer)"xtdefaultfont"},
+ {(String)XtNbackingStore, (String)XtCBackingStore, (String)XtRBackingStore,
+ sizeof (int), offset(dvi.backing_store),
+ (String)XtRString, (XtPointer)"default"},
+ {(String)XtNnoPolyText, (String)XtCNoPolyText, (String)XtRBoolean,
+ sizeof (Boolean), offset(dvi.noPolyText),
+ (String)XtRString, (XtPointer)"false"},
+ {(String)XtNresolution, (String)XtCResolution, (String)XtRInt,
+ sizeof(int), offset(dvi.default_resolution),
+ (String)XtRString, (XtPointer)"75"},
+};
+
+#undef offset
+
+static void ClassInitialize (void);
+static void ClassPartInitialize(WidgetClass);
+static void Initialize(Widget, Widget, ArgList, Cardinal *);
+static void Realize (Widget, XtValueMask *, XSetWindowAttributes *);
+static void Destroy (Widget);
+static void Redisplay (Widget, XEvent *, Region);
+static Boolean SetValues (Widget, Widget, Widget,
+ ArgList, Cardinal *);
+static Boolean SetValuesHook (Widget, ArgList, Cardinal *);
+static XtGeometryResult QueryGeometry (Widget, XtWidgetGeometry *,
+ XtWidgetGeometry *);
+static void ShowDvi (DviWidget);
+static void CloseFile (DviWidget);
+static void OpenFile (DviWidget);
+static void FindPage (DviWidget);
+
+static void SaveToFile (Widget, FILE *);
+
+DviClassRec dviClassRec = {
+{
+ &widgetClassRec, /* superclass */
+ (String)"Dvi", /* class_name */
+ sizeof(DviRec), /* size */
+ ClassInitialize, /* class_initialize */
+ ClassPartInitialize, /* class_part_initialize */
+ FALSE, /* class_inited */
+ Initialize, /* initialize */
+ NULL, /* initialize_hook */
+ Realize, /* realize */
+ NULL, /* actions */
+ 0, /* num_actions */
+ resources, /* resources */
+ XtNumber(resources), /* resource_count */
+ NULLQUARK, /* xrm_class */
+ FALSE, /* compress_motion */
+ TRUE, /* compress_exposure */
+ TRUE, /* compress_enterleave */
+ FALSE, /* visible_interest */
+ Destroy, /* destroy */
+ NULL, /* resize */
+ Redisplay, /* expose */
+ SetValues, /* set_values */
+ SetValuesHook, /* set_values_hook */
+ NULL, /* set_values_almost */
+ NULL, /* get_values_hook */
+ NULL, /* accept_focus */
+ XtVersion, /* version */
+ NULL, /* callback_private */
+ 0, /* tm_table */
+ QueryGeometry, /* query_geometry */
+ NULL, /* display_accelerator */
+ NULL /* extension */
+},{
+ SaveToFile, /* save */
+},
+};
+
+WidgetClass dviWidgetClass = (WidgetClass) &dviClassRec;
+
+static void ClassInitialize (void)
+{
+ int len1 = strlen(default_font_map_1);
+ int len2 = strlen(default_font_map_2);
+ int len3 = strlen(default_font_map_3);
+ char *dfm = XtMalloc(len1 + len2 + len3 + 1);
+ char *ptr = dfm;
+ strcpy(ptr, default_font_map_1); ptr += len1;
+ strcpy(ptr, default_font_map_2); ptr += len2;
+ strcpy(ptr, default_font_map_3);
+ resources[0].default_addr = dfm;
+
+ XtAddConverter( XtRString, XtRBackingStore, XmuCvtStringToBackingStore,
+ NULL, 0 );
+}
+
+/****************************************************************
+ *
+ * Private Procedures
+ *
+ ****************************************************************/
+
+/* ARGSUSED */
+static void Initialize(Widget request, Widget new_wd,
+ ArgList args, Cardinal *num_args)
+{
+ DviWidget dw = (DviWidget) new_wd;
+
+ dw->dvi.current_page = 0;
+ dw->dvi.font_map = 0;
+ dw->dvi.cache.index = 0;
+ dw->dvi.text_x_width = 0;
+ dw->dvi.text_device_width = 0;
+ dw->dvi.word_flag = 0;
+ dw->dvi.file = 0;
+ dw->dvi.tmpFile = 0;
+ dw->dvi.state = 0;
+ dw->dvi.readingTmp = 0;
+ dw->dvi.cache.char_index = 0;
+ dw->dvi.cache.font_size = -1;
+ dw->dvi.cache.font_number = -1;
+ dw->dvi.cache.adjustable[0] = 0;
+ dw->dvi.file_map = 0;
+ dw->dvi.fonts = 0;
+ dw->dvi.seek = False;
+ dw->dvi.device_resolution = dw->dvi.default_resolution;
+ dw->dvi.display_resolution = dw->dvi.default_resolution;
+ dw->dvi.paperlength = dw->dvi.default_resolution*11;
+ dw->dvi.paperwidth = (dw->dvi.default_resolution*8
+ + dw->dvi.default_resolution/2);
+ dw->dvi.scale_factor = 1.0;
+ dw->dvi.sizescale = 1;
+ dw->dvi.line_thickness = -1;
+ dw->dvi.line_width = 1;
+ dw->dvi.fill = DVI_FILL_MAX;
+ dw->dvi.device_font = 0;
+ dw->dvi.device_font_number = -1;
+ dw->dvi.device = 0;
+ dw->dvi.native = 0;
+
+ request = request; /* unused; suppress compiler warning */
+ args = args;
+ num_args = num_args;
+}
+
+#include "gray1.bm"
+#include "gray2.bm"
+#include "gray3.bm"
+#include "gray4.bm"
+#include "gray5.bm"
+#include "gray6.bm"
+#include "gray7.bm"
+#include "gray8.bm"
+
+static void
+Realize (Widget w, XtValueMask *valueMask, XSetWindowAttributes *attrs)
+{
+ DviWidget dw = (DviWidget) w;
+ XGCValues values;
+
+ if (dw->dvi.backing_store != Always + WhenMapped + NotUseful) {
+ attrs->backing_store = dw->dvi.backing_store;
+ *valueMask |= CWBackingStore;
+ }
+ XtCreateWindow (w, (unsigned)InputOutput, (Visual *) CopyFromParent,
+ *valueMask, attrs);
+ values.foreground = dw->dvi.foreground;
+ values.cap_style = CapRound;
+ values.join_style = JoinRound;
+ values.line_width = dw->dvi.line_width;
+ dw->dvi.normal_GC = XCreateGC (XtDisplay (w), XtWindow (w),
+ GCForeground|GCCapStyle|GCJoinStyle
+ |GCLineWidth,
+ &values);
+ dw->dvi.gray[0] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray1_bits,
+ gray1_width, gray1_height);
+ dw->dvi.gray[1] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray2_bits,
+ gray2_width, gray2_height);
+ dw->dvi.gray[2] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray3_bits,
+ gray3_width, gray3_height);
+ dw->dvi.gray[3] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray4_bits,
+ gray4_width, gray4_height);
+ dw->dvi.gray[4] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray5_bits,
+ gray5_width, gray5_height);
+ dw->dvi.gray[5] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray6_bits,
+ gray6_width, gray6_height);
+ dw->dvi.gray[6] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray7_bits,
+ gray7_width, gray7_height);
+ dw->dvi.gray[7] = XCreateBitmapFromData(XtDisplay (w), XtWindow (w),
+ gray8_bits,
+ gray8_width, gray8_height);
+ values.background = dw->dvi.background;
+ values.stipple = dw->dvi.gray[5];
+ dw->dvi.fill_GC = XCreateGC (XtDisplay (w), XtWindow (w),
+ GCForeground|GCBackground|GCStipple,
+ &values);
+
+ dw->dvi.fill_type = 9;
+
+ if (dw->dvi.file)
+ OpenFile (dw);
+ ParseFontMap (dw);
+}
+
+static void
+Destroy(Widget w)
+{
+ DviWidget dw = (DviWidget) w;
+
+ XFreeGC (XtDisplay (w), dw->dvi.normal_GC);
+ XFreeGC (XtDisplay (w), dw->dvi.fill_GC);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[0]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[1]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[2]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[3]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[4]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[5]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[6]);
+ XFreePixmap (XtDisplay (w), dw->dvi.gray[7]);
+ DestroyFontMap (dw->dvi.font_map);
+ DestroyFileMap (dw->dvi.file_map);
+ device_destroy (dw->dvi.device);
+}
+
+/*
+ * Repaint the widget window
+ */
+
+/* ARGSUSED */
+static void
+Redisplay(Widget w, XEvent *event, Region region)
+{
+ DviWidget dw = (DviWidget) w;
+ XRectangle extents;
+
+ XClipBox (region, &extents);
+ dw->dvi.extents.x1 = extents.x;
+ dw->dvi.extents.y1 = extents.y;
+ dw->dvi.extents.x2 = extents.x + extents.width;
+ dw->dvi.extents.y2 = extents.y + extents.height;
+ ShowDvi (dw);
+
+ event = event; /* unused; suppress compiler warning */
+}
+
+/*
+ * Set specified arguments into widget
+ */
+/* ARGSUSED */
+static Boolean
+SetValues (Widget wcurrent, Widget wrequest, Widget wnew,
+ ArgList args, Cardinal *num_args)
+{
+ Boolean redisplay = FALSE;
+ char *new_map;
+ int cur, req;
+ DviWidget current = (DviWidget)wcurrent;
+ DviWidget request = (DviWidget)wrequest;
+ DviWidget new_wd = (DviWidget)wnew;
+
+ if (current->dvi.font_map_string != request->dvi.font_map_string) {
+ new_map = XtMalloc (strlen (request->dvi.font_map_string) + 1);
+ if (new_map) {
+ redisplay = TRUE;
+ strcpy (new_map, request->dvi.font_map_string);
+ new_wd->dvi.font_map_string = new_map;
+ if (current->dvi.font_map_string)
+ XtFree (current->dvi.font_map_string);
+ current->dvi.font_map_string = 0;
+ ParseFontMap (new_wd);
+ }
+ }
+
+ req = request->dvi.requested_page;
+ cur = current->dvi.requested_page;
+ if (cur != req) {
+ if (!request->dvi.file)
+ req = 0;
+ else {
+ if (req < 1)
+ req = 1;
+ if (current->dvi.last_page != 0 &&
+ req > current->dvi.last_page)
+ req = current->dvi.last_page;
+ }
+ if (cur != req)
+ redisplay = TRUE;
+ new_wd->dvi.requested_page = req;
+ if (current->dvi.last_page == 0 && req > cur)
+ FindPage (new_wd);
+ }
+
+ args = args; /* unused; suppress compiler warning */
+ num_args = num_args;
+
+ return redisplay;
+}
+
+/*
+ * use the set_values_hook entry to check when
+ * the file is set
+ */
+
+static Boolean
+SetValuesHook (Widget wdw, ArgList args, Cardinal *num_argsp)
+{
+ Cardinal i;
+ DviWidget dw = (DviWidget)wdw;
+
+ for (i = 0; i < *num_argsp; i++) {
+ if (!strcmp (args[i].name, XtNfile)) {
+ CloseFile (dw);
+ OpenFile (dw);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void CloseFile (DviWidget dw)
+{
+ if (dw->dvi.tmpFile)
+ fclose (dw->dvi.tmpFile);
+ ForgetPagePositions (dw);
+}
+
+static void OpenFile (DviWidget dw)
+{
+ dw->dvi.tmpFile = 0;
+ if (!dw->dvi.seek)
+ dw->dvi.tmpFile = tmpfile();
+ dw->dvi.requested_page = 1;
+ dw->dvi.last_page = 0;
+}
+
+static XtGeometryResult
+QueryGeometry (Widget w, XtWidgetGeometry *request,
+ XtWidgetGeometry *geometry_return)
+{
+ XtGeometryResult ret;
+ DviWidget dw = (DviWidget) w;
+
+ ret = XtGeometryYes;
+ if (((request->request_mode & CWWidth)
+ && request->width < MY_WIDTH(dw))
+ || ((request->request_mode & CWHeight)
+ && request->height < MY_HEIGHT(dw)))
+ ret = XtGeometryAlmost;
+ geometry_return->width = MY_WIDTH(dw);
+ geometry_return->height = MY_HEIGHT(dw);
+ geometry_return->request_mode = CWWidth|CWHeight;
+ return ret;
+}
+
+void
+SetDevice (DviWidget dw, const char *name)
+{
+ XtWidgetGeometry request, reply;
+ XtGeometryResult ret;
+
+ ForgetFonts (dw);
+ dw->dvi.device = device_load (name);
+ if (!dw->dvi.device)
+ return;
+ dw->dvi.sizescale = dw->dvi.device->sizescale;
+ dw->dvi.device_resolution = dw->dvi.device->res;
+ dw->dvi.native = dw->dvi.device->X11;
+ dw->dvi.paperlength = dw->dvi.device->paperlength;
+ dw->dvi.paperwidth = dw->dvi.device->paperwidth;
+ if (dw->dvi.native) {
+ dw->dvi.display_resolution = dw->dvi.device_resolution;
+ dw->dvi.scale_factor = 1.0;
+ }
+ else {
+ dw->dvi.display_resolution = dw->dvi.default_resolution;
+ dw->dvi.scale_factor = ((double)dw->dvi.display_resolution
+ / dw->dvi.device_resolution);
+ }
+ request.request_mode = CWWidth|CWHeight;
+ request.width = MY_WIDTH(dw);
+ request.height = MY_HEIGHT(dw);
+ ret = XtMakeGeometryRequest ((Widget)dw, &request, &reply);
+ if (ret == XtGeometryAlmost
+ && reply.height >= request.height
+ && reply.width >= request.width) {
+ request.width = reply.width;
+ request.height = reply.height;
+ XtMakeGeometryRequest ((Widget)dw, &request, &reply);
+ }
+}
+
+static void
+ShowDvi (DviWidget dw)
+{
+ if (!dw->dvi.file) {
+ static char Error[] = "No file selected";
+
+ XSetFont (XtDisplay(dw), dw->dvi.normal_GC,
+ dw->dvi.default_font->fid);
+ XDrawString (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ 20, 20, Error, strlen (Error));
+ return;
+ }
+
+ FindPage (dw);
+
+ dw->dvi.display_enable = 1;
+ ParseInput (dw);
+ if (dw->dvi.last_page && dw->dvi.requested_page > dw->dvi.last_page)
+ dw->dvi.requested_page = dw->dvi.last_page;
+}
+
+static void
+FindPage (DviWidget dw)
+{
+ int i;
+ long file_position;
+
+ if (dw->dvi.requested_page < 1)
+ dw->dvi.requested_page = 1;
+
+ if (dw->dvi.last_page != 0 && dw->dvi.requested_page > dw->dvi.last_page)
+ dw->dvi.requested_page = dw->dvi.last_page;
+
+ file_position = SearchPagePosition (dw, dw->dvi.requested_page);
+ if (file_position != -1) {
+ FileSeek(dw, file_position);
+ dw->dvi.current_page = dw->dvi.requested_page;
+ } else {
+ for (i=dw->dvi.requested_page; i > 0; i--) {
+ file_position = SearchPagePosition (dw, i);
+ if (file_position != -1)
+ break;
+ }
+ if (file_position == -1)
+ file_position = 0;
+ FileSeek (dw, file_position);
+
+ dw->dvi.current_page = i;
+
+ dw->dvi.display_enable = 0;
+ while (dw->dvi.current_page != dw->dvi.requested_page) {
+ dw->dvi.current_page = ParseInput (dw);
+ /*
+ * at EOF, seek back to the beginning of this page.
+ */
+ if (!dw->dvi.readingTmp && feof (dw->dvi.file)) {
+ file_position = SearchPagePosition (dw,
+ dw->dvi.current_page);
+ if (file_position != -1)
+ FileSeek (dw, file_position);
+ dw->dvi.requested_page = dw->dvi.current_page;
+ break;
+ }
+ }
+ }
+}
+
+void DviSaveToFile(Widget w, FILE *fp)
+{
+ XtCheckSubclass(w, dviWidgetClass, NULL);
+ (*((DviWidgetClass) XtClass(w))->command_class.save)(w, fp);
+}
+
+static
+void SaveToFile(Widget w, FILE *fp)
+{
+ DviWidget dw = (DviWidget)w;
+ long pos;
+ int c;
+
+ if (dw->dvi.tmpFile) {
+ pos = ftell(dw->dvi.tmpFile);
+ if (dw->dvi.ungot) {
+ pos--;
+ dw->dvi.ungot = 0;
+ /* The ungot character is in the tmpFile, so we don't
+ want to read it from file. */
+ (void)getc(dw->dvi.file);
+ }
+ }
+ else
+ pos = ftell(dw->dvi.file);
+ FileSeek(dw, 0L);
+ while (DviGetC(dw, &c) != EOF)
+ if (putc(c, fp) == EOF) {
+ /* XXX print error message */
+ break;
+ }
+ FileSeek(dw, pos);
+}
+
+static
+void ClassPartInitialize(WidgetClass widget_class)
+{
+ DviWidgetClass wc = (DviWidgetClass)widget_class;
+ DviWidgetClass super = (DviWidgetClass) wc->core_class.superclass;
+ if (wc->command_class.save == InheritSaveToFile)
+ wc->command_class.save = super->command_class.save;
+}
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/Dvi.h b/src/devices/xditview/Dvi.h
new file mode 100644
index 0000000..bf97374
--- /dev/null
+++ b/src/devices/xditview/Dvi.h
@@ -0,0 +1,46 @@
+/*
+* $XConsortium: Dvi.h,v 1.4 89/07/21 14:22:06 jim Exp $
+*/
+
+#ifndef _XtDvi_h
+#define _XtDvi_h
+
+/***********************************************************************
+ *
+ * Dvi Widget
+ *
+ ***********************************************************************/
+
+/* Parameters:
+
+ Name Class RepType Default Value
+ ---- ----- ------- -------------
+ background Background pixel White
+ foreground Foreground Pixel Black
+ fontMap FontMap char * ...
+ pageNumber PageNumber int 1
+*/
+
+#define XtNfontMap (String)"fontMap"
+#define XtNpageNumber (String)"pageNumber"
+#define XtNlastPageNumber (String)"lastPageNumber"
+#define XtNnoPolyText (String)"noPolyText"
+#define XtNseek (String)"seek"
+#define XtNresolution (String)"resolution"
+
+#define XtCFontMap (String)"FontMap"
+#define XtCPageNumber (String)"PageNumber"
+#define XtCLastPageNumber (String)"LastPageNumber"
+#define XtCNoPolyText (String)"NoPolyText"
+#define XtCSeek (String)"Seek"
+#define XtCResolution (String)"Resolution"
+
+typedef struct _DviRec *DviWidget; /* completely defined in DviP.h */
+typedef struct _DviClassRec *DviWidgetClass; /* completely defined in DviP.h */
+
+extern WidgetClass dviWidgetClass;
+
+void DviSaveToFile(Widget, FILE *);
+
+#endif /* _XtDvi_h */
+/* DON'T ADD STUFF AFTER THIS #endif */
diff --git a/src/devices/xditview/DviP.h b/src/devices/xditview/DviP.h
new file mode 100644
index 0000000..8b71c69
--- /dev/null
+++ b/src/devices/xditview/DviP.h
@@ -0,0 +1,235 @@
+/*
+ * $XConsortium: DviP.h,v 1.5 89/07/22 19:44:08 keith Exp $
+ */
+
+/*
+ * DviP.h - Private definitions for Dvi widget
+ */
+
+#ifndef _XtDviP_h
+#define _XtDviP_h
+
+#include "Dvi.h"
+#include "DviChar.h"
+#include "device.h"
+
+/***********************************************************************
+ *
+ * Dvi Widget Private Data
+ *
+ ***********************************************************************/
+
+/************************************
+ *
+ * Class structure
+ *
+ ***********************************/
+
+/* Type for save method. */
+
+typedef void (*DviSaveProc)(Widget, FILE *);
+
+/*
+ * New fields for the Dvi widget class record
+ */
+
+
+typedef struct _DviClass {
+ DviSaveProc save;
+} DviClassPart;
+
+/*
+ * Full class record declaration
+ */
+
+typedef struct _DviClassRec {
+ CoreClassPart core_class;
+ DviClassPart command_class;
+} DviClassRec;
+
+extern DviClassRec dviClassRec;
+
+/***************************************
+ *
+ * Instance (widget) structure
+ *
+ **************************************/
+
+/*
+ * a list of fonts we've used for this widget
+ */
+
+typedef struct _dviFontSizeList {
+ struct _dviFontSizeList *next;
+ int size;
+ char *x_name;
+ XFontStruct *font;
+ int doesnt_exist;
+} DviFontSizeList;
+
+typedef struct _dviFontList {
+ struct _dviFontList *next;
+ char *dvi_name;
+ char *x_name;
+ int dvi_number;
+ Boolean initialized;
+ Boolean scalable;
+ DviFontSizeList *sizes;
+ DviCharNameMap *char_map;
+ DeviceFont *device_font;
+} DviFontList;
+
+typedef struct _dviFontMap {
+ struct _dviFontMap *next;
+ char *dvi_name;
+ char *x_name;
+} DviFontMap;
+
+#define DVI_TEXT_CACHE_SIZE 256
+#define DVI_CHAR_CACHE_SIZE 1024
+
+typedef struct _dviCharCache {
+ XTextItem cache[DVI_TEXT_CACHE_SIZE];
+ char adjustable[DVI_TEXT_CACHE_SIZE];
+ char char_cache[DVI_CHAR_CACHE_SIZE];
+ int index;
+ int max;
+ int char_index;
+ int font_size;
+ int font_number;
+ XFontStruct *font;
+ int start_x, start_y;
+ int x, y;
+} DviCharCache;
+
+typedef struct _dviState {
+ struct _dviState *next;
+ int font_size;
+ int font_number;
+ int x;
+ int y;
+} DviState;
+
+typedef struct _dviFileMap {
+ struct _dviFileMap *next;
+ long position;
+ int page_number;
+} DviFileMap;
+
+/*
+ * New fields for the Dvi widget record
+ */
+
+typedef struct {
+ /*
+ * resource specifiable items
+ */
+ char *font_map_string;
+ unsigned long foreground;
+ unsigned long background;
+ int requested_page;
+ int last_page;
+ XFontStruct *default_font;
+ FILE *file;
+ Boolean noPolyText;
+ Boolean seek; /* file is 'seekable' */
+ int default_resolution;
+ /*
+ * private state
+ */
+ FILE *tmpFile; /* used when reading stdin */
+ char readingTmp; /* reading now from tmp */
+ char ungot; /* have ungetc'd a char */
+ GC normal_GC;
+ GC fill_GC;
+ DviFileMap *file_map;
+ DviFontList *fonts;
+ DviFontMap *font_map;
+ int current_page;
+ int font_size;
+ int font_number;
+ DeviceFont *device_font;
+ int device_font_number;
+ Device *device;
+ int native;
+ int device_resolution;
+ int display_resolution;
+ int paperlength;
+ int paperwidth;
+ double scale_factor; /* display res / device res */
+ int sizescale;
+ int line_thickness;
+ int line_width;
+
+#define DVI_FILL_MAX 1000
+
+ int fill;
+#define DVI_FILL_WHITE 0
+#define DVI_FILL_GRAY 1
+#define DVI_FILL_BLACK 2
+ int fill_type;
+ Pixmap gray[8];
+ int backing_store;
+ XFontStruct *font;
+ int display_enable;
+ struct ExposedExtents {
+ int x1, y1, x2, y2;
+ } extents;
+ DviState *state;
+ DviCharCache cache;
+ int text_x_width;
+ int text_device_width;
+ int word_flag;
+} DviPart;
+
+int DviGetAndPut(DviWidget, int *);
+#define DviGetIn(dw,cp)\
+ (dw->dvi.tmpFile ? (\
+ DviGetAndPut (dw, cp) \
+ ) :\
+ (*cp = getc (dw->dvi.file))\
+)
+
+#define DviGetC(dw, cp)\
+ (dw->dvi.readingTmp ? (\
+ ((*cp = getc (dw->dvi.tmpFile)) == EOF) ? (\
+ fseek (dw->dvi.tmpFile, 0l, 2),\
+ (dw->dvi.readingTmp = 0),\
+ DviGetIn (dw,cp)\
+ ) : (\
+ *cp\
+ )\
+ ) : (\
+ DviGetIn(dw,cp)\
+ )\
+)
+
+#define DviUngetC(dw, c)\
+ (dw->dvi.readingTmp ? (\
+ ungetc (c, dw->dvi.tmpFile)\
+ ) : ( \
+ (dw->dvi.ungot = 1),\
+ ungetc (c, dw->dvi.file)))
+
+/*
+ * Full widget declaration
+ */
+
+typedef struct _DviRec {
+ CorePart core;
+ DviPart dvi;
+} DviRec;
+
+#define InheritSaveToFile ((DviSaveProc)_XtInherit)
+
+XFontStruct *QueryFont (DviWidget, int, int);
+
+DviCharNameMap *QueryFontMap (DviWidget, int);
+
+DeviceFont *QueryDeviceFont (DviWidget, int);
+
+char *GetWord(DviWidget, char *, int);
+char *GetLine(DviWidget, char *, int);
+
+void SetDevice (DviWidget dw, const char *name);
+#endif /* _XtDviP_h */
diff --git a/src/devices/xditview/FontMap-X11 b/src/devices/xditview/FontMap-X11
new file mode 100644
index 0000000..90911f0
--- /dev/null
+++ b/src/devices/xditview/FontMap-X11
@@ -0,0 +1,17 @@
+TR -adobe-times-medium-r-normal--*-*-*-*-p-*-iso8859-1
+TI -adobe-times-medium-i-normal--*-*-*-*-p-*-iso8859-1
+TB -adobe-times-bold-r-normal--*-*-*-*-p-*-iso8859-1
+TBI -adobe-times-bold-i-normal--*-*-*-*-p-*-iso8859-1
+CR -adobe-courier-medium-r-normal--*-*-*-*-m-*-iso8859-1
+CI -adobe-courier-medium-o-normal--*-*-*-*-m-*-iso8859-1
+CB -adobe-courier-bold-r-normal--*-*-*-*-m-*-iso8859-1
+CBI -adobe-courier-bold-o-normal--*-*-*-*-m-*-iso8859-1
+HR -adobe-helvetica-medium-r-normal--*-*-*-*-p-*-iso8859-1
+HI -adobe-helvetica-medium-o-normal--*-*-*-*-p-*-iso8859-1
+HB -adobe-helvetica-bold-r-normal--*-*-*-*-p-*-iso8859-1
+HBI -adobe-helvetica-bold-o-normal--*-*-*-*-p-*-iso8859-1
+NR -adobe-new century schoolbook-medium-r-normal--*-*-*-*-p-*-iso8859-1
+NI -adobe-new century schoolbook-medium-i-normal--*-*-*-*-p-*-iso8859-1
+NB -adobe-new century schoolbook-bold-r-normal--*-*-*-*-p-*-iso8859-1
+NBI -adobe-new century schoolbook-bold-i-normal--*-*-*-*-p-*-iso8859-1
+S -adobe-symbol-medium-r-normal--*-*-*-*-p-*-adobe-fontspecific
diff --git a/src/devices/xditview/GXditview-color.ad b/src/devices/xditview/GXditview-color.ad
new file mode 100644
index 0000000..61abd8f
--- /dev/null
+++ b/src/devices/xditview/GXditview-color.ad
@@ -0,0 +1,15 @@
+
+#include "GXditview"
+
+GXditview.paned.viewport.Scrollbar.background: Thistle
+GXditview.paned.viewport.Scrollbar.foreground: Orchid
+GXditview.paned.viewport.Scrollbar.thumb: None
+GXditview.paned.viewport.dvi.background: LemonChiffon
+GXditview.paned.viewport.background: Thistle
+GXditview.paned.label.background: PeachPuff
+GXditview.menu.background: Gold
+GXditview.promptShell.promptDialog*background: Gold
+GXditview.promptShell.promptDialog.accept.background: DarkOliveGreen1
+GXditview.promptShell.promptDialog.cancel.background: RosyBrown1
+GXditview.promptShell.promptDialog.value.background: Khaki
+
diff --git a/src/devices/xditview/GXditview.ad b/src/devices/xditview/GXditview.ad
new file mode 100644
index 0000000..549f0d7
--- /dev/null
+++ b/src/devices/xditview/GXditview.ad
@@ -0,0 +1,71 @@
+
+GXditview*shapeStyle: rectangle
+
+GXditview.paned.allowResize: true
+GXditview.paned.label.skipAdjust: true
+GXditview.paned.viewport.skipAdjust: false
+GXditview.paned.viewport.showGrip: false
+GXditview.paned.viewport.allowVert: true
+GXditview.paned.viewport.allowHoriz: true
+GXditview.paned.viewport.forceBars: true
+! viewport size = papersize * resol + scrollbarthickness + 1
+! letter size paper
+!GXditview.paned.viewport.width: 652
+!GXditview.paned.viewport.height: 840
+! a4 size paper
+! 865 is wide enough for a page of X100-12.
+GXditview.paned.viewport.width: 865
+GXditview.paned.viewport.height: 892
+GXditview.paned.viewport.Scrollbar.thickness: 14
+
+GXditview.geometry: 865x892
+
+GXditview.paned.translations: #augment \
+ <Key>Next: NextPage()\n\
+ <Key>n: NextPage()\n\
+ <Key>space: NextPage()\n\
+ <Key>Return: NextPage()\n\
+ <Key>Prior: PreviousPage()\n\
+ <Key>p: PreviousPage()\n\
+ <Key>b: PreviousPage()\n\
+ <Key>BackSpace: PreviousPage()\n\
+ <Key>Delete: PreviousPage()\n\
+ <Key>g: SelectPage()\n\
+ <Key>o: OpenFile()\n\
+ <Key>r: Rerasterize()\n\
+ <Key>q: Quit()
+GXditview.paned.viewport.clip.translations: #augment \
+ <Btn1Down>: XawPositionSimpleMenu(menu) MenuPopup(menu)
+GXditview.paned.label.translations: #augment \
+ <Btn1Down>: XawPositionSimpleMenu(menu) MenuPopup(menu)
+GXditview.paned.viewport.vertical.accelerators: #override \
+ <Key>k: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>j: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Up: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Down: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()
+GXditview.paned.viewport.horizontal.accelerators: #override \
+ <Key>h: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>l: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Left: StartScroll(Backward) NotifyScroll(FullLength) EndScroll()\n\
+ <Key>Right: StartScroll(Forward) NotifyScroll(FullLength) EndScroll()
+
+GXditview.menu.nextPage.label: Next Page
+GXditview.menu.previousPage.label: Previous Page
+GXditview.menu.selectPage.label: Goto Page
+GXditview.menu.print.label: Print
+GXditview.menu.openFile.label: Open
+GXditview.menu.quit.label: Quit
+
+GXditview.promptShell.allowShellResize: true
+GXditview.promptShell.promptDialog.value.translations: #override \
+ <Key>Return: Accept() \n\
+ <Key>Escape: Cancel()
+
+GXditview.promptShell.promptDialog.accept.label: Accept
+GXditview.promptShell.promptDialog.accept.translations: #override \
+ <BtnUp>: Accept() unset()
+
+GXditview.promptShell.promptDialog.cancel.label: Cancel
+GXditview.promptShell.promptDialog.cancel.translations: #override \
+ <BtnUp>: Cancel() unset()
+
diff --git a/src/devices/xditview/Menu.h b/src/devices/xditview/Menu.h
new file mode 100644
index 0000000..c306b27
--- /dev/null
+++ b/src/devices/xditview/Menu.h
@@ -0,0 +1,46 @@
+/*
+ * $XConsortium: Menu.h,v 1.2 89/07/21 14:22:10 jim Exp $
+ */
+
+#ifndef _XtMenu_h
+#define _XtMenu_h
+
+/***********************************************************************
+ *
+ * Menu Widget
+ *
+ ***********************************************************************/
+
+/* Parameters:
+
+ Name Class RepType Default Value
+ ---- ----- ------- -------------
+ background Background pixel White
+ border BorderColor pixel Black
+ borderWidth BorderWidth int 1
+ height Height int 120
+ mappedWhenManaged MappedWhenManaged Boolean True
+ reverseVideo ReverseVideo Boolean False
+ width Width int 120
+ x Position int 0
+ y Position int 0
+
+*/
+
+#define XtNmenuEntries "menuEntries"
+#define XtNhorizontalPadding "horizontalPadding"
+#define XtNverticalPadding "verticalPadding"
+#define XtNselection "Selection"
+
+#define XtCMenuEntries "MenuEntries"
+#define XtCPadding "Padding"
+#define XtCSelection "Selection"
+
+typedef struct _MenuRec *MenuWidget; /* completely defined in MenuPrivate.h */
+typedef struct _MenuClassRec *MenuWidgetClass; /* completely defined in MenuPrivate.h */
+
+extern WidgetClass menuWidgetClass;
+
+extern Widget XawMenuCreate ();
+#endif /* _XtMenu_h */
+/* DON'T ADD STUFF AFTER THIS #endif */
diff --git a/src/devices/xditview/README b/src/devices/xditview/README
new file mode 100644
index 0000000..9de7869
--- /dev/null
+++ b/src/devices/xditview/README
@@ -0,0 +1,13 @@
+This is gxditview, an X11 previewer for groff based on MIT's xditview.
+This version can be used with the output of gtroff -Tps as well as
+with -TX75 and -TX100. You will need X11R5 or newer to install it (it
+might work on X11R4, but I haven't tested it.)
+
+Previously, gxditview was installed in the usual place for X binaries
+(e.g., /usr/bin/X11); you have to remove it manually.
+
+xditview is copyrighted by MIT under the usual X terms (see
+gxditview.man); the changes to that which come along with the groff package
+are in the public domain.
+
+Please report bugs at http://savannah.gnu.org/bugs/?group=groff.
diff --git a/src/devices/xditview/TODO b/src/devices/xditview/TODO
new file mode 100644
index 0000000..0b5b140
--- /dev/null
+++ b/src/devices/xditview/TODO
@@ -0,0 +1,21 @@
+Open the main window with the correct width and height, depending on
+both the selected device and the paper dimensions.
+
+Add a command-line option to specify the paper dimensions (similar to
+other groff devices).
+
+Better error handling.
+
+Resource and command-line option to specify font path.
+
+Resource to specify name of environment variable from which to get the
+font path.
+
+Have character substitutions (currently done in draw.c:FakeCharacter)
+specified in a resource (similar format to FontMap).
+
+The initial width of the dialog box should expand to accommodate the
+default value.
+
+Option in Print dialog to specify that only the current page should be
+printed.
diff --git a/src/devices/xditview/ad2c b/src/devices/xditview/ad2c
new file mode 100644
index 0000000..2ec45ed
--- /dev/null
+++ b/src/devices/xditview/ad2c
@@ -0,0 +1,64 @@
+#! /bin/sh
+#
+# ad2c : Convert app-defaults file to C strings decls.
+#
+# George Ferguson, ferguson@cs.rcohester.edu, 12 Nov 1990.
+# 19 Mar 1991: gf
+# Made it self-contained.
+# 6 Jan 1992: mycroft@gnu.ai.mit.edu (Charles Hannum)
+# Removed use of "-n" and ":read" label since Gnu and
+# IBM sed print pattern space on "n" command. Still works
+# with Sun sed, of course.
+# 7 Jan 1992: matthew@sunpix.East.Sun.COM (Matthew Stier)
+# Escape quotes after escaping backslashes.
+# 8 Jul 1992: Version 1.6
+# Manpage fixes.
+# 19 Apr 1993: Version 1.7
+# Remove comments that were inside the sed command since
+# some versions of sed don't like them. The comments are
+# now given here in the header.
+# 31 May 2004: Werner Lemberg <wl@gnu.org>
+# Force casts to 'String'.
+#
+# Comments on the script by line:
+# /^!/d Remove comments
+# /^$/d Remove blanks
+# s/\\/\\\\/g Escape backslashes...
+# s/\\$//g ...except the line continuation ones
+# s/"/\\"/g Escape quotes
+# s/^/"/ Add leading quote and cast
+# : test Establish label for later branch
+# /\\$/b slash Branch to label "slash" if line ends in backslash
+# s/$/",/ Otherwise add closing quote and comma...
+# p ...output the line...
+# d ...and clear the pattern space so it's not printed again
+# : slash Branch comes here if line ends in backslash
+# n Read next line, append to pattern space
+# [...] The "d" and "s" commands that follow just delete
+# comments and blank lines and escape control sequences
+# b test Branch up to see if the line ends in backslash or not
+#
+
+sed '
+/^!/d
+/^$/d
+s/\\/\\\\/g
+s/\\$//g
+s/"/\\"/g
+s/^/(String)"/
+: test
+/\\$/b slash
+s/$/",/
+p
+d
+: slash
+n
+/^!/d
+/^$/d
+s/"/\\"/g
+s/\\\\/\\/g
+s/\\n/\\\\n/g
+s/\\t/\\\\t/g
+s/\\f/\\\\f/g
+s/\\b/\\\\b/g
+b test' "$@"
diff --git a/src/devices/xditview/device.c b/src/devices/xditview/device.c
new file mode 100644
index 0000000..1eaafbe
--- /dev/null
+++ b/src/devices/xditview/device.c
@@ -0,0 +1,565 @@
+/* device.c */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xos.h>
+#include <X11/Intrinsic.h>
+
+#include "device.h"
+#include "defs.h"
+
+#ifndef isascii
+#define isascii(c) (1)
+#endif
+
+/* Name of environment variable containing path to be used for
+searching for device and font description files. */
+#define FONTPATH_ENV_VAR "GROFF_FONT_PATH"
+
+#define WS " \t\r\n"
+
+#ifndef INT_MIN
+/* Minimum and maximum values a 'signed int' can hold. */
+#define INT_MIN (-INT_MAX-1)
+#define INT_MAX 2147483647
+#endif
+
+#define CHAR_TABLE_SIZE 307
+
+struct _DeviceFont {
+ char *name;
+ int special;
+ DeviceFont *next;
+ Device *dev;
+ struct charinfo *char_table[CHAR_TABLE_SIZE];
+ struct charinfo *code_table[256];
+};
+
+struct charinfo {
+ int width;
+ int code;
+ struct charinfo *next;
+ struct charinfo *code_next;
+ char name[1];
+};
+
+static char *current_filename = 0;
+static int current_lineno = -1;
+
+static void error(const char *s);
+static FILE *open_device_file(const char *, const char *, char **);
+static DeviceFont *load_font(Device *, const char *);
+static Device *new_device(const char *);
+static DeviceFont *new_font(const char *, Device *);
+static void delete_font(DeviceFont *);
+static unsigned hash_name(const char *);
+static struct charinfo *add_char(DeviceFont *, const char *, int, int);
+static int read_charset_section(DeviceFont *, FILE *);
+static char *canonicalize_name(const char *);
+static int scale_round(int, int, int);
+
+static
+Device *new_device(const char *name)
+{
+ Device *dev;
+
+ dev = XtNew(Device);
+ dev->sizescale = 1;
+ dev->res = 0;
+ dev->unitwidth = 0;
+ dev->fonts = 0;
+ dev->X11 = 0;
+ dev->paperlength = 0;
+ dev->paperwidth = 0;
+ dev->name = XtNewString(name);
+ return dev;
+}
+
+void device_destroy(Device *dev)
+{
+ DeviceFont *f;
+
+ if (!dev)
+ return;
+ f = dev->fonts;
+ while (f) {
+ DeviceFont *tem = f;
+ f = f->next;
+ delete_font(tem);
+ }
+
+ XtFree(dev->name);
+ XtFree((char *)dev);
+}
+
+Device *device_load(const char *name)
+{
+ Device *dev;
+ FILE *fp;
+ int err = 0;
+ char buf[256];
+
+ fp = open_device_file(name, "DESC", &current_filename);
+ if (!fp)
+ return 0;
+ dev = new_device(name);
+ current_lineno = 0;
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *p;
+ current_lineno++;
+ p = strtok(buf, WS);
+ if (p) {
+ int *np = 0;
+ char *q;
+
+ if (strcmp(p, "charset") == 0)
+ break;
+ if (strcmp(p, "X11") == 0)
+ dev->X11 = 1;
+ else if (strcmp(p, "sizescale") == 0)
+ np = &dev->sizescale;
+ else if (strcmp(p, "res") == 0)
+ np = &dev->res;
+ else if (strcmp(p, "unitwidth") == 0)
+ np = &dev->unitwidth;
+ else if (strcmp(p, "paperwidth") == 0)
+ np = &dev->paperwidth;
+ else if (strcmp(p, "paperlength") == 0)
+ np = &dev->paperlength;
+
+ if (np) {
+ q = strtok((char *)0, WS);
+ if (!q || sscanf(q, "%d", np) != 1 || *np <= 0) {
+ error("bad argument");
+ err = 1;
+ break;
+ }
+ }
+ }
+ }
+ fclose(fp);
+ current_lineno = -1;
+ if (!err) {
+ if (dev->res == 0) {
+ error("missing res line");
+ err = 1;
+ }
+ else if (dev->unitwidth == 0) {
+ error("missing unitwidth line");
+ err = 1;
+ }
+ }
+ if (dev->paperlength == 0)
+ dev->paperlength = dev->res*11;
+ if (dev->paperwidth == 0)
+ dev->paperwidth = dev->res*8 + dev->res/2;
+ if (err) {
+ device_destroy(dev);
+ dev = 0;
+ }
+ XtFree(current_filename);
+ current_filename = 0;
+ return dev;
+}
+
+
+DeviceFont *device_find_font(Device *dev, const char *name)
+{
+ DeviceFont *f;
+
+ if (!dev)
+ return 0;
+ for (f = dev->fonts; f; f = f->next)
+ if (strcmp(f->name, name) == 0)
+ return f;
+ return load_font(dev, name);
+}
+
+static
+DeviceFont *load_font(Device *dev, const char *name)
+{
+ FILE *fp;
+ char buf[256];
+ DeviceFont *f;
+ int special = 0;
+
+ fp = open_device_file(dev->name, name, &current_filename);
+ if (!fp)
+ return 0;
+ current_lineno = 0;
+ for (;;) {
+ char *p;
+
+ if (!fgets(buf, sizeof(buf), fp)) {
+ error("no charset line");
+ return 0;
+ }
+ current_lineno++;
+ p = strtok(buf, WS);
+ /* charset must be on a line by itself */
+ if (p && strcmp(p, "charset") == 0 && strtok((char *)0, WS) == 0)
+ break;
+ if (p && strcmp(p, "special") == 0)
+ special = 1;
+ }
+ f = new_font(name, dev);
+ f->special = special;
+ if (!read_charset_section(f, fp)) {
+ delete_font(f);
+ f = 0;
+ }
+ else {
+ f->next = dev->fonts;
+ dev->fonts = f;
+ }
+ fclose(fp);
+ XtFree(current_filename);
+ current_filename = 0;
+ return f;
+}
+
+static
+DeviceFont *new_font(const char *name, Device *dev)
+{
+ int i;
+ DeviceFont *f;
+
+ f = XtNew(DeviceFont);
+ f->name = XtNewString(name);
+ f->dev = dev;
+ f->special = 0;
+ f->next = 0;
+ for (i = 0; i < CHAR_TABLE_SIZE; i++)
+ f->char_table[i] = 0;
+ for (i = 0; i < 256; i++)
+ f->code_table[i] = 0;
+ return f;
+}
+
+static
+void delete_font(DeviceFont *f)
+{
+ int i;
+
+ if (!f)
+ return;
+ XtFree(f->name);
+ for (i = 0; i < CHAR_TABLE_SIZE; i++) {
+ struct charinfo *ptr = f->char_table[i];
+ while (ptr) {
+ struct charinfo *tem = ptr;
+ ptr = ptr->next;
+ XtFree((char *)tem);
+ }
+ }
+ XtFree((char *)f);
+}
+
+
+static
+unsigned hash_name(const char *name)
+{
+ unsigned n = 0;
+ /* XXX do better than this */
+ while (*name)
+ n = (n << 1) ^ *name++;
+
+ return n;
+}
+
+static
+int scale_round(int n, int x, int y)
+{
+ int y2;
+
+ if (x == 0)
+ return 0;
+ y2 = y/2;
+ if (n >= 0) {
+ if (n <= (INT_MAX - y2)/x)
+ return (n*x + y2)/y;
+ return (int)(n*(double)x/(double)y + .5);
+ }
+ else {
+ if (-(unsigned)n <= (-(unsigned)INT_MIN - y2)/x)
+ return (n*x - y2)/y;
+ return (int)(n*(double)x/(double)y + .5);
+ }
+}
+
+static
+char *canonicalize_name(const char *s)
+{
+ static char ch[2];
+ if (s[0] == 'c' && s[1] == 'h' && s[2] == 'a' && s[3] == 'r') {
+ const char *p;
+ int n;
+
+ for (p = s + 4; *p; p++)
+ if (!isascii(*p) || !isdigit((unsigned char)*p))
+ return (char *)s;
+ n = atoi(s + 4);
+ if (n >= 0 && n <= 0xff) {
+ ch[0] = (char)n;
+ return ch;
+ }
+ }
+ return (char *)s;
+}
+
+/* Return 1 if the character is present in the font; widthp gets the
+width if non-null. */
+
+int device_char_width(DeviceFont *f, int ps, const char *name, int *widthp)
+{
+ struct charinfo *p;
+
+ name = canonicalize_name(name);
+ for (p = f->char_table[hash_name(name) % CHAR_TABLE_SIZE];; p = p->next) {
+ if (!p)
+ return 0;
+ if (strcmp(p->name, name) == 0)
+ break;
+ }
+ *widthp = scale_round(p->width, ps, f->dev->unitwidth);
+ return 1;
+}
+
+int device_code_width(DeviceFont *f, int ps, int code, int *widthp)
+{
+ struct charinfo *p;
+
+ for (p = f->code_table[code & 0xff];; p = p->code_next) {
+ if (!p)
+ return 0;
+ if (p->code == code)
+ break;
+ }
+ *widthp = scale_round(p->width, ps, f->dev->unitwidth);
+ return 1;
+}
+
+char *device_name_for_code(DeviceFont *f, int code)
+{
+ static struct charinfo *state = 0;
+ if (f)
+ state = f->code_table[code & 0xff];
+ for (; state; state = state->code_next)
+ if (state->code == code && state->name[0] != '\0') {
+ char *name = state->name;
+ state = state->code_next;
+ return name;
+ }
+ return 0;
+}
+
+int device_font_special(DeviceFont *f)
+{
+ return f->special;
+}
+
+static
+struct charinfo *add_char(DeviceFont *f, const char *name, int width, int code)
+{
+ struct charinfo **pp;
+ struct charinfo *ci;
+
+ name = canonicalize_name(name);
+ if (strcmp(name, "---") == 0)
+ name = "";
+
+ ci = (struct charinfo *)XtMalloc(XtOffsetOf(struct charinfo, name[0])
+ + strlen(name) + 1);
+
+ strcpy(ci->name, name);
+ ci->width = width;
+ ci->code = code;
+
+ if (*name != '\0') {
+ pp = &f->char_table[hash_name(name) % CHAR_TABLE_SIZE];
+ ci->next = *pp;
+ *pp = ci;
+ }
+ pp = &f->code_table[code & 0xff];
+ ci->code_next = *pp;
+ *pp = ci;
+ return ci;
+}
+
+/* Return non-zero for success. */
+
+static
+int read_charset_section(DeviceFont *f, FILE *fp)
+{
+ struct charinfo *last_charinfo = 0;
+ char buf[256];
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ char *name;
+ int width;
+ int code;
+ char *p;
+
+ current_lineno++;
+ name = strtok(buf, WS);
+ if (!name)
+ continue; /* ignore blank lines */
+ p = strtok((char *)0, WS);
+ if (!p) /* end of charset section */
+ break;
+ if (strcmp(p, "\"") == 0) {
+ if (!last_charinfo) {
+ error("first line of charset section cannot use '\"'");
+ return 0;
+ }
+ else
+ (void)add_char(f, name,
+ last_charinfo->width, last_charinfo->code);
+ }
+ else {
+ char *q;
+ if (sscanf(p, "%d", &width) != 1) {
+ error("bad width field");
+ return 0;
+ }
+ p = strtok((char *)0, WS);
+ if (!p) {
+ error("missing type field");
+ return 0;
+ }
+ p = strtok((char *)0, WS);
+ if (!p) {
+ error("missing code field");
+ return 0;
+ }
+ code = (int)strtol(p, &q, 0);
+ if (q == p) {
+ error("bad code field");
+ return 0;
+ }
+ last_charinfo = add_char(f, name, width, code);
+ }
+ }
+ return 1;
+}
+
+static
+FILE *find_file(const char *file, char **result)
+{
+ char *buf = NULL;
+ int bufsiz = 0;
+ int flen;
+ FILE *fp;
+ char *path;
+ char *env;
+
+ env = getenv(FONTPATH_ENV_VAR);
+ path = XtMalloc(((env && *env) ? strlen(env) + 1 : 0)
+ + strlen(FONTPATH) + 1);
+ *path = '\0';
+ if (env && *env) {
+ strcat(path, env);
+ strcat(path, ":");
+ }
+ strcat(path, FONTPATH);
+
+ *result = NULL;
+
+ if (file == NULL)
+ return NULL;
+ if (*file == '\0')
+ return NULL;
+
+ if (*file == '/') {
+ fp = fopen(file, "r");
+ if (fp)
+ *result = XtNewString(file);
+ return fp;
+ }
+
+ flen = strlen(file);
+
+ while (*path) {
+ int len;
+ char *start, *end;
+
+ start = path;
+ end = strchr(path, ':');
+ if (end)
+ path = end + 1;
+ else
+ path = end = strchr(path, '\0');
+ if (start >= end)
+ continue;
+ if (end[-1] == '/')
+ --end;
+ len = (end - start) + 1 + flen + 1;
+ if (len > bufsiz) {
+ if (buf)
+ buf = XtRealloc(buf, len);
+ else
+ buf = XtMalloc(len);
+ bufsiz = len;
+ }
+ memcpy(buf, start, end - start);
+ buf[end - start] = '/';
+ strcpy(buf + (end - start) + 1, file);
+ fp = fopen(buf, "r");
+ if (fp) {
+ *result = buf;
+ return fp;
+ }
+ }
+ XtFree(buf);
+ return NULL;
+}
+
+static
+FILE *open_device_file(const char *device_name, const char *file_name,
+ char **result)
+{
+ char *buf;
+ FILE *fp;
+
+ buf = XtMalloc(3 + strlen(device_name) + 1 + strlen(file_name) + 1);
+ sprintf(buf, "dev%s/%s", device_name, file_name);
+ fp = find_file(buf, result);
+ if (!fp) {
+ fprintf(stderr, "can't find device file '%s'\n", file_name);
+ fflush(stderr);
+ }
+ XtFree(buf);
+ return fp;
+}
+
+static
+void error(const char *s)
+{
+ if (current_filename) {
+ fprintf(stderr, "%s:", current_filename);
+ if (current_lineno > 0)
+ fprintf(stderr, "%d:", current_lineno);
+ putc(' ', stderr);
+ }
+ fputs(s, stderr);
+ putc('\n', stderr);
+ fflush(stderr);
+}
+
+/*
+Local Variables:
+c-indent-level: 4
+c-continued-statement-offset: 4
+c-brace-offset: -4
+c-argdecl-indent: 4
+c-label-offset: -4
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/device.h b/src/devices/xditview/device.h
new file mode 100644
index 0000000..6f2944b
--- /dev/null
+++ b/src/devices/xditview/device.h
@@ -0,0 +1,21 @@
+
+typedef struct _DeviceFont DeviceFont;
+
+typedef struct _Device {
+ char *name;
+ int sizescale;
+ int res;
+ int unitwidth;
+ int paperlength;
+ int paperwidth;
+ int X11;
+ DeviceFont *fonts;
+} Device;
+
+void device_destroy(Device *);
+Device *device_load(const char *);
+DeviceFont *device_find_font(Device *, const char *);
+int device_char_width(DeviceFont *, int, const char *, int *);
+char *device_name_for_code(DeviceFont *, int);
+int device_code_width(DeviceFont *, int, int, int *);
+int device_font_special(DeviceFont *);
diff --git a/src/devices/xditview/draw.c b/src/devices/xditview/draw.c
new file mode 100644
index 0000000..288d98a
--- /dev/null
+++ b/src/devices/xditview/draw.c
@@ -0,0 +1,709 @@
+/*
+ * draw.c
+ *
+ * accept dvi function calls and translate to X
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <math.h>
+
+/* math.h on a Sequent doesn't define M_PI, apparently */
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#include "DviP.h"
+#include "draw.h"
+#include "font.h"
+
+#define DeviceToX(dw, n) ((int)((n) * (dw)->dvi.scale_factor + .5))
+#define XPos(dw) (DeviceToX((dw), (dw)->dvi.state->x - \
+ (dw)->dvi.text_device_width) + (dw)->dvi.text_x_width)
+#define YPos(dw) (DeviceToX((dw), (dw)->dvi.state->y))
+
+/* forward reference */
+static int FakeCharacter(DviWidget, char *, int);
+
+/* shadowed by a macro definition in parse.c, and unused elsewhere */
+#if 0
+static void
+HorizontalMove(DviWidget dw, int delta)
+{
+ dw->dvi.state->x += delta;
+}
+#endif
+
+void
+HorizontalGoto(DviWidget dw, int NewPosition)
+{
+ dw->dvi.state->x = NewPosition;
+}
+
+void
+VerticalMove(DviWidget dw, int delta)
+{
+ dw->dvi.state->y += delta;
+}
+
+void
+VerticalGoto(DviWidget dw, int NewPosition)
+{
+ dw->dvi.state->y = NewPosition;
+}
+
+static void
+AdjustCacheDeltas (DviWidget dw)
+{
+ int extra;
+ int nadj;
+ int i;
+
+ nadj = 0;
+ extra = DeviceToX(dw, dw->dvi.text_device_width)
+ - dw->dvi.text_x_width;
+ if (extra == 0)
+ return;
+ for (i = 0; i <= dw->dvi.cache.index; i++)
+ if (dw->dvi.cache.adjustable[i])
+ ++nadj;
+ dw->dvi.text_x_width += extra;
+ if (nadj <= 1)
+ return;
+ for (i = 0; i <= dw->dvi.cache.index; i++)
+ if (dw->dvi.cache.adjustable[i]) {
+ int x;
+ int *deltap;
+
+ x = extra/nadj;
+ deltap = &dw->dvi.cache.cache[i].delta;
+#define MIN_DELTA 2
+ if (*deltap > 0 && x + *deltap < MIN_DELTA) {
+ x = MIN_DELTA - *deltap;
+ if (x <= 0)
+ *deltap = MIN_DELTA;
+ else
+ x = 0;
+ }
+ else
+ *deltap += x;
+ extra -= x;
+ --nadj;
+ dw->dvi.cache.adjustable[i] = 0;
+ }
+}
+
+void
+FlushCharCache (DviWidget dw)
+{
+ if (dw->dvi.cache.char_index != 0) {
+ AdjustCacheDeltas (dw);
+ XDrawText (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ dw->dvi.cache.start_x, dw->dvi.cache.start_y,
+ dw->dvi.cache.cache, dw->dvi.cache.index + 1);
+ }
+ dw->dvi.cache.index = 0;
+ dw->dvi.cache.max = DVI_TEXT_CACHE_SIZE;
+#if 0
+ if (dw->dvi.noPolyText)
+ dw->dvi.cache.max = 1;
+#endif
+ dw->dvi.cache.char_index = 0;
+ dw->dvi.cache.cache[0].nchars = 0;
+ dw->dvi.cache.start_x = dw->dvi.cache.x = XPos (dw);
+ dw->dvi.cache.start_y = dw->dvi.cache.y = YPos (dw);
+}
+
+void
+Newline (DviWidget dw)
+{
+ FlushCharCache (dw);
+ dw->dvi.text_x_width = dw->dvi.text_device_width = 0;
+ dw->dvi.word_flag = 0;
+}
+
+void
+Word (DviWidget dw)
+{
+ dw->dvi.word_flag = 1;
+}
+
+#define charWidth(fi,c) (\
+ (fi)->per_char ?\
+ (fi)->per_char[(c) - (fi)->min_char_or_byte2].width\
+ :\
+ (fi)->max_bounds.width\
+)
+
+
+static
+int charExists (XFontStruct *fi, int c)
+{
+ XCharStruct *p;
+
+ /* 'c' is always >= 0 */
+ if (fi->per_char == NULL
+ || (unsigned int)c < fi->min_char_or_byte2
+ || (unsigned int)c > fi->max_char_or_byte2)
+ return 0;
+ p = fi->per_char + (c - fi->min_char_or_byte2);
+ return (p->lbearing != 0 || p->rbearing != 0 || p->width != 0
+ || p->ascent != 0 || p->descent != 0 || p->attributes != 0);
+}
+
+/* 'wid' is in device units */
+static void
+DoCharacter (DviWidget dw, int c, int wid)
+{
+ register XFontStruct *font;
+ register XTextItem *text;
+ int x, y;
+
+ x = XPos(dw);
+ y = YPos(dw);
+
+ /*
+ * quick and dirty extents calculation:
+ */
+ if (!(y + 24 >= dw->dvi.extents.y1
+ && y - 24 <= dw->dvi.extents.y2
+#if 0
+ && x + 24 >= dw->dvi.extents.x1
+ && x - 24 <= dw->dvi.extents.x2
+#endif
+ ))
+ return;
+
+ if (y != dw->dvi.cache.y
+ || dw->dvi.cache.char_index >= DVI_CHAR_CACHE_SIZE) {
+ FlushCharCache (dw);
+ x = dw->dvi.cache.x;
+ dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
+ }
+ /*
+ * load a new font, if the current block is not empty,
+ * step to the next.
+ */
+ if (dw->dvi.cache.font_size != dw->dvi.state->font_size ||
+ dw->dvi.cache.font_number != dw->dvi.state->font_number)
+ {
+ FlushCharCache (dw);
+ x = dw->dvi.cache.x;
+ dw->dvi.cache.font_size = dw->dvi.state->font_size;
+ dw->dvi.cache.font_number = dw->dvi.state->font_number;
+ dw->dvi.cache.font = QueryFont (dw,
+ dw->dvi.cache.font_number,
+ dw->dvi.cache.font_size);
+ if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) {
+ ++dw->dvi.cache.index;
+ if (dw->dvi.cache.index >= dw->dvi.cache.max)
+ FlushCharCache (dw);
+ dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0;
+ dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
+ }
+ }
+ if (x != dw->dvi.cache.x || dw->dvi.word_flag) {
+ if (dw->dvi.cache.cache[dw->dvi.cache.index].nchars != 0) {
+ ++dw->dvi.cache.index;
+ if (dw->dvi.cache.index >= dw->dvi.cache.max)
+ FlushCharCache (dw);
+ dw->dvi.cache.cache[dw->dvi.cache.index].nchars = 0;
+ dw->dvi.cache.adjustable[dw->dvi.cache.index] = 0;
+ }
+ dw->dvi.cache.adjustable[dw->dvi.cache.index]
+ = dw->dvi.word_flag;
+ dw->dvi.word_flag = 0;
+ }
+ font = dw->dvi.cache.font;
+ text = &dw->dvi.cache.cache[dw->dvi.cache.index];
+ if (text->nchars == 0) {
+ text->chars = &dw->dvi.cache.char_cache[dw->dvi.cache.char_index];
+ text->delta = x - dw->dvi.cache.x;
+ if (font != dw->dvi.font) {
+ text->font = font->fid;
+ dw->dvi.font = font;
+ } else
+ text->font = None;
+ dw->dvi.cache.x += text->delta;
+ }
+ if (charExists(font, c)) {
+ int w;
+ dw->dvi.cache.char_cache[dw->dvi.cache.char_index++] = (char) c;
+ ++text->nchars;
+ w = charWidth(font, c);
+ dw->dvi.cache.x += w;
+ if (wid != 0) {
+ dw->dvi.text_x_width += w;
+ dw->dvi.text_device_width += wid;
+ }
+ }
+}
+
+static
+int FindCharWidth (DviWidget dw, char *buf, int *widp)
+{
+ int maxpos;
+ int i;
+
+ if (dw->dvi.device_font == 0
+ || dw->dvi.state->font_number != dw->dvi.device_font_number) {
+ dw->dvi.device_font_number = dw->dvi.state->font_number;
+ dw->dvi.device_font
+ = QueryDeviceFont (dw, dw->dvi.device_font_number);
+ }
+ if (dw->dvi.device_font
+ && device_char_width (dw->dvi.device_font,
+ dw->dvi.state->font_size, buf, widp))
+ return 1;
+
+ maxpos = MaxFontPosition (dw);
+ for (i = 1; i <= maxpos; i++) {
+ DeviceFont *f = QueryDeviceFont (dw, i);
+ if (f && device_font_special (f)
+ && device_char_width (f, dw->dvi.state->font_size,
+ buf, widp)) {
+ dw->dvi.state->font_number = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Return the width of the character in device units. */
+
+int
+PutCharacter (DviWidget dw, char *buf)
+{
+ int prevFont;
+ int c = -1;
+ int wid = 0;
+ DviCharNameMap *map;
+
+ if (!dw->dvi.display_enable)
+ return 0; /* The width doesn't matter in this case. */
+ prevFont = dw->dvi.state->font_number;
+ if (!FindCharWidth (dw, buf, &wid))
+ return 0;
+ map = QueryFontMap (dw, dw->dvi.state->font_number);
+ if (map)
+ c = DviCharIndex (map, buf);
+ if (c >= 0)
+ DoCharacter (dw, c, wid);
+ else
+ (void) FakeCharacter (dw, buf, wid);
+ dw->dvi.state->font_number = prevFont;
+ return wid;
+}
+
+/* Return 1 if we can fake it; 0 otherwise. */
+
+static
+int FakeCharacter (DviWidget dw, char *buf, int wid)
+{
+ int oldx, oldw;
+ char ch[2];
+ const char *chars = 0;
+
+ if (buf[0] == '\0' || buf[1] == '\0' || buf[2] != '\0')
+ return 0;
+#define pack2(c1, c2) (((c1) << 8) | (c2))
+
+ switch (pack2(buf[0], buf[1])) {
+ case pack2('f', 'i'):
+ chars = "fi";
+ break;
+ case pack2('f', 'l'):
+ chars = "fl";
+ break;
+ case pack2('f', 'f'):
+ chars = "ff";
+ break;
+ case pack2('F', 'i'):
+ chars = "ffi";
+ break;
+ case pack2('F', 'l'):
+ chars = "ffl";
+ break;
+ }
+ if (!chars)
+ return 0;
+ oldx = dw->dvi.state->x;
+ oldw = dw->dvi.text_device_width;
+ ch[1] = '\0';
+ for (; *chars; chars++) {
+ ch[0] = *chars;
+ dw->dvi.state->x += PutCharacter (dw, ch);
+ }
+ dw->dvi.state->x = oldx;
+ dw->dvi.text_device_width = oldw + wid;
+ return 1;
+}
+
+void
+PutNumberedCharacter (DviWidget dw, int c)
+{
+ char *name;
+ int wid;
+ DviCharNameMap *map;
+
+ if (!dw->dvi.display_enable)
+ return;
+
+ if (dw->dvi.device_font == 0
+ || dw->dvi.state->font_number != dw->dvi.device_font_number) {
+ dw->dvi.device_font_number = dw->dvi.state->font_number;
+ dw->dvi.device_font
+ = QueryDeviceFont (dw, dw->dvi.device_font_number);
+ }
+
+ if (dw->dvi.device_font == 0
+ || !device_code_width (dw->dvi.device_font,
+ dw->dvi.state->font_size, c, &wid))
+ return;
+ if (dw->dvi.native) {
+ DoCharacter (dw, c, wid);
+ return;
+ }
+ map = QueryFontMap (dw, dw->dvi.state->font_number);
+ if (!map)
+ return;
+ for (name = device_name_for_code (dw->dvi.device_font, c);
+ name;
+ name = device_name_for_code ((DeviceFont *)0, c)) {
+ int code = DviCharIndex (map, name);
+ if (code >= 0) {
+ DoCharacter (dw, code, wid);
+ break;
+ }
+ if (FakeCharacter (dw, name, wid))
+ break;
+ }
+}
+
+/* unused */
+#if 0
+static void
+ClearPage (DviWidget dw)
+{
+ XClearWindow (XtDisplay (dw), XtWindow (dw));
+}
+#endif
+
+static void
+setGC (DviWidget dw)
+{
+ int desired_line_width;
+
+ if (dw->dvi.line_thickness < 0)
+ desired_line_width = (int)(((double)dw->dvi.device_resolution
+ * dw->dvi.state->font_size)
+ / (10.0*72.0*dw->dvi.sizescale));
+ else
+ desired_line_width = dw->dvi.line_thickness;
+
+ if (desired_line_width != dw->dvi.line_width) {
+ XGCValues values;
+ values.line_width = DeviceToX(dw, desired_line_width);
+ if (values.line_width == 0)
+ values.line_width = 1;
+ XChangeGC(XtDisplay (dw), dw->dvi.normal_GC,
+ GCLineWidth, &values);
+ dw->dvi.line_width = desired_line_width;
+ }
+}
+
+static void
+setFillGC (DviWidget dw)
+{
+ int fill_type;
+ unsigned long mask = GCFillStyle | GCForeground;
+
+ fill_type = (dw->dvi.fill * 10) / (DVI_FILL_MAX + 1);
+ if (dw->dvi.fill_type != fill_type) {
+ XGCValues values;
+ if (fill_type <= 0) {
+ values.foreground = dw->dvi.background;
+ values.fill_style = FillSolid;
+ } else if (fill_type >= 9) {
+ values.foreground = dw->dvi.foreground;
+ values.fill_style = FillSolid;
+ } else {
+ values.foreground = dw->dvi.foreground;
+ values.fill_style = FillOpaqueStippled;
+ values.stipple = dw->dvi.gray[fill_type - 1];
+ mask |= GCStipple;
+ }
+ XChangeGC(XtDisplay (dw), dw->dvi.fill_GC, mask, &values);
+ dw->dvi.fill_type = fill_type;
+ }
+}
+
+void
+DrawLine (DviWidget dw, int x, int y)
+{
+ int xp, yp;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ xp = XPos (dw);
+ yp = YPos (dw);
+ XDrawLine (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ xp, yp,
+ xp + DeviceToX (dw, x), yp + DeviceToX (dw, y));
+}
+
+void
+DrawCircle (DviWidget dw, int diam)
+{
+ int d;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ d = DeviceToX (dw, diam);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ XPos (dw), YPos (dw) - d/2,
+ d, d, 0, 64*360);
+}
+
+void
+DrawFilledCircle (DviWidget dw, int diam)
+{
+ int d;
+
+ AdjustCacheDeltas (dw);
+ setFillGC (dw);
+ d = DeviceToX (dw, diam);
+ XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - d/2,
+ d, d, 0, 64*360);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - d/2,
+ d, d, 0, 64*360);
+}
+
+void
+DrawEllipse (DviWidget dw, int a, int b)
+{
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
+ DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
+}
+
+void
+DrawFilledEllipse (DviWidget dw, int a, int b)
+{
+ AdjustCacheDeltas (dw);
+ setFillGC (dw);
+ XFillArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
+ DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ XPos (dw), YPos (dw) - DeviceToX (dw, b/2),
+ DeviceToX (dw, a), DeviceToX (dw, b), 0, 64*360);
+}
+
+void
+DrawArc (DviWidget dw, int x_0, int y_0, int x_1, int y_1)
+{
+ int angle1, angle2;
+ int rad = (int)((sqrt ((double)x_0*x_0 + (double)y_0*y_0)
+ + sqrt ((double)x_1*x_1 + (double)y_1*y_1)
+ + 1.0)/2.0);
+ if ((x_0 == 0 && y_0 == 0) || (x_1 == 0 && y_1 == 0))
+ return;
+ angle1 = (int)(atan2 ((double)y_0, (double)-x_0)*180.0*64.0/M_PI);
+ angle2 = (int)(atan2 ((double)-y_1, (double)x_1)*180.0*64.0/M_PI);
+
+ angle2 -= angle1;
+ if (angle2 < 0)
+ angle2 += 64*360;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+
+ rad = DeviceToX (dw, rad);
+ XDrawArc (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ XPos (dw) + DeviceToX (dw, x_0) - rad,
+ YPos (dw) + DeviceToX (dw, y_0) - rad,
+ rad*2, rad*2, angle1, angle2);
+}
+
+void
+DrawPolygon (DviWidget dw, int *v, int n)
+{
+ XPoint *p;
+ int i;
+ int dx, dy;
+
+ n /= 2;
+
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint));
+ p[0].x = XPos (dw);
+ p[0].y = YPos (dw);
+ dx = 0;
+ dy = 0;
+ for (i = 0; i < n; i++) {
+ dx += v[2*i];
+ p[i + 1].x = DeviceToX (dw, dx) + p[0].x;
+ dy += v[2*i + 1];
+ p[i + 1].y = DeviceToX (dw, dy) + p[0].y;
+ }
+ p[n+1].x = p[0].x;
+ p[n+1].y = p[0].y;
+ XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ p, n + 2, CoordModeOrigin);
+ XtFree((char *)p);
+}
+
+void
+DrawFilledPolygon (DviWidget dw, int *v, int n)
+{
+ XPoint *p;
+ int i;
+ int dx, dy;
+
+ n /= 2;
+ if (n < 2)
+ return;
+
+ AdjustCacheDeltas (dw);
+ setFillGC (dw);
+ p = (XPoint *)XtMalloc((n + 2)*sizeof(XPoint));
+ p[0].x = p[n+1].x = XPos (dw);
+ p[0].y = p[n+1].y = YPos (dw);
+ dx = 0;
+ dy = 0;
+ for (i = 0; i < n; i++) {
+ dx += v[2*i];
+ p[i + 1].x = DeviceToX (dw, dx) + p[0].x;
+ dy += v[2*i + 1];
+ p[i + 1].y = DeviceToX (dw, dy) + p[0].y;
+ }
+ XFillPolygon (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ p, n + 1, Complex, CoordModeOrigin);
+ XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.fill_GC,
+ p, n + 2, CoordModeOrigin);
+ XtFree((char *)p);
+}
+
+#define POINTS_MAX 10000
+
+static void
+appendPoint(XPoint *points, int *pointi, int x, int y)
+{
+ if (*pointi < POINTS_MAX) {
+ points[*pointi].x = x;
+ points[*pointi].y = y;
+ *pointi += 1;
+ }
+}
+
+#define FLATNESS 1
+
+static void
+flattenCurve(XPoint *points, int *pointi,
+ int x_2, int y_2, int x_3, int y_3, int x_4, int y_4)
+{
+ int x_1, y_1, dx, dy, n1, n2, n;
+
+ x_1 = points[*pointi - 1].x;
+ y_1 = points[*pointi - 1].y;
+
+ dx = x_4 - x_1;
+ dy = y_4 - y_1;
+
+ n1 = dy*(x_2 - x_1) - dx*(y_2 - y_1);
+ n2 = dy*(x_3 - x_1) - dx*(y_3 - y_1);
+ if (n1 < 0)
+ n1 = -n1;
+ if (n2 < 0)
+ n2 = -n2;
+ n = n1 > n2 ? n1 : n2;
+
+ if (n*n / (dy*dy + dx*dx) <= FLATNESS*FLATNESS)
+ appendPoint (points, pointi, x_4, y_4);
+ else {
+ flattenCurve (points, pointi,
+ (x_1 + x_2)/2,
+ (y_1 + y_2)/2,
+ (x_1 + x_2*2 + x_3)/4,
+ (y_1 + y_2*2 + y_3)/4,
+ (x_1 + 3*x_2 + 3*x_3 + x_4)/8,
+ (y_1 + 3*y_2 + 3*y_3 + y_4)/8);
+ flattenCurve (points, pointi,
+ (x_2 + x_3*2 + x_4)/4,
+ (y_2 + y_3*2 + y_4)/4,
+ (x_3 + x_4)/2,
+ (y_3 + y_4)/2,
+ x_4,
+ y_4);
+ }
+}
+
+void
+DrawSpline (DviWidget dw, int *v, int n)
+{
+ int sx, sy, tx, ty;
+ int ox, oy, dx, dy;
+ int i;
+ int pointi;
+ XPoint points[POINTS_MAX];
+
+ if (n == 0 || (n & 1) != 0)
+ return;
+ AdjustCacheDeltas (dw);
+ setGC (dw);
+ ox = XPos (dw);
+ oy = YPos (dw);
+ dx = v[0];
+ dy = v[1];
+ sx = ox;
+ sy = oy;
+ tx = sx + DeviceToX (dw, dx);
+ ty = sy + DeviceToX (dw, dy);
+
+ pointi = 0;
+
+ appendPoint (points, &pointi, sx, sy);
+ appendPoint (points, &pointi, (sx + tx)/2, (sy + ty)/2);
+
+ for (i = 2; i < n; i += 2) {
+ int ux = ox + DeviceToX (dw, dx += v[i]);
+ int uy = oy + DeviceToX (dw, dy += v[i+1]);
+ flattenCurve (points, &pointi,
+ (sx + tx*5)/6, (sy + ty*5)/6,
+ (tx*5 + ux)/6, (ty*5 + uy)/6,
+ (tx + ux)/2, (ty + uy)/2);
+ sx = tx;
+ sy = ty;
+ tx = ux;
+ ty = uy;
+ }
+
+ appendPoint (points, &pointi, tx, ty);
+
+ XDrawLines (XtDisplay (dw), XtWindow (dw), dw->dvi.normal_GC,
+ points, pointi, CoordModeOrigin);
+}
+
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/draw.h b/src/devices/xditview/draw.h
new file mode 100644
index 0000000..7a3772c
--- /dev/null
+++ b/src/devices/xditview/draw.h
@@ -0,0 +1,18 @@
+void HorizontalGoto(DviWidget, int);
+void VerticalGoto(DviWidget, int);
+void VerticalMove(DviWidget, int);
+void FlushCharCache(DviWidget);
+void Newline(DviWidget);
+void Word(DviWidget);
+int PutCharacter(DviWidget, char *);
+void PutNumberedCharacter(DviWidget, int);
+void DrawLine(DviWidget, int, int);
+void DrawCircle(DviWidget, int);
+void DrawFilledCircle(DviWidget, int);
+void DrawEllipse(DviWidget, int, int);
+void DrawFilledEllipse(DviWidget, int, int);
+void DrawArc(DviWidget, int, int, int, int);
+void DrawPolygon(DviWidget, int *, int);
+void DrawFilledPolygon(DviWidget, int *, int);
+void DrawSpline(DviWidget, int *, int);
+
diff --git a/src/devices/xditview/font.c b/src/devices/xditview/font.c
new file mode 100644
index 0000000..8462608
--- /dev/null
+++ b/src/devices/xditview/font.c
@@ -0,0 +1,446 @@
+/*
+ * font.c
+ *
+ * map dvi fonts to X fonts
+ */
+
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "DviP.h"
+#include "XFontName.h"
+#include "font.h"
+
+/* forward reference */
+static void DisposeFontSizes(DviWidget, DviFontSizeList *);
+
+static char *
+savestr (const char *s)
+{
+ char *n;
+
+ if (!s)
+ return 0;
+ n = XtMalloc (strlen (s) + 1);
+ if (n)
+ strcpy (n, s);
+ return n;
+}
+
+static DviFontList *
+LookupFontByPosition (DviWidget dw, int position)
+{
+ DviFontList *f;
+
+ for (f = dw->dvi.fonts; f; f = f->next)
+ if (f->dvi_number == position)
+ break;
+ return f;
+}
+
+int
+MaxFontPosition (DviWidget dw)
+{
+ DviFontList *f;
+ int n = -1;
+
+ for (f = dw->dvi.fonts; f; f = f->next)
+ if (f->dvi_number > n)
+ n = f->dvi_number;
+ return n;
+}
+
+static DviFontSizeList *
+LookupFontSizeBySize (DviWidget dw, DviFontList *f, int size)
+{
+ DviFontSizeList *fs, *best = 0, *smallest = 0;
+ int bestsize = 0;
+ XFontName fontName;
+ unsigned int fontNameAttributes;
+ char fontNameString[2048];
+ int decipointsize;
+
+ if (f->scalable) {
+ decipointsize = (10*size)/dw->dvi.sizescale;
+ for (best = f->sizes; best; best = best->next)
+ if (best->size == decipointsize)
+ return best;
+ best = (DviFontSizeList *) XtMalloc(sizeof *best);
+ best->next = f->sizes;
+ best->size = decipointsize;
+ f->sizes = best;
+ XParseFontName (f->x_name, &fontName, &fontNameAttributes);
+ fontNameAttributes &= ~(FontNamePixelSize|FontNameAverageWidth);
+ fontNameAttributes |= FontNameResolutionX;
+ fontNameAttributes |= FontNameResolutionY;
+ fontNameAttributes |= FontNamePointSize;
+ fontName.ResolutionX = dw->dvi.display_resolution;
+ fontName.ResolutionY = dw->dvi.display_resolution;
+ fontName.PointSize = decipointsize;
+ XFormatFontName (&fontName, fontNameAttributes, fontNameString);
+ best->x_name = savestr (fontNameString);
+ best->doesnt_exist = 0;
+ best->font = 0;
+ return best;
+ }
+ for (fs = f->sizes; fs; fs=fs->next) {
+ if (dw->dvi.sizescale*fs->size <= 10*size
+ && fs->size >= bestsize) {
+ best = fs;
+ bestsize = fs->size;
+ }
+ if (smallest == 0 || fs->size < smallest->size)
+ smallest = fs;
+ }
+ return best ? best : smallest;
+}
+
+static char *
+SkipFontNameElement (char *n)
+{
+ while (*n != '-')
+ if (!*++n)
+ return 0;
+ return n+1;
+}
+
+# define SizePosition 8
+# define EncodingPosition 13
+
+static int
+ConvertFontNameToSize (char *n)
+{
+ int i, size;
+
+ for (i = 0; i < SizePosition; i++) {
+ n = SkipFontNameElement (n);
+ if (!n)
+ return -1;
+ }
+ size = atoi (n);
+ return size;
+}
+
+static char *
+ConvertFontNameToEncoding (char *n)
+{
+ int i;
+ for (i = 0; i < EncodingPosition; i++) {
+ n = SkipFontNameElement (n);
+ if (!n)
+ return 0;
+ }
+ return n;
+}
+
+static DviFontSizeList *
+InstallFontSizes (DviWidget dw, const char *x_name, Boolean *scalablep)
+{
+ char fontNameString[2048];
+ char **fonts;
+ int i, count;
+ int size;
+ DviFontSizeList *sizes, *new_size;
+ XFontName fontName;
+ unsigned int fontNameAttributes;
+
+ *scalablep = FALSE;
+ if (!XParseFontName ((XFontNameString)x_name, &fontName,
+ &fontNameAttributes))
+ return 0;
+ fontNameAttributes &= ~(FontNamePixelSize|FontNamePointSize
+ |FontNameAverageWidth);
+ fontNameAttributes |= FontNameResolutionX;
+ fontNameAttributes |= FontNameResolutionY;
+ fontName.ResolutionX = dw->dvi.display_resolution;
+ fontName.ResolutionY = dw->dvi.display_resolution;
+ XFormatFontName (&fontName, fontNameAttributes, fontNameString);
+ fonts = XListFonts (XtDisplay (dw), fontNameString, 10000000, &count);
+ sizes = 0;
+ for (i = 0; i < count; i++) {
+ size = ConvertFontNameToSize (fonts[i]);
+ if (size == 0) {
+ DisposeFontSizes (dw, sizes);
+ sizes = 0;
+ *scalablep = TRUE;
+ break;
+ }
+ if (size != -1) {
+ new_size = (DviFontSizeList *) XtMalloc (sizeof *new_size);
+ new_size->next = sizes;
+ new_size->size = size;
+ new_size->x_name = savestr (fonts[i]);
+ new_size->doesnt_exist = 0;
+ new_size->font = 0;
+ sizes = new_size;
+ }
+ }
+ XFreeFontNames (fonts);
+ return sizes;
+}
+
+static void
+DisposeFontSizes (DviWidget dw, DviFontSizeList *fs)
+{
+ DviFontSizeList *next;
+
+ for (; fs; fs=next) {
+ next = fs->next;
+ if (fs->x_name)
+ XtFree (fs->x_name);
+ if (fs->font && fs->font != dw->dvi.default_font) {
+ XUnloadFont (XtDisplay (dw), fs->font->fid);
+ XFree ((char *)fs->font);
+ }
+ XtFree ((char *) fs);
+ }
+}
+
+static DviFontList *
+InstallFont (DviWidget dw, int position,
+ const char *dvi_name, const char *x_name)
+{
+ DviFontList *f;
+ char *encoding;
+
+ if ((f = LookupFontByPosition (dw, position)) != NULL) {
+ /*
+ * ignore gratuitous font loading
+ */
+ if (!strcmp (f->dvi_name, dvi_name) &&
+ !strcmp (f->x_name, x_name))
+ return f;
+
+ DisposeFontSizes (dw, f->sizes);
+ if (f->dvi_name)
+ XtFree (f->dvi_name);
+ if (f->x_name)
+ XtFree (f->x_name);
+ f->device_font = 0;
+ } else {
+ f = (DviFontList *) XtMalloc (sizeof (*f));
+ f->next = dw->dvi.fonts;
+ dw->dvi.fonts = f;
+ }
+ f->initialized = FALSE;
+ f->dvi_name = savestr (dvi_name);
+ f->device_font = device_find_font (dw->dvi.device, dvi_name);
+ f->x_name = savestr (x_name);
+ f->dvi_number = position;
+ f->sizes = 0;
+ f->scalable = FALSE;
+ if (f->x_name) {
+ encoding = ConvertFontNameToEncoding (f->x_name);
+ f->char_map = DviFindMap (encoding);
+ } else
+ f->char_map = 0;
+ /*
+ * force requery of fonts
+ */
+ dw->dvi.font = 0;
+ dw->dvi.font_number = -1;
+ dw->dvi.cache.font = 0;
+ dw->dvi.cache.font_number = -1;
+ dw->dvi.device_font = 0;
+ dw->dvi.device_font_number = -1;
+ return f;
+}
+
+void
+ForgetFonts (DviWidget dw)
+{
+ DviFontList *f = dw->dvi.fonts;
+
+ while (f) {
+ DviFontList *tem = f;
+
+ if (f->sizes)
+ DisposeFontSizes (dw, f->sizes);
+ if (f->dvi_name)
+ XtFree (f->dvi_name);
+ if (f->x_name)
+ XtFree (f->x_name);
+ f = f->next;
+ XtFree ((char *) tem);
+ }
+
+ /*
+ * force requery of fonts
+ */
+ dw->dvi.font = 0;
+ dw->dvi.font_number = -1;
+ dw->dvi.cache.font = 0;
+ dw->dvi.cache.font_number = -1;
+ dw->dvi.device_font = 0;
+ dw->dvi.device_font_number = -1;
+ dw->dvi.fonts = 0;
+}
+
+
+static char *
+MapDviNameToXName (DviWidget dw, const char *dvi_name)
+{
+ DviFontMap *fm;
+
+ for (fm = dw->dvi.font_map; fm; fm=fm->next)
+ if (!strcmp (fm->dvi_name, dvi_name))
+ return fm->x_name;
+ return 0;
+}
+
+#if 0
+static char *
+MapXNameToDviName (DviWidget dw, const char *x_name)
+{
+ DviFontMap *fm;
+
+ for (fm = dw->dvi.font_map; fm; fm=fm->next)
+ if (!strcmp (fm->x_name, x_name))
+ return fm->dvi_name;
+ return 0;
+}
+#endif
+
+void
+ParseFontMap (DviWidget dw)
+{
+ char dvi_name[1024];
+ char x_name[2048];
+ char *m, *s;
+ DviFontMap *fm, *new_map;
+
+ if (dw->dvi.font_map)
+ DestroyFontMap (dw->dvi.font_map);
+ fm = 0;
+ m = dw->dvi.font_map_string;
+ while (*m) {
+ s = m;
+ while (*m && !isspace (*m))
+ ++m;
+ strncpy (dvi_name, s, m-s);
+ dvi_name[m-s] = '\0';
+ while (isspace (*m))
+ ++m;
+ s = m;
+ while (*m && *m != '\n')
+ ++m;
+ strncpy (x_name, s, m-s);
+ x_name[m-s] = '\0';
+ new_map = (DviFontMap *) XtMalloc (sizeof *new_map);
+ new_map->x_name = savestr (x_name);
+ new_map->dvi_name = savestr (dvi_name);
+ new_map->next = fm;
+ fm = new_map;
+ ++m;
+ }
+ dw->dvi.font_map = fm;
+}
+
+void
+DestroyFontMap (DviFontMap *font_map)
+{
+ DviFontMap *next;
+
+ for (; font_map; font_map = next) {
+ next = font_map->next;
+ if (font_map->x_name)
+ XtFree (font_map->x_name);
+ if (font_map->dvi_name)
+ XtFree (font_map->dvi_name);
+ XtFree ((char *) font_map);
+ }
+}
+
+/* ARGSUSED */
+
+void
+SetFontPosition (DviWidget dw, int position,
+ const char *dvi_name, const char *extra)
+{
+ char *x_name;
+
+ x_name = MapDviNameToXName (dw, dvi_name);
+ if (x_name)
+ (void) InstallFont (dw, position, dvi_name, x_name);
+
+ extra = extra; /* unused; suppress compiler warning */
+}
+
+XFontStruct *
+QueryFont (DviWidget dw, int position, int size)
+{
+ DviFontList *f;
+ DviFontSizeList *fs;
+
+ f = LookupFontByPosition (dw, position);
+ if (!f)
+ return dw->dvi.default_font;
+ if (!f->initialized) {
+ f->sizes = InstallFontSizes (dw, f->x_name, &f->scalable);
+ f->initialized = TRUE;
+ }
+ fs = LookupFontSizeBySize (dw, f, size);
+ if (!fs)
+ return dw->dvi.default_font;
+ if (!fs->font) {
+ if (fs->x_name)
+ fs->font = XLoadQueryFont (XtDisplay (dw), fs->x_name);
+ if (!fs->font)
+ fs->font = dw->dvi.default_font;
+ }
+ return fs->font;
+}
+
+DeviceFont *
+QueryDeviceFont (DviWidget dw, int position)
+{
+ DviFontList *f;
+
+ f = LookupFontByPosition (dw, position);
+ if (!f)
+ return 0;
+ return f->device_font;
+}
+
+DviCharNameMap *
+QueryFontMap (DviWidget dw, int position)
+{
+ DviFontList *f;
+
+ f = LookupFontByPosition (dw, position);
+ if (f)
+ return f->char_map;
+ else
+ return 0;
+}
+
+#if 0
+LoadFont (DviWidget dw, int position, int size)
+{
+ XFontStruct *font;
+
+ font = QueryFont (dw, position, size);
+ dw->dvi.font_number = position;
+ dw->dvi.font_size = size;
+ dw->dvi.font = font;
+ XSetFont (XtDisplay (dw), dw->dvi.normal_GC, font->fid);
+ return;
+}
+#endif
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/font.h b/src/devices/xditview/font.h
new file mode 100644
index 0000000..ae6c765
--- /dev/null
+++ b/src/devices/xditview/font.h
@@ -0,0 +1,6 @@
+void DestroyFontMap(DviFontMap *);
+void ForgetFonts (DviWidget dw);
+int MaxFontPosition (DviWidget dw);
+void ParseFontMap (DviWidget dw);
+void SetFontPosition (DviWidget dw, int position, const char *dvi_name,
+ const char *extra);
diff --git a/src/devices/xditview/gray1.bm b/src/devices/xditview/gray1.bm
new file mode 100644
index 0000000..c40a95e
--- /dev/null
+++ b/src/devices/xditview/gray1.bm
@@ -0,0 +1,4 @@
+#define gray1_width 3
+#define gray1_height 3
+static char gray1_bits[] = {
+ 0x00, 0x02, 0x00};
diff --git a/src/devices/xditview/gray2.bm b/src/devices/xditview/gray2.bm
new file mode 100644
index 0000000..e87a1bc
--- /dev/null
+++ b/src/devices/xditview/gray2.bm
@@ -0,0 +1,4 @@
+#define gray2_width 3
+#define gray2_height 3
+static char gray2_bits[] = {
+ 0x00, 0x03, 0x00};
diff --git a/src/devices/xditview/gray3.bm b/src/devices/xditview/gray3.bm
new file mode 100644
index 0000000..d9313eb
--- /dev/null
+++ b/src/devices/xditview/gray3.bm
@@ -0,0 +1,4 @@
+#define gray3_width 3
+#define gray3_height 3
+static char gray3_bits[] = {
+ 0x00, 0x03, 0x02};
diff --git a/src/devices/xditview/gray4.bm b/src/devices/xditview/gray4.bm
new file mode 100644
index 0000000..dad142a
--- /dev/null
+++ b/src/devices/xditview/gray4.bm
@@ -0,0 +1,4 @@
+#define gray4_width 3
+#define gray4_height 3
+static char gray4_bits[] = {
+ 0x00, 0x07, 0x02};
diff --git a/src/devices/xditview/gray5.bm b/src/devices/xditview/gray5.bm
new file mode 100644
index 0000000..5f57618
--- /dev/null
+++ b/src/devices/xditview/gray5.bm
@@ -0,0 +1,4 @@
+#define gray5_width 3
+#define gray5_height 3
+static char gray5_bits[] = {
+ 0x04, 0x07, 0x02};
diff --git a/src/devices/xditview/gray6.bm b/src/devices/xditview/gray6.bm
new file mode 100644
index 0000000..b76701d
--- /dev/null
+++ b/src/devices/xditview/gray6.bm
@@ -0,0 +1,4 @@
+#define gray6_width 3
+#define gray6_height 3
+static char gray6_bits[] = {
+ 0x04, 0x07, 0x03};
diff --git a/src/devices/xditview/gray7.bm b/src/devices/xditview/gray7.bm
new file mode 100644
index 0000000..ef47bc6
--- /dev/null
+++ b/src/devices/xditview/gray7.bm
@@ -0,0 +1,4 @@
+#define gray7_width 3
+#define gray7_height 3
+static char gray7_bits[] = {
+ 0x05, 0x07, 0x03};
diff --git a/src/devices/xditview/gray8.bm b/src/devices/xditview/gray8.bm
new file mode 100644
index 0000000..12de7cb
--- /dev/null
+++ b/src/devices/xditview/gray8.bm
@@ -0,0 +1,4 @@
+#define gray8_width 3
+#define gray8_height 3
+static char gray8_bits[] = {
+ 0x05, 0x07, 0x07};
diff --git a/src/devices/xditview/gxditview.1.man b/src/devices/xditview/gxditview.1.man
new file mode 100644
index 0000000..920ccfd
--- /dev/null
+++ b/src/devices/xditview/gxditview.1.man
@@ -0,0 +1,815 @@
+.TH gxditview @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+gxditview \- display
+.I groff
+intermediate output files in X11
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright 1991 Massachusetts Institute of Technology
+.\"
+.\" Permission to use, copy, modify, distribute, and sell this software
+.\" and its documentation for any purpose is hereby granted without fee,
+.\" provided that the above copyright notice appear in all copies and
+.\" that both that copyright notice and this permission notice appear in
+.\" supporting documentation, and that the name of M.I.T. not be used in
+.\" advertising or publicity pertaining to distribution of the software
+.\" without specific, written prior permission. M.I.T. makes no
+.\" representations about the suitability of this software for any
+.\" purpose. It is provided "as is" without express or implied
+.\" warranty.
+.\"
+.\" M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+.\" INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+.\" NO EVENT SHALL M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+.\" CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+.\" OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+.\" NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+.\" WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_gxditview_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY gxditview
+.RI [ X-toolkit-option \~.\|.\|.\&]
+.RB [ \-backingStore\~\c
+.IR backing-store-type ]
+.RB [ \-filename\~\c
+.IR file ]
+.\" While recognized, the relevant logic is "#if 0"ed out in draw.c.
+.\" .RB [ \-noPolyText ]
+.RB [ \-page\~\c
+.IR page-number ]
+.RB [ \-printCommand\~\c
+.IR command ]
+.RB [ \-resolution\~\c
+.IR resolution ]
+.I file
+.YS
+.
+.
+.SY gxditview
+.B \-help
+.
+.SY gxditview
+.B \-\-help
+.YS
+.
+.
+.SY gxditview
+.B \-version
+.
+.SY gxditview
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I gxditview
+interprets and displays the intermediate output format of
+.MR groff @MAN1EXT@
+on an X11\~display.
+.
+It uses the standard X11 fonts,
+so it does not require access to the server machine for font loading.
+.
+There are several ways to use
+.IR gxditview .
+.
+.
+.PP
+The
+intermediate output format of
+.IR groff ,
+documented in
+.MR groff_out @MAN5EXT@ ,
+is produced by
+.I @g@troff
+or the
+.B \-Z
+option to
+.IR groff .
+.
+.
+It can be viewed by explicitly calling
+.RB \[lq] gxditview
+.IR file \[rq].
+.
+If the
+.I file
+operand is
+.RB \[lq] \- \[rq],
+.I gxditview
+will read the standard input stream;
+.I file
+cannot be omitted.
+.
+The intermediate output format of
+.I groff
+is device-independent but not device-agnostic.
+.
+.I gxditview
+can view it for all typesetter devices,
+but the quality is device-dependent.
+.
+.I gxditview
+will not display output for terminal
+.RI ( nroff )
+devices.
+.
+.
+.PP
+The best results are achieved with the
+.BR X *
+devices for
+.IR groff 's
+.B \-T
+option,
+of which there are four:
+.BR \-TX75 ,
+.BR \-TX75\-12 ,
+.BR \-TX100 ,
+and
+.BR \-TX100\-12 .
+.
+They differ by the X\~resolution
+(75 or 100 dots per inch)
+and the base point size
+(10 or 12 points).
+.
+They are especially built for
+.IR gxditview .
+.
+When using one of these,
+.I groff
+generates the intermediate output for this device and calls
+.I gxditview
+automatically for viewing.
+.
+.
+.P
+.B \-X
+produces good results only with
+.BR \-Tps ,
+.BR \-TX75 ,
+.BR \-TX75\-12 ,
+.BR \-TX100 ,
+and
+.BR \-TX100\-12 .
+.
+The default resolution for previewing
+.B \-Tps
+output is 75\|dpi;
+this can be changed with the
+.B \-resolution
+option.
+.
+.
+.PP
+While
+.I gxditview
+is running,
+the left mouse button brings up a menu with several entries.
+.
+.
+.TP 13n
+.B Next Page
+Display the next page.
+.
+.
+.TP
+.B Previous Page
+Display the previous page.
+.
+.
+.TP
+.B Select Page
+Select a particular numbered page specified by a dialog box.
+.
+.
+.TP
+.B Print
+Print the
+.I groff
+intermediate output using a command specified by a dialog box.
+.
+The default command initially displayed is controlled by the
+.B printCommand
+application resource,
+and by the
+.B \-printCommand
+option.
+.
+.
+.TP
+.B Open
+Open for display a new file specified by a dialog box.
+.
+The file should contain
+.I groff
+intermediate output.
+.
+If the filename starts with a bar or pipe symbol,
+.RB \[lq] | \[rq]
+it will be interpreted as a command from which to read.
+.
+.
+.TP
+.B Quit
+Exit from
+.BR gxditview .
+.
+.
+.PP
+The menu entries correspond to actions with similar but not identical
+names,
+which can also be accessed with keyboard accelerators.
+.
+The
+.IR n ,
+.IR Space ,
+.IR Return ,
+and
+.I Next
+.RI ( PgDn )
+keys are bound to the
+.B NextPage
+action.
+.
+The
+.IR p ,
+.IR b ,
+.IR BackSpace ,
+.IR Delete ,
+and
+.I Prior
+.RI ( PgUp )
+keys are bound to the
+.B PreviousPage
+action.
+.
+The
+.I g
+key is bound to the
+.B SelectPage
+action.
+.
+The
+.I o
+key is bound to the
+.B OpenFile
+action.
+.
+The
+.I q
+key is bound to the
+.B Quit
+action.
+.
+The
+.I r
+key is bound to a
+.B Rerasterize
+action which rereads the current file,
+and redisplays the current page;
+if the current file is a command,
+the command will be re-executed.
+.
+Vertical scrolling can be done with the
+.I k
+and
+.I j
+keys;
+horizontal scrolling is bound to the
+.I h
+and
+.I l
+keys.
+.
+The arrow keys
+.RI ( up ,
+.IR down ,
+.IR left ,
+and
+.IR right )
+are also bound to the obvious scrolling actions.
+.
+.
+.PP
+The
+.B paperlength
+and
+.B paperwidth
+commands in the
+.I DESC
+file describing a
+.I groff
+output device specify the length and width in machine units of the
+virtual page displayed by
+.IR gxditview ;
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS "X defaults"
+.\" ====================================================================
+.
+This program uses the
+.I Dvi
+widget from the X\~Toolkit.
+.
+It understands all of the core resource names and classes as well as:
+.
+.
+.TP
+.BR width\~ (class\~ Width )
+Specifies the width of the window.
+.
+.
+.TP
+.BR height\~ (class\~ Height )
+Specifies the height of the window.
+.
+.
+.TP
+.BR foreground\~ (class\~ Foreground )
+Specifies the default foreground color.
+.
+.
+.TP
+.BR font\~ (class\~ Font )
+Specifies the font to be used for error messages.
+.
+.
+.TP
+.BR fontMap\~ (class\~ FontMap )
+Specifies the mapping from
+.I groff
+font names to X\~font names.
+.
+This must be a string containing a sequence of lines.
+.
+Each line contains two whitespace-separated fields:
+firstly the
+.I groff
+font name,
+and secondly the XLFD
+(X Logical Font Description).
+.
+The default is shown in subsection \[lq]Default font map\[rq] below.
+.
+.
+.\" ====================================================================
+.SS "Default font map"
+.\" ====================================================================
+.
+XLFDs are long and unwieldy,
+so some lines are shown broken and indented below.
+.\" Break them after the POINT_SIZE field (in "decipoints", so "100").
+.
+.
+.PP
+.EX
+TR \-adobe\-times\-medium\-r\-normal\-\-*\-100\
+\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+TI \-adobe\-times\-medium\-i\-normal\-\-*\-100\
+\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+TB \-adobe\-times\-bold\-r\-normal\-\-*\-100\
+\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+TBI \-adobe\-times\-bold\-i\-normal\
+\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CR \-adobe\-courier\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CI \-adobe\-courier\-medium\-o\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CB \-adobe\-courier\-bold\-r\-normal\
+\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+CBI \-adobe\-courier\-bold\-o\-normal\
+\-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HR \-adobe\-helvetica\-medium\-r\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HI \-adobe\-helvetica\-medium\-o\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HB \-adobe\-helvetica\-bold\-r\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+HBI \-adobe\-helvetica\-bold\-o\-normal\" break
+ \-\-*\-100\-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NR \-adobe\-new century schoolbook\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NI \-adobe\-new century schoolbook\-medium\-i\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NB \-adobe\-new century schoolbook\-bold\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+NBI \-adobe\-new century schoolbook\-bold\-i\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-iso8859\-1\[rs]n\[rs]
+S \-adobe\-symbol\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-adobe\-fontspecific\[rs]n\[rs]
+SS \-adobe\-symbol\-medium\-r\-normal\-\-*\-100\" break
+ \-*\-*\-*\-*\-adobe\-fontspecific\[rs]n\[rs]
+.EE
+.
+.
+.br
+.ne 3v
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-help
+and
+.B \-\-help
+display a usage message,
+while
+.B \-version
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.P
+.I gxditview
+accepts all of the standard X\~Toolkit command-line options along with
+the additional options listed below.
+.
+.
+.TP
+.B \-page
+This option specifies the page number of the document to be displayed.
+.
+.
+.TP
+.BI \-backingStore\~ backing-store-type
+Because redisplay of the
+.I groff
+intermediate output window can take a perceiptible amount of time,
+this option causes the server to save the window contents so that when
+it is scrolled around the viewport,
+the window is painted from contents saved in backing store.
+.
+.I backing-store-type
+can be one of
+.BR Always ,
+.B WhenMapped
+or
+.BR NotUseful .
+.
+.
+.TP
+.BI \-printCommand\~ command
+The default command displayed in the dialog box for the
+.B Print
+menu entry will be
+.IR command .
+.
+.
+.TP
+.BI \-resolution\~ res
+The
+.I groff
+intermediate output file will be displayed at a resolution of
+.I res
+dots per inch,
+unless the
+.I DESC
+file contains the
+.B X11
+command,
+in which case the device resolution will be used.
+.
+This corresponds to the
+.I Dvi
+widget's
+.B resolution
+resource.
+.
+The default is
+.BR 75 .
+.
+.
+.TP
+.BI \-filename\~ string
+The default filename displayed in the dialog box for the
+.B Open
+menu entry will be
+.IR string .
+.
+This can be either a filename,
+or a command starting with
+.RB \[lq] | \[rq].
+.
+.
+.PP
+The following standard X\~Toolkit command-line arguments are commonly
+used with
+.IR gxditview .
+.
+.
+.TP
+.BI \-bg\~ color
+This option specifies the color to use for the background of the window.
+.
+The default is
+.RB \[lq] white \[rq].
+.
+.
+.TP
+.BI \-bd\~ color
+This option specifies the color to use for the border of the window.
+.
+The default is
+.RB \[lq] black \[rq].
+.
+.
+.TP
+.BI \-bw\~ number
+This option specifies the width in pixels of the border surrounding the
+window.
+.
+.
+.TP
+.BI \-fg\~ color
+This option specifies the color to use for displaying text.
+.
+The default is
+.RB \[lq] black \[rq].
+.
+.
+.TP
+.BI \-fn\~ font
+This option specifies the font to be used for displaying widget text.
+.
+The default is
+.RB \[lq] fixed \[rq].
+.
+.
+.TP
+.B \-rv
+This option indicates that reverse video should be simulated by swapping
+the foreground and background colors.
+.
+.
+.TP
+.BI \-geometry\~ geometry
+This option specifies the preferred size and position of the window.
+.
+.
+.TP
+.BI \-display\~ host : display
+This option specifies the X\~server to contact.
+.
+.
+.TP
+.BI \-xrm\~ resourcestring
+This option specifies a resource string to be used.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @APPDEFDIR@/\:\%GXditview
+.TQ
+.I @APPDEFDIR@/\:\%GXditview\-color
+define X application defaults for
+.IR gxditview .
+.
+Users can override these values in the
+.I .Xdefaults
+file,
+normally located in the user's home directory.
+.
+See
+.MR appres 1
+and
+.MR xrdb 1 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX100/\:DESC
+describes the
+.B X100
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX100/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X100 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX100\-12/\:DESC
+describes the
+.B X100\-12
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX100\-12/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X100\-12 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX75/\:DESC
+describes the
+.B X75
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX75/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X75 .
+.
+.
+.TP
+.I @FONTDIR@/\:\%devX75\-12/\:DESC
+describes the
+.B X75\-12
+output device.
+.
+.
+.TP
+.IR @FONTDIR@/\:\%devX75\-12/ F
+describes the font known
+.RI as\~ F
+on device
+.BR X75\-12 .
+.
+.
+.TP
+.I @MACRODIR@/\:X.tmac
+defines macros for use with the
+.BR X100 ,
+.BR X100\-12 ,
+.BR X75 ,
+and
+.B X75\-12
+output devices.
+.
+It is automatically loaded by
+.I troffrc
+when any of those output devices is selected.
+.
+.
+.TP
+.I @MACRODIR@/\:Xps\:.tmac
+sets up
+.I @g@troff
+to use
+.I \%gxditview
+as a previewer for device-independent output targeting the
+.B ps
+output device.
+.
+It is automatically loaded by
+.I troffrc
+when
+.I @g@troff
+is given the options
+.B \-X
+and
+.BR \-Tps .
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+The following command views this man page with a base point size of 12.
+.
+.
+.RS
+.P
+.EX
+groff \-TX100\-12 \-man gxditview.1
+.EE
+.RE
+.
+.
+.P
+The quality of the result depends mainly on the chosen point size and
+display resolution;
+for rapid previewing,
+however,
+something like
+.
+.RS
+.EX
+.RI "groff \-X \-P\-resolution \-P100\~" document
+.EE
+.RE
+.
+yields acceptable results.
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I gxditview
+and its predecessor
+.I xditview
+were written by
+Keith Packard (MIT X Consortium),
+Richard L.\& Hyde (Purdue),
+David Slattengren (Berkeley),
+Malcolm Slaney (Schlumberger Palo Alto Research),
+Mark Moraes (University of Toronto),
+and
+James Clark.
+.
+.
+.PP
+This program is derived from
+.IR xditview ;
+portions of
+.I xditview
+originated in
+.IR xtroff ,
+which was derived from
+.IR \%suntroff .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.UR https://\:www\:.x\:.org/\:releases/\:X11R7.6/\:doc/\:xorg\-docs/\
+\:specs/\:XLFD/xlfd\:.html
+\[lq]X Logical Font Description Conventions\[rq]
+.UE ,
+by Jim Flowers and Stephen Gildea.
+.
+.
+.PP
+.MR X 7 ,
+.MR xrdb 1 ,
+.MR xditview 1 ,
+.MR groff @MAN1EXT@ ,
+.MR groff_out @MAN5EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_gxditview_1_man_C]
+.do rr *groff_gxditview_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/devices/xditview/lex.c b/src/devices/xditview/lex.c
new file mode 100644
index 0000000..19cf292
--- /dev/null
+++ b/src/devices/xditview/lex.c
@@ -0,0 +1,100 @@
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+
+#include "DviP.h"
+#include "lex.h"
+
+int
+DviGetAndPut(DviWidget dw, int *cp)
+{
+ if (dw->dvi.ungot) {
+ dw->dvi.ungot = 0;
+ *cp = getc (dw->dvi.file);
+ }
+ else {
+ *cp = getc (dw->dvi.file);
+ if (*cp != EOF)
+ putc (*cp, dw->dvi.tmpFile);
+ }
+ return *cp;
+}
+
+char *
+GetLine(DviWidget dw, char *Buffer, int Length)
+{
+ int i = 0, c;
+
+ Length--; /* Save room for final '\0' */
+
+ while (DviGetC (dw, &c) != EOF) {
+ if (Buffer && i < Length)
+ Buffer[i++] = c;
+ if (c == '\n') {
+ DviUngetC(dw, c);
+ break;
+ }
+ }
+ if (Buffer)
+ Buffer[i] = '\0';
+ return Buffer;
+}
+
+char *
+GetWord(DviWidget dw, char *Buffer, int Length)
+{
+ int i = 0, c;
+
+ Length--; /* Save room for final '\0' */
+ while (DviGetC(dw, &c) == ' ' || c == '\n')
+ ;
+ while (c != EOF) {
+ if (Buffer && i < Length)
+ Buffer[i++] = c;
+ if (DviGetC(dw, &c) == ' ' || c == '\n') {
+ DviUngetC(dw, c);
+ break;
+ }
+ }
+ if (Buffer)
+ Buffer[i] = '\0';
+ return Buffer;
+}
+
+int
+GetNumber(DviWidget dw)
+{
+ int i = 0, c;
+ int negative = 0;
+
+ while (DviGetC(dw, &c) == ' ' || c == '\n')
+ ;
+ if (c == '-') {
+ negative = 1;
+ DviGetC(dw, &c);
+ }
+
+ for (; c >= '0' && c <= '9'; DviGetC(dw, &c)) {
+ if (negative)
+ i = i*10 - (c - '0');
+ else
+ i = i*10 + c - '0';
+ }
+ if (c != EOF)
+ DviUngetC(dw, c);
+ return i;
+}
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/lex.h b/src/devices/xditview/lex.h
new file mode 100644
index 0000000..0a4cba0
--- /dev/null
+++ b/src/devices/xditview/lex.h
@@ -0,0 +1 @@
+int GetNumber(DviWidget);
diff --git a/src/devices/xditview/page.c b/src/devices/xditview/page.c
new file mode 100644
index 0000000..352d871
--- /dev/null
+++ b/src/devices/xditview/page.c
@@ -0,0 +1,86 @@
+/*
+ * page.c
+ *
+ * map page numbers to file position
+ */
+
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "DviP.h"
+#include "page.h"
+
+#ifdef X_NOT_STDC_ENV
+extern long ftell();
+#endif
+
+static DviFileMap *
+MapPageNumberToFileMap (DviWidget dw, int number)
+{
+ DviFileMap *m;
+
+ for (m = dw->dvi.file_map; m; m=m->next)
+ if (m->page_number == number)
+ break;
+ return m;
+}
+
+void
+DestroyFileMap (DviFileMap *m)
+{
+ DviFileMap *next;
+
+ for (; m; m = next) {
+ next = m->next;
+ XtFree ((char *) m);
+ }
+}
+
+void
+ForgetPagePositions (DviWidget dw)
+{
+ DestroyFileMap (dw->dvi.file_map);
+ dw->dvi.file_map = 0;
+}
+
+void
+RememberPagePosition(DviWidget dw, int number)
+{
+ DviFileMap *m;
+
+ if (!(m = MapPageNumberToFileMap (dw, number))) {
+ m = (DviFileMap *) XtMalloc (sizeof *m);
+ m->page_number = number;
+ m->next = dw->dvi.file_map;
+ dw->dvi.file_map = m;
+ }
+ if (dw->dvi.tmpFile)
+ m->position = ftell (dw->dvi.tmpFile);
+ else
+ m->position = ftell (dw->dvi.file);
+}
+
+long
+SearchPagePosition (DviWidget dw, int number)
+{
+ DviFileMap *m;
+
+ if (!(m = MapPageNumberToFileMap (dw, number)))
+ return -1;
+ return m->position;
+}
+
+void
+FileSeek(DviWidget dw, long position)
+{
+ if (dw->dvi.tmpFile) {
+ dw->dvi.readingTmp = 1;
+ fseek (dw->dvi.tmpFile, position, 0);
+ } else
+ fseek (dw->dvi.file, position, 0);
+}
diff --git a/src/devices/xditview/page.h b/src/devices/xditview/page.h
new file mode 100644
index 0000000..e2c938d
--- /dev/null
+++ b/src/devices/xditview/page.h
@@ -0,0 +1,5 @@
+void DestroyFileMap(DviFileMap *);
+void FileSeek(DviWidget, long);
+void ForgetPagePositions(DviWidget);
+void RememberPagePosition(DviWidget, int);
+long SearchPagePosition(DviWidget, int);
diff --git a/src/devices/xditview/parse.c b/src/devices/xditview/parse.c
new file mode 100644
index 0000000..456c7da
--- /dev/null
+++ b/src/devices/xditview/parse.c
@@ -0,0 +1,343 @@
+/*
+ * parse.c
+ *
+ * parse dvi input
+ */
+
+#include <config.h>
+
+#include <X11/Xos.h>
+#include <X11/IntrinsicP.h>
+#include <X11/StringDefs.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "DviP.h"
+#include "draw.h"
+#include "font.h"
+#include "lex.h"
+#include "page.h"
+#include "parse.h"
+
+static int StopSeen = 0;
+static void ParseDrawFunction(DviWidget, char *);
+static void ParseDeviceControl(DviWidget);
+static void push_env(DviWidget);
+static void pop_env(DviWidget);
+
+#define HorizontalMove(dw, delta) ((dw)->dvi.state->x += (delta))
+
+
+int
+ParseInput(register DviWidget dw)
+{
+ int n, k;
+ int c;
+ char Buffer[BUFSIZ];
+ int NextPage;
+ int otherc;
+
+ StopSeen = 0;
+
+ /*
+ * make sure some state exists
+ */
+
+ if (!dw->dvi.state)
+ push_env (dw);
+ for (;;) {
+ switch (DviGetC(dw, &c)) {
+ case '\n':
+ break;
+ case ' ': /* when input is text */
+ case 0: /* occasional noise creeps in */
+ break;
+ case '{': /* push down current environment */
+ push_env(dw);
+ break;
+ case '}':
+ pop_env(dw);
+ break;
+ /*
+ * two motion digits plus a character
+ */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ HorizontalMove(dw, (c-'0')*10 +
+ DviGetC(dw,&otherc)-'0');
+ /* fall through */
+ case 'c': /* single ascii character */
+ DviGetC(dw,&c);
+ if (c == ' ')
+ break;
+ Buffer[0] = c;
+ Buffer[1] = '\0';
+ (void) PutCharacter (dw, Buffer);
+ break;
+ case 'C':
+ GetWord (dw, Buffer, BUFSIZ);
+ (void) PutCharacter (dw, Buffer);
+ break;
+ case 't':
+ Buffer[1] = '\0';
+ while (DviGetC (dw, &c) != EOF
+ && c != ' ' && c != '\n') {
+ Buffer[0] = c;
+ HorizontalMove (dw, PutCharacter (dw, Buffer));
+ }
+ break;
+ case 'u':
+ n = GetNumber(dw);
+ Buffer[1] = '\0';
+ while (DviGetC (dw, &c) == ' ')
+ ;
+ while (c != EOF && c != ' ' && c != '\n') {
+ Buffer[0] = c;
+ HorizontalMove (dw,
+ PutCharacter (dw, Buffer) + n);
+ DviGetC (dw, &c);
+ }
+ break;
+
+ case 'D': /* draw function */
+ (void) GetLine(dw, Buffer, BUFSIZ);
+ if (dw->dvi.display_enable)
+ ParseDrawFunction(dw, Buffer);
+ break;
+ case 's': /* ignore fractional sizes */
+ n = GetNumber(dw);
+ dw->dvi.state->font_size = n;
+ break;
+ case 'f':
+ n = GetNumber(dw);
+ dw->dvi.state->font_number = n;
+ break;
+ case 'H': /* absolute horizontal motion */
+ k = GetNumber(dw);
+ HorizontalGoto(dw, k);
+ break;
+ case 'h': /* relative horizontal motion */
+ k = GetNumber(dw);
+ HorizontalMove(dw, k);
+ break;
+ case 'w': /* word space */
+ Word (dw);
+ break;
+ case 'V':
+ n = GetNumber(dw);
+ VerticalGoto(dw, n);
+ break;
+ case 'v':
+ n = GetNumber(dw);
+ VerticalMove(dw, n);
+ break;
+ case 'P': /* new spread */
+ break;
+ case 'p': /* new page */
+ (void) GetNumber(dw);
+ NextPage = dw->dvi.current_page + 1;
+ RememberPagePosition(dw, NextPage);
+ FlushCharCache (dw);
+ return(NextPage);
+ case 'N':
+ n = GetNumber(dw);
+ PutNumberedCharacter (dw, n);
+ break;
+ case 'n': /* end of line */
+ GetNumber(dw);
+ GetNumber(dw);
+ Newline (dw);
+ HorizontalGoto(dw, 0);
+ break;
+ case 'F': /* input files */
+ case '+': /* continuation of X device control */
+ case 'm': /* color */
+ case '#': /* comment */
+ GetLine(dw, NULL, 0);
+ break;
+ case 'x': /* device control */
+ ParseDeviceControl(dw);
+ break;
+ case EOF:
+ dw->dvi.last_page = dw->dvi.current_page;
+ FlushCharCache (dw);
+ return dw->dvi.current_page;
+ default:
+ break;
+ }
+ }
+}
+
+static void
+push_env(DviWidget dw)
+{
+ DviState *new_state;
+
+ new_state = (DviState *) XtMalloc (sizeof (*new_state));
+ if (dw->dvi.state)
+ *new_state = *(dw->dvi.state);
+ else {
+ new_state->font_size = 10;
+ new_state->font_number = 1;
+ new_state->x = 0;
+ new_state->y = 0;
+ }
+ new_state->next = dw->dvi.state;
+ dw->dvi.state = new_state;
+}
+
+static void
+pop_env(DviWidget dw)
+{
+ DviState *old;
+
+ old = dw->dvi.state;
+ dw->dvi.state = old->next;
+ XtFree ((char *) old);
+}
+
+static void
+InitTypesetter (DviWidget dw)
+{
+ while (dw->dvi.state)
+ pop_env (dw);
+ push_env (dw);
+ FlushCharCache (dw);
+}
+
+#define DRAW_ARGS_MAX 128
+
+static void
+ParseDrawFunction(DviWidget dw, char *buf)
+{
+ int v[DRAW_ARGS_MAX];
+ int i, no_move = 0;
+ char *ptr;
+
+ v[0] = v[1] = v[2] = v[3] = 0;
+
+ if (buf[0] == '\0')
+ return;
+ ptr = buf+1;
+
+ for (i = 0; i < DRAW_ARGS_MAX; i++) {
+ if (sscanf(ptr, "%d", v + i) != 1)
+ break;
+ while (*ptr == ' ')
+ ptr++;
+ while (*ptr != '\0' && *ptr != ' ')
+ ptr++;
+ }
+
+ switch (buf[0]) {
+ case 'l': /* draw a line */
+ DrawLine(dw, v[0], v[1]);
+ break;
+ case 'c': /* circle */
+ DrawCircle(dw, v[0]);
+ break;
+ case 'C':
+ DrawFilledCircle(dw, v[0]);
+ break;
+ case 'e': /* ellipse */
+ DrawEllipse(dw, v[0], v[1]);
+ break;
+ case 'E':
+ DrawFilledEllipse(dw, v[0], v[1]);
+ break;
+ case 'a': /* arc */
+ DrawArc(dw, v[0], v[1], v[2], v[3]);
+ break;
+ case 'p':
+ DrawPolygon(dw, v, i);
+ break;
+ case 'P':
+ DrawFilledPolygon(dw, v, i);
+ break;
+ case '~': /* wiggly line */
+ DrawSpline(dw, v, i);
+ break;
+ case 't':
+ dw->dvi.line_thickness = v[0];
+ break;
+ case 'f':
+ if (i > 0 && v[0] >= 0 && v[0] <= DVI_FILL_MAX)
+ dw->dvi.fill = v[0];
+ no_move = 1;
+ break;
+ default:
+#if 0
+ warning("unknown drawing function %s", buf);
+#endif
+ no_move = 1;
+ break;
+ }
+
+ if (!no_move) {
+ if (buf[0] == 'e') {
+ if (i > 0)
+ dw->dvi.state->x += v[0];
+ }
+ else {
+ while (--i >= 0) {
+ if (i & 1)
+ dw->dvi.state->y += v[i];
+ else
+ dw->dvi.state->x += v[i];
+ }
+ }
+ }
+}
+
+static void
+ParseDeviceControl(DviWidget dw) /* Parse the x commands */
+{
+ char str[20], str1[50];
+ int c, n;
+
+ GetWord (dw, str, 20);
+ switch (str[0]) { /* crude for now */
+ case 'T': /* output device */
+ GetWord (dw, str, 20);
+ SetDevice (dw, str);
+ break;
+ case 'i': /* initialize */
+ InitTypesetter (dw);
+ break;
+ case 't': /* trailer */
+ break;
+ case 'p': /* pause -- can restart */
+ break;
+ case 's': /* stop */
+ StopSeen = 1;
+ return;
+ case 'r': /* resolution when prepared */
+ break;
+ case 'f': /* font used */
+ n = GetNumber (dw);
+ GetWord (dw, str, 20);
+ GetLine (dw, str1, 50);
+ SetFontPosition (dw, n, str, str1);
+ break;
+ case 'H': /* char height */
+ break;
+ case 'S': /* slant */
+ break;
+ }
+ while (DviGetC (dw, &c) != '\n') /* skip rest of input line */
+ if (c == EOF)
+ return;
+ return;
+}
+
+
+/*
+Local Variables:
+c-indent-level: 8
+c-continued-statement-offset: 8
+c-brace-offset: -8
+c-argdecl-indent: 8
+c-label-offset: -8
+c-tab-always-indent: nil
+End:
+*/
diff --git a/src/devices/xditview/parse.h b/src/devices/xditview/parse.h
new file mode 100644
index 0000000..636f3a6
--- /dev/null
+++ b/src/devices/xditview/parse.h
@@ -0,0 +1 @@
+int ParseInput(register DviWidget dw);
diff --git a/src/devices/xditview/xdit.bm b/src/devices/xditview/xdit.bm
new file mode 100644
index 0000000..0c7aa8c
--- /dev/null
+++ b/src/devices/xditview/xdit.bm
@@ -0,0 +1,14 @@
+#define xdit_width 32
+#define xdit_height 32
+static unsigned char xdit_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x03, 0x02, 0x00, 0x00, 0x02,
+ 0x8a, 0xa2, 0xfc, 0x03, 0x52, 0x14, 0x03, 0x04, 0x02, 0x80, 0x00, 0x08,
+ 0x52, 0x54, 0x00, 0x10, 0x8a, 0x22, 0x8f, 0x23, 0x02, 0x20, 0x06, 0x21,
+ 0x8a, 0x12, 0x8c, 0x40, 0x52, 0x14, 0x8c, 0x40, 0x02, 0x10, 0x58, 0x40,
+ 0x52, 0x14, 0x30, 0x40, 0x8a, 0x12, 0x30, 0x40, 0x02, 0x10, 0x70, 0x40,
+ 0x8a, 0x12, 0xc8, 0x40, 0x52, 0x24, 0xc4, 0xe0, 0x02, 0x20, 0x84, 0xe1,
+ 0x52, 0x54, 0xce, 0xf3, 0x8a, 0xa2, 0x00, 0xf8, 0x02, 0x00, 0x03, 0xfc,
+ 0x8a, 0x22, 0xfc, 0xf3, 0x52, 0x14, 0x00, 0xc2, 0x02, 0x00, 0x00, 0x02,
+ 0x52, 0x14, 0x45, 0x02, 0x8a, 0xa2, 0x28, 0x02, 0x02, 0x00, 0x00, 0x02,
+ 0x02, 0x00, 0x00, 0x02, 0xfe, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
diff --git a/src/devices/xditview/xdit_mask.bm b/src/devices/xditview/xdit_mask.bm
new file mode 100644
index 0000000..a584629
--- /dev/null
+++ b/src/devices/xditview/xdit_mask.bm
@@ -0,0 +1,14 @@
+#define xdit_mask_width 32
+#define xdit_mask_height 32
+static unsigned char xdit_mask_bits[] = {
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07,
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x1f,
+ 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xc7,
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07,
+ 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff, 0xff, 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
diff --git a/src/devices/xditview/xditview.am b/src/devices/xditview/xditview.am
new file mode 100644
index 0000000..7bb32ba
--- /dev/null
+++ b/src/devices/xditview/xditview.am
@@ -0,0 +1,130 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# This file is part of groff.
+#
+# groff is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+GXDITVIEWSOURCES = \
+ src/devices/xditview/device.c \
+ src/devices/xditview/draw.c \
+ src/devices/xditview/draw.h \
+ src/devices/xditview/Dvi.c \
+ src/devices/xditview/font.c \
+ src/devices/xditview/font.h \
+ src/devices/xditview/lex.c \
+ src/devices/xditview/lex.h \
+ src/devices/xditview/page.c \
+ src/devices/xditview/page.h \
+ src/devices/xditview/parse.c \
+ src/devices/xditview/parse.h \
+ src/devices/xditview/xditview.c \
+ src/devices/xditview/device.h \
+ src/devices/xditview/DviP.h \
+ src/devices/xditview/Menu.h \
+ src/devices/xditview/Dvi.h
+
+GXDITVIEW_GROFF_VERSION_H=src/devices/xditview/groff_version.h
+
+$(GXDITVIEW_GROFF_VERSION_H): $(top_srcdir)/.version
+ $(AM_V_at)$(MKDIR_P) `dirname $@`
+ $(AM_V_GEN)printf \
+ 'const char *Version_string = "%s";\n' '@VERSION@' > $@
+
+if WITHOUT_X11
+GXDITVIEW_MAN1 =
+EXTRA_DIST += $(GXDITVIEWSOURCES)
+else
+GXDITVIEW_MAN1 = src/devices/xditview/gxditview.1
+xditview_srcdir = $(top_srcdir)/src/devices/xditview
+bin_PROGRAMS += gxditview
+gxditview_CPPFLAGS = $(AM_CPPFLAGS) $(X_CFLAGS) -Dlint \
+ -I$(top_builddir)/src/devices/xditview
+gxditview_LDADD = $(X_LIBS) $(X_PRE_LIBS) -lXaw -lXmu -lXt -lX11 \
+ $(X_EXTRA_LIBS) $(LIBM) libxutil.a lib/libgnu.a
+XDITVIEW_GENHDRS = \
+ src/devices/xditview/GXditview-ad.h \
+ $(GXDITVIEW_GROFF_VERSION_H)
+gxditview_SOURCES = $(GXDITVIEWSOURCES)
+nodist_gxditview_SOURCES = $(XDITVIEW_GENHDRS)
+CLEANFILES += $(XDITVIEW_GENHDRS)
+
+man1_MANS += $(GXDITVIEW_MAN1)
+
+# Because we defined gxditview_CPPFLAGS, automake renames all of
+# xditview's objects, adding an "gxditview-" prefix.
+src/devices/xditview/gxditview-device.$(OBJEXT): defs.h
+src/devices/xditview/gxditview-xditview.$(OBJEXT): $(XDITVIEW_GENHDRS)
+
+src/devices/xditview/GXditview-ad.h: $(xditview_srcdir)/GXditview.ad
+ $(AM_V_GEN)$(SHELL) $(xditview_srcdir)/ad2c \
+ $(xditview_srcdir)/GXditview.ad > $@
+endif
+
+EXTRA_DIST += \
+ src/devices/xditview/ad2c \
+ src/devices/xditview/ChangeLog \
+ src/devices/xditview/DESC.in \
+ src/devices/xditview/FontMap-X11 \
+ src/devices/xditview/GXditview-color.ad \
+ src/devices/xditview/GXditview.ad \
+ src/devices/xditview/README \
+ src/devices/xditview/TODO \
+ src/devices/xditview/gray1.bm \
+ src/devices/xditview/gray2.bm \
+ src/devices/xditview/gray3.bm \
+ src/devices/xditview/gray4.bm \
+ src/devices/xditview/gray5.bm \
+ src/devices/xditview/gray6.bm \
+ src/devices/xditview/gray7.bm \
+ src/devices/xditview/gray8.bm \
+ src/devices/xditview/xdit.bm \
+ src/devices/xditview/xdit_mask.bm \
+ src/devices/xditview/gxditview.1.man
+
+# Custom installation of GXditview.ad and GXditview-color.ad
+install-data-local: install_xditview
+uninstall-local: uninstall_xditview
+
+if WITHOUT_X11
+install_xditview:
+uninstall_xditview:
+else
+install_xditview: \
+ $(xditview_srcdir)/FontMap-X11 \
+ $(xditview_srcdir)/GXditview.ad \
+ $(xditview_srcdir)/GXditview-color.ad
+ $(MKDIR_P) $(DESTDIR)$(fontdir)
+ $(INSTALL_DATA) $(xditview_srcdir)/FontMap-X11 \
+ $(DESTDIR)$(fontdir)/FontMap-X11
+ -test -d $(DESTDIR)$(appdefdir) \
+ || $(mkinstalldirs) $(DESTDIR)$(appdefdir)
+ $(INSTALL_DATA) $(xditview_srcdir)/GXditview.ad \
+ $(DESTDIR)$(appdefdir)/GXditview
+ $(INSTALL_DATA) $(xditview_srcdir)/GXditview-color.ad \
+ $(DESTDIR)$(appdefdir)/GXditview-color
+
+uninstall_xditview:
+ rm -f $(DESTDIR)$(appdefdir)/GXditview
+ rm -f $(DESTDIR)$(appdefdir)/GXditview-color
+ rm -f $(DESTDIR)$(fontdir)/FontMap-X11
+ -rmdir $(DESTDIR)$(fontdir) 2>/dev/null
+
+endif
+
+
+# Local Variables:
+# fill-column: 72
+# mode: makefile-automake
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/devices/xditview/xditview.c b/src/devices/xditview/xditview.c
new file mode 100644
index 0000000..1f56940
--- /dev/null
+++ b/src/devices/xditview/xditview.c
@@ -0,0 +1,684 @@
+/*
+ * Copyright 1991 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. M.I.T. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+/*
+ * xditview --
+ *
+ * Display ditroff output in an X window
+ */
+
+#ifndef SABER
+#ifndef lint
+static char rcsid[] = "$XConsortium: xditview.c,v 1.17 89/12/10 17:05:08 rws Exp $";
+#endif /* lint */
+#endif /* SABER */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xos.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <X11/Shell.h>
+#include <X11/Xaw/Paned.h>
+#include <X11/Xaw/Viewport.h>
+#include <X11/Xaw/Box.h>
+#include <X11/Xaw/Command.h>
+#include <X11/Xaw/Dialog.h>
+#include <X11/Xaw/Label.h>
+#include <X11/Xaw/SimpleMenu.h>
+#include <X11/Xaw/SmeBSB.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "Dvi.h"
+#include "groff_version.h"
+
+#include "xdit.bm"
+#include "xdit_mask.bm"
+
+#ifdef NEED_DECLARATION_POPEN
+FILE *popen(const char *, const char *);
+#endif /* NEED_DECLARATION_POPEN */
+
+#ifdef NEED_DECLARATION_PCLOSE
+int pclose (FILE *);
+#endif /* NEED_DECLARATION_PCLOSE */
+
+typedef void (*MakePromptFunc)(const char *);
+
+static String fallback_resources[] = {
+#include "GXditview-ad.h"
+ NULL
+};
+
+static struct app_resources {
+ char *print_command;
+ char *filename;
+} app_resources;
+
+#define offset(field) XtOffset(struct app_resources *, field)
+
+/* Application resources. */
+
+static XtResource resources[] = {
+ {(String)"printCommand", (String)"PrintCommand", (String)XtRString,
+ sizeof(char*), offset(print_command), (String)XtRString, NULL},
+ {(String)"filename", (String)"Filename", (String)XtRString,
+ sizeof(char*), offset(filename), (String)XtRString, NULL},
+};
+
+#undef offset
+
+/* Command line options table. Only resources are entered here...there is a
+ pass over the remaining options after XrmParseCommand is let loose. */
+
+static XrmOptionDescRec options[] = {
+{(char *)"-page", (char *)"*dvi.pageNumber",
+ XrmoptionSepArg, NULL},
+{(char *)"-backingStore", (char *)"*dvi.backingStore",
+ XrmoptionSepArg, NULL},
+{(char *)"-resolution", (char *)"*dvi.resolution",
+ XrmoptionSepArg, NULL},
+{(char *)"-printCommand", (char *)".printCommand",
+ XrmoptionSepArg, NULL},
+{(char *)"-filename", (char *)".filename",
+ XrmoptionSepArg, NULL},
+{(char *)"-noPolyText", (char *)"*dvi.noPolyText",
+ XrmoptionNoArg, (XPointer)"TRUE"},
+};
+
+static char current_print_command[1024];
+
+static char current_file_name[1024];
+static FILE *current_file;
+
+/*
+ * Report the syntax for calling xditview.
+ */
+
+static void
+Syntax(const char *progname, bool had_error)
+{
+ FILE *stream = stdout;
+ if (had_error)
+ stream = stderr;
+ (void) fprintf (stream, "usage: %s [X-toolkit-option]"
+ " [-backingStore backing-store-type]"
+ " [-filename file]"
+// See draw.c:FlushCharCache().
+#if 0
+ " [-noPolyText]"
+#endif
+ " [-page page-number]"
+ " [-printCommand command]"
+ " [-resolution resolution]"
+ " [file]\n", progname);
+ (void) fprintf (stream, "usage: %s {-version | --version}\n",
+ progname);
+ (void) fprintf (stream, "usage: %s {-help | --help}\n",
+ progname);
+ if (had_error)
+ exit(EXIT_FAILURE);
+ else
+ exit(EXIT_SUCCESS);
+}
+
+static void NewFile (const char *);
+static void SetPageNumber (int);
+static Widget toplevel, paned, viewport, dvi;
+static Widget page;
+static Widget simpleMenu;
+
+static void NextPage(Widget, XtPointer, XtPointer);
+static void PreviousPage(Widget, XtPointer, XtPointer);
+static void SelectPage(Widget, XtPointer, XtPointer);
+static void OpenFile(Widget, XtPointer, XtPointer);
+static void Quit(Widget, XtPointer, XtPointer);
+static void Print(Widget, XtPointer, XtPointer);
+
+static struct menuEntry {
+ const char *name;
+ XtCallbackProc function;
+} menuEntries[] = {
+ {"nextPage", NextPage},
+ {"previousPage", PreviousPage},
+ {"selectPage", SelectPage},
+ {"print", Print},
+ {"openFile", OpenFile},
+ {"quit", Quit},
+};
+
+static void NextPageAction(Widget, XEvent *, String *, Cardinal *);
+static void PreviousPageAction(Widget, XEvent *, String *, Cardinal *);
+static void SelectPageAction(Widget, XEvent *, String *, Cardinal *);
+static void OpenFileAction(Widget, XEvent *, String *, Cardinal *);
+static void QuitAction(Widget, XEvent *, String *, Cardinal *);
+static void AcceptAction(Widget, XEvent *, String *, Cardinal *);
+static void CancelAction(Widget, XEvent *, String *, Cardinal *);
+static void PrintAction(Widget, XEvent *, String *, Cardinal *);
+static void RerasterizeAction(Widget, XEvent *, String *, Cardinal *);
+
+static void MakePrompt(Widget, const char *, MakePromptFunc, const char *);
+
+XtActionsRec xditview_actions[] = {
+ {(String)"NextPage", NextPageAction},
+ {(String)"PreviousPage", PreviousPageAction},
+ {(String)"SelectPage", SelectPageAction},
+ {(String)"Print", PrintAction},
+ {(String)"OpenFile", OpenFileAction},
+ {(String)"Rerasterize", RerasterizeAction},
+ {(String)"Quit", QuitAction},
+ {(String)"Accept", AcceptAction},
+ {(String)"Cancel", CancelAction},
+};
+
+#define MenuNextPage 0
+#define MenuPreviousPage 1
+#define MenuSelectPage 2
+#define MenuPrint 3
+#define MenuOpenFile 4
+#define MenuQuit 5
+
+static char pageLabel[256] = "Page <none>";
+
+int main(int argc, char **argv)
+{
+ char *file_name = 0;
+ Cardinal i;
+ static Arg labelArgs[] = {
+ {XtNlabel, (XtArgVal) pageLabel},
+ };
+ XtAppContext xtcontext;
+ Arg topLevelArgs[2];
+ Widget entry;
+ Arg pageNumberArgs[1];
+ int page_number;
+
+ toplevel = XtAppInitialize(&xtcontext, "GXditview",
+ options, XtNumber (options),
+ &argc, argv, fallback_resources, NULL, 0);
+ if (argc > 2)
+ Syntax(argv[0], true /* had error */);
+ else if (argc == 2) {
+ if ((strcmp(argv[1], "-help") == 0)
+ || (strcmp(argv[1], "--help") == 0))
+ Syntax(argv[0], false /* did not have error */);
+ else if ((strcmp(argv[1], "-version") == 0)
+ || (strcmp(argv[1], "--version") == 0)) {
+ (void) printf("GNU gxditview (groff) version %s\n",
+ Version_string);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ XtGetApplicationResources(toplevel, (XtPointer)&app_resources,
+ resources, XtNumber(resources),
+ NULL, (Cardinal) 0);
+ if (app_resources.print_command)
+ strcpy(current_print_command, app_resources.print_command);
+
+ XtAppAddActions(xtcontext, xditview_actions, XtNumber (xditview_actions));
+
+ XtSetArg (topLevelArgs[0], XtNiconPixmap,
+ XCreateBitmapFromData (XtDisplay (toplevel),
+ XtScreen(toplevel)->root,
+ (char *)xdit_bits,
+ xdit_width, xdit_height));
+
+ XtSetArg (topLevelArgs[1], XtNiconMask,
+ XCreateBitmapFromData (XtDisplay (toplevel),
+ XtScreen(toplevel)->root,
+ (char *)xdit_mask_bits,
+ xdit_mask_width, xdit_mask_height));
+ XtSetValues (toplevel, topLevelArgs, 2);
+ if (argc > 1)
+ file_name = argv[1];
+
+ /*
+ * create the menu and insert the entries
+ */
+ simpleMenu = XtCreatePopupShell ("menu", simpleMenuWidgetClass, toplevel,
+ NULL, 0);
+ for (i = 0; i < XtNumber (menuEntries); i++) {
+ entry = XtCreateManagedWidget(menuEntries[i].name,
+ smeBSBObjectClass, simpleMenu,
+ NULL, (Cardinal) 0);
+ XtAddCallback(entry, XtNcallback, menuEntries[i].function, NULL);
+ }
+
+ paned = XtCreateManagedWidget("paned", panedWidgetClass, toplevel,
+ NULL, (Cardinal) 0);
+ viewport = XtCreateManagedWidget("viewport", viewportWidgetClass, paned,
+ NULL, (Cardinal) 0);
+ dvi = XtCreateManagedWidget ("dvi", dviWidgetClass, viewport, NULL, 0);
+ page = XtCreateManagedWidget ("label", labelWidgetClass, paned,
+ labelArgs, XtNumber (labelArgs));
+ XtSetArg (pageNumberArgs[0], XtNpageNumber, &page_number);
+ XtGetValues (dvi, pageNumberArgs, 1);
+ if (file_name)
+ NewFile (file_name);
+ /* NewFile modifies current_file_name, so do this here. */
+ if (app_resources.filename)
+ strcpy(current_file_name, app_resources.filename);
+ XtRealizeWidget (toplevel);
+ if (file_name)
+ SetPageNumber (page_number);
+ XtInstallAllAccelerators(paned,paned);
+ XtAppMainLoop(xtcontext);
+ return 0;
+}
+
+static void
+SetPageNumber (int number)
+{
+ Arg arg[2];
+ int actual_number, last_page;
+
+ XtSetArg (arg[0], XtNpageNumber, number);
+ XtSetValues (dvi, arg, 1);
+ XtSetArg (arg[0], XtNpageNumber, &actual_number);
+ XtSetArg (arg[1], XtNlastPageNumber, &last_page);
+ XtGetValues (dvi, arg, 2);
+ if (actual_number == 0)
+ sprintf (pageLabel, "Page <none>");
+ else if (last_page > 0)
+ sprintf (pageLabel, "Page %d of %d", actual_number, last_page);
+ else
+ sprintf (pageLabel, "Page %d", actual_number);
+ XtSetArg (arg[0], XtNlabel, pageLabel);
+ XtSetValues (page, arg, 1);
+}
+
+static void
+SelectPageNumber (const char *number_string)
+{
+ SetPageNumber (atoi(number_string));
+}
+
+static int hadFile = 0;
+
+static void
+NewFile (const char *name)
+{
+ Arg arg[2];
+ char *n;
+ FILE *new_file;
+ Boolean seek = 0;
+
+ if (current_file) {
+ if (!strcmp (current_file_name, "-"))
+ ;
+ else if (current_file_name[0] == '|')
+ pclose (current_file);
+ else
+ fclose (current_file);
+ }
+ if (!strcmp (name, "-"))
+ new_file = stdin;
+ else if (name[0] == '|')
+ new_file = popen (name+1, "r");
+ else {
+ new_file = fopen (name, "r");
+ seek = 1;
+ }
+ if (!new_file) {
+ /* XXX display error message */
+ return;
+ }
+ XtSetArg (arg[0], XtNfile, new_file);
+ XtSetArg (arg[1], XtNseek, seek);
+ XtSetValues (dvi, arg, 2);
+ if (hadFile || name[0] != '-' || name[1] != '\0') {
+ XtSetArg (arg[0], XtNtitle, name);
+ if (name[0] != '/' && (n = strrchr (name, '/')))
+ n = n + 1;
+ else
+ n = (char *)name;
+ XtSetArg (arg[1], XtNiconName, n);
+ XtSetValues (toplevel, arg, 2);
+ }
+ hadFile = 1;
+ SelectPageNumber ("1");
+ strcpy (current_file_name, name);
+ current_file = new_file;
+}
+
+static char fileBuf[1024];
+
+static void
+ResetMenuEntry (Widget entry)
+{
+ Arg arg[1];
+
+ XtSetArg (arg[0], (String)XtNpopupOnEntry, entry);
+ XtSetValues (XtParent(entry) , arg, (Cardinal) 1);
+}
+
+/*ARGSUSED*/
+
+static void
+NextPage (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ NextPageAction((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+NextPageAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ Arg args[1];
+ int number;
+
+ XtSetArg (args[0], XtNpageNumber, &number);
+ XtGetValues (dvi, args, 1);
+ SetPageNumber (number+1);
+
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+}
+
+/*ARGSUSED*/
+
+static void
+PreviousPage (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ PreviousPageAction ((Widget)NULL, (XEvent *) 0, (String *) 0,
+ (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+PreviousPageAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ Arg args[1];
+ int number;
+
+ XtSetArg (args[0], XtNpageNumber, &number);
+ XtGetValues (dvi, args, 1);
+ SetPageNumber (number-1);
+
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+}
+
+/* ARGSUSED */
+
+static void
+SelectPage (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ SelectPageAction ((Widget)NULL, (XEvent *) 0, (String *) 0,
+ (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+SelectPageAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ MakePrompt (toplevel, "Page number", SelectPageNumber, "");
+}
+
+
+static void
+DoPrint (const char *name)
+{
+ FILE *print_file;
+ RETSIGTYPE (*handler)(int);
+
+ /* Avoid dying because of an invalid command. */
+ handler = signal(SIGPIPE, SIG_IGN);
+
+ print_file = popen(name, "w");
+ if (!print_file)
+ /* XXX print error message */
+ return;
+ DviSaveToFile(dvi, print_file);
+ pclose(print_file);
+ signal(SIGPIPE, handler);
+ strcpy(current_print_command, name);
+}
+
+static void
+RerasterizeAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ Arg args[1];
+ int number;
+
+ if (current_file_name[0] == 0) {
+ /* XXX display an error message */
+ return;
+ }
+ XtSetArg (args[0], XtNpageNumber, &number);
+ XtGetValues (dvi, args, 1);
+ NewFile(current_file_name);
+ SetPageNumber (number);
+
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+}
+
+/* ARGSUSED */
+
+static void
+Print (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ PrintAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+PrintAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ if (current_print_command[0])
+ strcpy (fileBuf, current_print_command);
+ else
+ fileBuf[0] = '\0';
+ MakePrompt (toplevel, "Print command:", DoPrint, fileBuf);
+}
+
+
+/* ARGSUSED */
+
+static void
+OpenFile (Widget entry, XtPointer name, XtPointer data)
+{
+ name = name; /* unused; suppress compiler warning */
+ data = data;
+
+ OpenFileAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ ResetMenuEntry (entry);
+}
+
+static void
+OpenFileAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ if (current_file_name[0])
+ strcpy (fileBuf, current_file_name);
+ else
+ fileBuf[0] = '\0';
+ MakePrompt (toplevel, "File to open:", NewFile, fileBuf);
+}
+
+/* ARGSUSED */
+
+static void
+Quit (Widget entry, XtPointer closure, XtPointer data)
+{
+ entry = entry; /* unused; suppress compiler warning */
+ closure = closure;
+ data = data;
+
+ QuitAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+}
+
+static void
+QuitAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ exit (0);
+}
+
+Widget promptShell, promptDialog;
+MakePromptFunc promptfunction;
+
+/* ARGSUSED */
+static
+void CancelAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ widget = widget; /* unused; suppress compiler warning */
+ event = event;
+ params = params;
+ num_params = num_params;
+
+ if (promptShell) {
+ XtSetKeyboardFocus(toplevel, (Widget) None);
+ XtDestroyWidget(promptShell);
+ promptShell = (Widget) 0;
+ }
+}
+
+static
+void AcceptAction (Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ (*promptfunction)(XawDialogGetValueString(promptDialog));
+ CancelAction (widget, event, params, num_params);
+}
+
+static void
+MakePrompt(Widget centerw, const char *prompt,
+ MakePromptFunc func, const char *def)
+{
+ static Arg dialogArgs[] = {
+ {XtNlabel, 0},
+ {XtNvalue, 0},
+ };
+ Arg valueArgs[1];
+ Arg centerArgs[2];
+ Position source_x, source_y;
+ Position dest_x, dest_y;
+ Dimension center_width, center_height;
+ Dimension prompt_width, prompt_height;
+ Widget valueWidget;
+
+ CancelAction ((Widget)NULL, (XEvent *) 0, (String *) 0, (Cardinal *) 0);
+ promptShell = XtCreatePopupShell ("promptShell", transientShellWidgetClass,
+ toplevel, NULL, (Cardinal) 0);
+ dialogArgs[0].value = (XtArgVal)prompt;
+ dialogArgs[1].value = (XtArgVal)def;
+ promptDialog = XtCreateManagedWidget( "promptDialog", dialogWidgetClass,
+ promptShell, dialogArgs, XtNumber (dialogArgs));
+ XawDialogAddButton(promptDialog, "accept", NULL, (XtPointer) 0);
+ XawDialogAddButton(promptDialog, "cancel", NULL, (XtPointer) 0);
+ valueWidget = XtNameToWidget (promptDialog, "value");
+ if (valueWidget) {
+ XtSetArg (valueArgs[0], (String)XtNresizable, TRUE);
+ XtSetValues (valueWidget, valueArgs, 1);
+ /*
+ * as resizable isn't set until just above, the
+ * default value will be displayed incorrectly.
+ * rectify the situation by resetting the values
+ */
+ XtSetValues (promptDialog, dialogArgs, XtNumber (dialogArgs));
+ }
+ XtSetKeyboardFocus (promptDialog, valueWidget);
+ XtSetKeyboardFocus (toplevel, valueWidget);
+ XtRealizeWidget (promptShell);
+ /*
+ * place the widget in the center of the "parent"
+ */
+ XtSetArg (centerArgs[0], XtNwidth, &center_width);
+ XtSetArg (centerArgs[1], XtNheight, &center_height);
+ XtGetValues (centerw, centerArgs, 2);
+ XtSetArg (centerArgs[0], XtNwidth, &prompt_width);
+ XtSetArg (centerArgs[1], XtNheight, &prompt_height);
+ XtGetValues (promptShell, centerArgs, 2);
+ source_x = (center_width - prompt_width) / 2;
+ source_y = (center_height - prompt_height) / 3;
+ XtTranslateCoords (centerw, source_x, source_y, &dest_x, &dest_y);
+ XtSetArg (centerArgs[0], XtNx, dest_x);
+ XtSetArg (centerArgs[1], XtNy, dest_y);
+ XtSetValues (promptShell, centerArgs, 2);
+ XtMapWidget(promptShell);
+ promptfunction = func;
+}
+
+/*
+Local Variables:
+c-indent-level: 4
+c-continued-statement-offset: 4
+c-brace-offset: -4
+c-argdecl-indent: 4
+c-label-offset: -4
+c-tab-always-indent: nil
+End:
+*/