/* $NetBSD: lj4.cpp,v 1.1.1.1 2016/01/13 18:41:49 christos Exp $ */
// -*- C++ -*-
/* Copyright (C) 1994, 2000, 2001, 2002, 2003, 2004
Free Software Foundation, Inc.
Written by James Clark (jjc@jclark.com)
This file is part of groff.
groff is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
version.
groff is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License along
with groff; see the file COPYING. If not, write to the Free Software
Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
/*
TODO
option to use beziers for circle/ellipse/arc
option to use lines for spline (for LJ3)
left/top offset registration
output bin selection option
paper source option
output non-integer parameters using fixed point numbers
X command to insert contents of file
X command to specify inline escape sequence (how to specify unprintable chars?)
X command to include bitmap graphics
*/
#include "driver.h"
#include "nonposix.h"
extern "C" const char *Version_string;
static struct {
const char *name;
int code;
// at 300dpi
int x_offset_portrait;
int x_offset_landscape;
} paper_table[] = {
{ "letter", 2, 75, 60 },
{ "legal", 3, 75, 60 },
{ "executive", 1, 75, 60 },
{ "a4", 26, 71, 59 },
{ "com10", 81, 75, 60 },
{ "monarch", 80, 75, 60 },
{ "c5", 91, 71, 59 },
{ "b5", 100, 71, 59 },
{ "dl", 90, 71, 59 },
};
static int user_paper_size = -1;
static int landscape_flag = 0;
static int duplex_flag = 0;
// An upper limit on the paper size in centipoints,
// used for setting HPGL picture frame.
#define MAX_PAPER_WIDTH (12*720)
#define MAX_PAPER_HEIGHT (17*720)
// Dotted lines that are thinner than this don't work right.
#define MIN_DOT_PEN_WIDTH .351
#ifndef DEFAULT_LINE_WIDTH_FACTOR
// in ems/1000
#define DEFAULT_LINE_WIDTH_FACTOR 40
#endif
const int DEFAULT_HPGL_UNITS = 1016;
int line_width_factor = DEFAULT_LINE_WIDTH_FACTOR;
unsigned ncopies = 0; // 0 means don't send ncopies command
static int lookup_paper_size(const char *);
class lj4_font : public font {
public:
~lj4_font();
void handle_unknown_font_command(const char *command, const char *arg,
const char *filename, int lineno);
static lj4_font *load_lj4_font(const char *);
int weight;
int style;
int proportional;
int typeface;
private:
lj4_font(const char *);
};
lj4_font::lj4_font(const char *nm)
: font(nm), weight(0), style(0), proportional(0), typeface(0)
{
}
lj4_font::~lj4_font()
{
}
lj4_font *lj4_font::load_lj4_font(const char *s)
{
lj4_font *f = new lj4_font(s);
if (!f->load()) {
delete f;
return 0;
}
return f;
}
static struct {
const char *s;
int lj4_font::*ptr;
int min;
int max;
} command_table[] = {
{ "pclweight", &lj4_font::weight, -7, 7 },
{ "pclstyle", &lj4_font::style, 0, 32767 },
{ "pclproportional", &lj4_font::proportional, 0, 1 },
{ "pcltypeface", &lj4_font::typeface, 0, 65535 },
};
void lj4_font::handle_unknown_font_command(const char *command,
const char *arg,
const char *filename, int lineno)
{
for (unsigned int i = 0;
i < sizeof(command_table)/sizeof(command_table[0]); i++) {
if (strcmp(command, command_table[i].s) == 0) {
if (arg == 0)
fatal_with_file_and_line(filename, lineno,
"`%1' command requires an argument",
command);
char *ptr;
long n = strtol(arg, &ptr, 10);
if (n == 0 && ptr == arg)
fatal_with_file_and_line(filename, lineno,
"`%1' command requires numeric argument",
command);
if (n < command_table[i].min) {
error_with_file_and_line(filename, lineno,
"argument for `%1' command must not be less than %2",
command, command_table[i].min);
n = command_table[i].min;
}
else if (n > command_table[i].max) {
error_with_file_and_line(filename, lineno,
"argument for `%1' command must not be greater than %2",
command, command_table[i].max);
n = command_table[i].max;
}
this->*command_table[i].ptr = int(n);
break;
}
}
}
class lj4_printer : public printer {
public:
lj4_printer(int);
~lj4_printer();
void set_char(int, font *, const environment *, int, const char *name);
void draw(int code, int *p, int np, const environment *env);
void begin_page(int);
void end_page(int page_length);
font *make_font(const char *);
void end_of_line();
private:
void set_line_thickness(int size, int dot = 0);
void hpgl_init();
void hpgl_start();
void hpgl_end();
int moveto(int hpos, int vpos);
int moveto1(int hpos, int vpos);
int cur_hpos;
int cur_vpos;
lj4_font *cur_font;
int cur_size;
unsigned short cur_symbol_set;
int x_offset;
int line_thickness;
double pen_width;
double hpgl_scale;
int hpgl_inited;
int paper_size;
};
inline
int lj4_printer::moveto(int hpos, int vpos)
{
if (cur_hpos != hpos || cur_vpos != vpos || cur_hpos < 0)
return moveto1(hpos, vpos);
else
return 1;
}
inline
void lj4_printer::hpgl_start()
{
fputs("\033%1B", stdout);
}
inline
void lj4_printer::hpgl_end()
{
fputs(";\033%0A", stdout);
}
lj4_printer::lj4_printer(int ps)
: cur_hpos(-1),
cur_font(0),
cur_size(0),
cur_symbol_set(0),
line_thickness(-1),
pen_width(-1.0),
hpgl_inited(0)
{
if (7200 % font::res != 0)
fatal("invalid resolution %1: resolution must be a factor of 7200",
font::res);
fputs("\033E", stdout); // reset
if (font::res != 300)
printf("\033&u%dD", font::res); // unit of measure
if (ncopies > 0)
printf("\033&l%uX", ncopies);
paper_size = 0; // default to letter
if (font::papersize) {
int n = lookup_paper_size(font::papersize);
if (n < 0)
error("unknown paper size `%1'", font::papersize);
else
paper_size = n;
}
if (ps >= 0)
paper_size = ps;
printf("\033&l%dA" // paper size
"\033&l%dO" // orientation
"\033&l0E", // no top margin
paper_table[paper_size].code,
landscape_flag != 0);
if (landscape_flag)
x_offset = paper_table[paper_size].x_offset_landscape;
else
x_offset = paper_table[paper_size].x_offset_portrait;
x_offset = (x_offset * font::res) / 300;
if (duplex_flag)
printf("\033&l%dS", duplex_flag);
}
lj4_printer::~lj4_printer()
{
fputs("\033E", stdout);
}
void lj4_printer::begin_page(int)
{
}
void lj4_printer::end_page(int)
{
putchar('\f');
cur_hpos = -1;
}
void lj4_printer::end_of_line()
{
cur_hpos = -1; // force absolute motion
}
inline
int is_unprintable(unsigned char c)
{
return c < 32 && (c == 0 || (7 <= c && c <= 15) || c == 27);
}
void lj4_printer::set_char(int idx, font *f, const environment *env,
int w, const char *)
{
int code = f->get_code(idx);
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("unknown paper size `%1'", optarg);
else
user_paper_size = n;
break;
}
case 'v':
printf("GNU grolj4 (groff) version %s\n", Version_string);
exit(0);
break;
case 'F':
font::command_line_font_dir(optarg);
break;
case 'c':
{
char *ptr;
long n = strtol(optarg, &ptr, 10);
if (n == 0 && ptr == optarg)
error("argument for -c must be a positive integer");
else if (n <= 0 || n > 32767)
error("out of range argument for -c");
else
ncopies = unsigned(n);
break;
}
case 'w':
{
char *ptr;
long n = strtol(optarg, &ptr, 10);
if (n == 0 && ptr == optarg)
error("argument for -w must be a non-negative integer");
else if (n < 0 || n > INT_MAX)
error("out of range argument for -w");
else
line_width_factor = int(n);
break;
}
case CHAR_MAX + 1: // --help
usage(stdout);
exit(0);
break;
case '?':
usage(stderr);
exit(1);
break;
default:
assert(0);
}
SET_BINARY(fileno(stdout));
if (optind >= argc)
do_file("-");
else {
for (int i = optind; i < argc; i++)
do_file(argv[i]);
}
return 0;
}
static void usage(FILE *stream)
{
fprintf(stream,
"usage: %s [-lv] [-d [n]] [-c n] [-p paper_size]\n"
" [-w n] [-F dir] [files ...]\n",
program_name);
}