summaryrefslogtreecommitdiffstats
path: root/src/devices/grodvi
diff options
context:
space:
mode:
Diffstat (limited to '')
-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
3 files changed, 1653 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: