diff options
Diffstat (limited to 'src/preproc/pic/troff.cpp')
-rw-r--r-- | src/preproc/pic/troff.cpp | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/src/preproc/pic/troff.cpp b/src/preproc/pic/troff.cpp new file mode 100644 index 0000000..3dc87a7 --- /dev/null +++ b/src/preproc/pic/troff.cpp @@ -0,0 +1,579 @@ +/* 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 "pic.h" +#include "common.h" + + +const double RELATIVE_THICKNESS = -1.0; +const double BAD_THICKNESS = -2.0; + +class simple_output : public common_output { + virtual void simple_line(const position &, const position &) = 0; + virtual void simple_spline(const position &, const position *, int n) = 0; + virtual void simple_arc(const position &, const position &, + const position &) = 0; + virtual void simple_circle(int, const position &, double rad) = 0; + virtual void simple_ellipse(int, const position &, const distance &) = 0; + virtual void simple_polygon(int, const position *, int) = 0; + virtual void line_thickness(double) = 0; + virtual void set_fill(double) = 0; + virtual void set_color(char *, char *) = 0; + virtual void reset_color() = 0; + virtual char *get_last_filled() = 0; + void dot(const position &, const line_type &) = 0; +public: + void start_picture(double sc, const position &ll, const position &ur) = 0; + void finish_picture() = 0; + void text(const position &, text_piece *, int, double) = 0; + void line(const position &, const position *, int n, + const line_type &); + void polygon(const position *, int n, + const line_type &, double); + void spline(const position &, const position *, int n, + const line_type &); + void arc(const position &, const position &, const position &, + const line_type &); + void circle(const position &, double rad, const line_type &, double); + void ellipse(const position &, const distance &, const line_type &, double); + int supports_filled_polygons(); +}; + +int simple_output::supports_filled_polygons() +{ + return driver_extension_flag != 0; +} + +void simple_output::arc(const position &start, const position ¢, + const position &end, const line_type <) +{ + switch (lt.type) { + case line_type::solid: + line_thickness(lt.thickness); + simple_arc(start, cent, end); + break; + case line_type::invisible: + break; + case line_type::dashed: + dashed_arc(start, cent, end, lt); + break; + case line_type::dotted: + dotted_arc(start, cent, end, lt); + break; + } +} + +void simple_output::line(const position &start, const position *v, int n, + const line_type <) +{ + position pos = start; + line_thickness(lt.thickness); + for (int i = 0; i < n; i++) { + switch (lt.type) { + case line_type::solid: + simple_line(pos, v[i]); + break; + case line_type::dotted: + { + distance vec(v[i] - pos); + double dist = hypot(vec); + int ndots = int(dist/lt.dash_width + .5); + if (ndots == 0) + dot(pos, lt); + else { + vec /= double(ndots); + for (int j = 0; j <= ndots; j++) + dot(pos + vec*j, lt); + } + } + break; + case line_type::dashed: + { + distance vec(v[i] - pos); + double dist = hypot(vec); + if (dist <= lt.dash_width*2.0) + simple_line(pos, v[i]); + else { + int ndashes = int((dist - lt.dash_width)/(lt.dash_width*2.0) + .5); + distance dash_vec = vec*(lt.dash_width/dist); + double dash_gap = (dist - lt.dash_width)/ndashes; + distance dash_gap_vec = vec*(dash_gap/dist); + for (int j = 0; j <= ndashes; j++) { + position s(pos + dash_gap_vec*j); + simple_line(s, s + dash_vec); + } + } + } + break; + case line_type::invisible: + break; + default: + assert(0); + } + pos = v[i]; + } +} + +void simple_output::spline(const position &start, const position *v, int n, + const line_type <) +{ + line_thickness(lt.thickness); + simple_spline(start, v, n); +} + +void simple_output::polygon(const position *v, int n, + const line_type <, double fill) +{ + if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) { + if (get_last_filled() == 0) + set_fill(fill); + simple_polygon(1, v, n); + } + if (lt.type == line_type::solid && driver_extension_flag) { + line_thickness(lt.thickness); + simple_polygon(0, v, n); + } + else if (lt.type != line_type::invisible) { + line_thickness(lt.thickness); + line(v[n - 1], v, n, lt); + } +} + +void simple_output::circle(const position ¢, double rad, + const line_type <, double fill) +{ + if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) { + if (get_last_filled() == 0) + set_fill(fill); + simple_circle(1, cent, rad); + } + line_thickness(lt.thickness); + switch (lt.type) { + case line_type::invisible: + break; + case line_type::dashed: + dashed_circle(cent, rad, lt); + break; + case line_type::dotted: + dotted_circle(cent, rad, lt); + break; + case line_type::solid: + simple_circle(0, cent, rad); + break; + default: + assert(0); + } +} + +void simple_output::ellipse(const position ¢, const distance &dim, + const line_type <, double fill) +{ + if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) { + if (get_last_filled() == 0) + set_fill(fill); + simple_ellipse(1, cent, dim); + } + if (lt.type != line_type::invisible) + line_thickness(lt.thickness); + switch (lt.type) { + case line_type::invisible: + break; + case line_type::dotted: + dotted_ellipse(cent, dim, lt); + break; + case line_type::dashed: + dashed_ellipse(cent, dim, lt); + break; + case line_type::solid: + simple_ellipse(0, cent, dim); + break; + default: + assert(0); + } +} + +class troff_output : public simple_output { + const char *last_filename; + position upper_left; + double height; + double scale; + double last_line_thickness; + double last_fill; + char *last_filled; // color + char *last_outlined; // color +public: + troff_output(); + ~troff_output(); + void start_picture(double, const position &ll, const position &ur); + void finish_picture(); + void text(const position &, text_piece *, int, double); + void dot(const position &, const line_type &); + void command(const char *, const char *, int); + void set_location(const char *, int); + void simple_line(const position &, const position &); + void simple_spline(const position &, const position *, int n); + void simple_arc(const position &, const position &, const position &); + void simple_circle(int, const position &, double rad); + void simple_ellipse(int, const position &, const distance &); + void simple_polygon(int, const position *, int); + void line_thickness(double p); + void set_fill(double); + void set_color(char *, char *); + void reset_color(); + char *get_last_filled(); + char *get_outline_color(); + position transform(const position &); +}; + +output *make_troff_output() +{ + return new troff_output; +} + +troff_output::troff_output() +: last_filename(0), last_line_thickness(BAD_THICKNESS), + last_fill(-1.0), last_filled(0), last_outlined(0) +{ +} + +troff_output::~troff_output() +{ + free((char *)last_filename); +} + +inline position troff_output::transform(const position &pos) +{ + return position((pos.x - upper_left.x)/scale, + (upper_left.y - pos.y)/scale); +} + +#define FILL_REG "00" + +// If this register > 0, then pic will generate \X'ps: ...' commands +// if the aligned attribute is used. +#define GROPS_REG "0p" + +// If this register is defined, geqn won't produce '\x's. +#define EQN_NO_EXTRA_SPACE_REG "0x" + +void troff_output::start_picture(double sc, + const position &ll, const position &ur) +{ + upper_left.x = ll.x; + upper_left.y = ur.y; + scale = compute_scale(sc, ll, ur); + height = (ur.y - ll.y)/scale; + double width = (ur.x - ll.x)/scale; + printf(".PS %.3fi %.3fi", height, width); + if (args) + printf(" %s\n", args); + else + putchar('\n'); + printf(".\\\" %g %g %g %g\n", ll.x, ll.y, ur.x, ur.y); + printf(".\\\" %.3fi %.3fi %.3fi %.3fi\n", 0.0, height, width, 0.0); + printf(".nr " FILL_REG " \\n(.u\n.nf\n"); + printf(".nr " EQN_NO_EXTRA_SPACE_REG " 1\n"); + // This guarantees that if the picture is used in a diversion it will + // have the right width. + printf("\\h'%.3fi'\n.sp -1\n", width); +} + +void troff_output::finish_picture() +{ + line_thickness(BAD_THICKNESS); + last_fill = -1.0; // force it to be reset for each picture + reset_color(); + if (!(want_flyback || want_alternate_flyback)) + printf(".sp %.3fi+1\n", height); + printf(".if \\n(" FILL_REG " .fi\n"); + printf(".br\n"); + printf(".nr " EQN_NO_EXTRA_SPACE_REG " 0\n"); + // this is a little gross + set_location(current_filename, current_lineno); + if (want_flyback) + fputs(".PF\n", stdout); + else if (want_alternate_flyback) + fputs(".PY\n", stdout); + else + fputs(".PE\n", stdout); +} + +void troff_output::command(const char *s, + const char *filename, int lineno) +{ + if (filename != 0) + set_location(filename, lineno); + fputs(s, stdout); + putchar('\n'); +} + +void troff_output::simple_circle(int filled, const position ¢, double rad) +{ + position c = transform(cent); + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'%c %.3fi'" + "\n.sp -1\n", + c.x - rad/scale, + c.y, + (filled ? 'C' : 'c'), + rad*2.0/scale); +} + +void troff_output::simple_ellipse(int filled, const position ¢, + const distance &dim) +{ + position c = transform(cent); + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'%c %.3fi %.3fi'" + "\n.sp -1\n", + c.x - dim.x/(2.0*scale), + c.y, + (filled ? 'E' : 'e'), + dim.x/scale, dim.y/scale); +} + +void troff_output::simple_arc(const position &start, const distance ¢, + const distance &end) +{ + position s = transform(start); + position c = transform(cent); + distance cv = c - s; + distance ev = transform(end) - c; + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'a %.3fi %.3fi %.3fi %.3fi'" + "\n.sp -1\n", + s.x, s.y, cv.x, cv.y, ev.x, ev.y); +} + +void troff_output::simple_line(const position &start, const position &end) +{ + position s = transform(start); + distance ev = transform(end) - s; + printf("\\h'%.3fi'" + "\\v'%.3fi'" + "\\D'l %.3fi %.3fi'" + "\n.sp -1\n", + s.x, s.y, ev.x, ev.y); +} + +void troff_output::simple_spline(const position &start, + const position *v, int n) +{ + position pos = transform(start); + printf("\\h'%.3fi'" + "\\v'%.3fi'", + pos.x, pos.y); + fputs("\\D'~ ", stdout); + for (int i = 0; i < n; i++) { + position temp = transform(v[i]); + distance d = temp - pos; + pos = temp; + if (i != 0) + putchar(' '); + printf("%.3fi %.3fi", d.x, d.y); + } + printf("'\n.sp -1\n"); +} + +// a solid polygon + +void troff_output::simple_polygon(int filled, const position *v, int n) +{ + position pos = transform(v[0]); + printf("\\h'%.3fi'" + "\\v'%.3fi'", + pos.x, pos.y); + printf("\\D'%c ", (filled ? 'P' : 'p')); + for (int i = 1; i < n; i++) { + position temp = transform(v[i]); + distance d = temp - pos; + pos = temp; + if (i != 1) + putchar(' '); + printf("%.3fi %.3fi", d.x, d.y); + } + printf("'\n.sp -1\n"); +} + +const double TEXT_AXIS = 0.22; // in ems + +static const char *choose_delimiter(const char *text) +{ + if (strchr(text, '\'') == 0) + return "'"; + else + return "\\(ts"; +} + +void troff_output::text(const position ¢er, text_piece *v, int n, + double ang) +{ + line_thickness(BAD_THICKNESS); // text might use lines (e.g., in equations) + int rotate_flag = 0; + if (driver_extension_flag && ang != 0.0) { + rotate_flag = 1; + position c = transform(center); + printf(".if \\n(" GROPS_REG " \\{\\\n" + "\\h'%.3fi'" + "\\v'%.3fi'" + "\\X'ps: exec gsave currentpoint 2 copy translate %.4f rotate neg exch neg exch translate'" + "\n.sp -1\n" + ".\\}\n", + c.x, c.y, -ang*180.0/M_PI); + } + for (int i = 0; i < n; i++) + if (v[i].text != 0 && *v[i].text != '\0') { + position c = transform(center); + if (v[i].filename != 0) + set_location(v[i].filename, v[i].lineno); + printf("\\h'%.3fi", c.x); + const char *delim = choose_delimiter(v[i].text); + if (v[i].adj.h == RIGHT_ADJUST) + printf("-\\w%s%s%su", delim, v[i].text, delim); + else if (v[i].adj.h != LEFT_ADJUST) + printf("-(\\w%s%s%su/2u)", delim, v[i].text, delim); + putchar('\''); + printf("\\v'%.3fi-(%dv/2u)+%dv+%.2fm", + c.y, + n - 1, + i, + TEXT_AXIS); + if (v[i].adj.v == ABOVE_ADJUST) + printf("-.5v"); + else if (v[i].adj.v == BELOW_ADJUST) + printf("+.5v"); + putchar('\''); + fputs(v[i].text, stdout); + fputs("\n.sp -1\n", stdout); + } + if (rotate_flag) + printf(".if \\n(" GROPS_REG " \\{\\\n" + "\\X'ps: exec grestore'\n.sp -1\n" + ".\\}\n"); +} + +void troff_output::line_thickness(double p) +{ + if (p < 0.0) + p = RELATIVE_THICKNESS; + if (driver_extension_flag && p != last_line_thickness) { + printf("\\D't %.3fp'\\h'%.3fp'\n.sp -1\n", p, -p); + last_line_thickness = p; + } +} + +void troff_output::set_fill(double f) +{ + if (driver_extension_flag && f != last_fill) { + // \D'Fg ...' emits a node only in compatibility mode, + // thus we add a dummy node + printf("\\&\\D'Fg %.3f'\n.sp -1\n", 1.0 - f); + last_fill = f; + } + if (last_filled) { + free(last_filled); + last_filled = 0; + printf(".fcolor\n"); + } +} + +void troff_output::set_color(char *color_fill, char *color_outlined) +{ + if (driver_extension_flag) { + if (last_filled || last_outlined) { + reset_color(); + } + // .gcolor and .fcolor emit a node in compatibility mode only, + // but that won't work anyway + if (color_fill) { + printf(".fcolor %s\n", color_fill); + last_filled = strsave(color_fill); + } + if (color_outlined) { + printf(".gcolor %s\n", color_outlined); + last_outlined = strsave(color_outlined); + } + } +} + +void troff_output::reset_color() +{ + if (driver_extension_flag) { + if (last_filled) { + printf(".fcolor\n"); + free(last_filled); + last_filled = 0; + } + if (last_outlined) { + printf(".gcolor\n"); + free(last_outlined); + last_outlined = 0; + } + } +} + +char *troff_output::get_last_filled() +{ + return last_filled; +} + +char *troff_output::get_outline_color() +{ + return last_outlined; +} + +const double DOT_AXIS = .044; + +void troff_output::dot(const position ¢, const line_type <) +{ + if (driver_extension_flag) { + line_thickness(lt.thickness); + simple_line(cent, cent); + } + else { + position c = transform(cent); + printf("\\h'%.3fi-(\\w'.'u/2u)'" + "\\v'%.3fi+%.2fm'" + ".\n.sp -1\n", + c.x, + c.y, + DOT_AXIS); + } +} + +void troff_output::set_location(const char *s, int n) +{ + if (last_filename != 0 && strcmp(s, last_filename) == 0) + printf(".lf %d\n", n); + else { + printf(".lf %d %s\n", n, s); + char *lfn = strdup(s); + if (0 == lfn) + fatal("memory allocation failure while copying file name"); + last_filename = lfn; + } +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: |