// -*- C++ -*- /* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005 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. */ #include "pic.h" #include "ptable.h" #include "object.h" void print_object_list(object *); line_type::line_type() : type(solid), thickness(1.0) { } output::output() : args(0), desired_height(0.0), desired_width(0.0) { } output::~output() { a_delete args; } void output::set_desired_width_height(double wid, double ht) { desired_width = wid; desired_height = ht; } void output::set_args(const char *s) { a_delete args; if (s == 0 || *s == '\0') args = 0; else args = strsave(s); } int output::supports_filled_polygons() { return 0; } void output::begin_block(const position &, const position &) { } void output::end_block() { } double output::compute_scale(double sc, const position &ll, const position &ur) { distance dim = ur - ll; if (desired_width != 0.0 || desired_height != 0.0) { sc = 0.0; if (desired_width != 0.0) { if (dim.x == 0.0) error("width specified for picture with zero width"); else sc = dim.x/desired_width; } if (desired_height != 0.0) { if (dim.y == 0.0) error("height specified for picture with zero height"); else { double tem = dim.y/desired_height; if (tem > sc) sc = tem; } } return sc == 0.0 ? 1.0 : sc; } else { if (sc <= 0.0) sc = 1.0; distance sdim = dim/sc; double max_width = 0.0; lookup_variable("maxpswid", &max_width); double max_height = 0.0; lookup_variable("maxpsht", &max_height); if ((max_width > 0.0 && sdim.x > max_width) || (max_height > 0.0 && sdim.y > max_height)) { double xscale = dim.x/max_width; double yscale = dim.y/max_height; return xscale > yscale ? xscale : yscale; } else return sc; } } position::position(const place &pl) { if (pl.obj != 0) { // Use two statements to work around bug in SGI C++. object *tem = pl.obj; *this = tem->origin(); } else { x = pl.x; y = pl.y; } } position::position() : x(0.0), y(0.0) { } position::position(double a, double b) : x(a), y(b) { } int operator==(const position &a, const position &b) { return a.x == b.x && a.y == b.y; } int operator!=(const position &a, const position &b) { return a.x != b.x || a.y != b.y; } position &position::operator+=(const position &a) { x += a.x; y += a.y; return *this; } position &position::operator-=(const position &a) { x -= a.x; y -= a.y; return *this; } position &position::operator*=(double a) { x *= a; y *= a; return *this; } position &position::operator/=(double a) { x /= a; y /= a; return *this; } position operator-(const position &a) { return position(-a.x, -a.y); } position operator+(const position &a, const position &b) { return position(a.x + b.x, a.y + b.y); } position operator-(const position &a, const position &b) { return position(a.x - b.x, a.y - b.y); } position operator/(const position &a, double n) { return position(a.x/n, a.y/n); } position operator*(const position &a, double n) { return position(a.x*n, a.y*n); } // dot product double operator*(const position &a, const position &b) { return a.x*b.x + a.y*b.y; } double hypot(const position &a) { return groff_hypot(a.x, a.y); } struct arrow_head_type { double height; double width; int solid; }; void draw_arrow(const position &pos, const distance &dir, const arrow_head_type &aht, const line_type <, char *outline_color_for_fill) { double hyp = hypot(dir); if (hyp == 0.0) { error("cannot draw arrow on object with zero length"); return; } position base = -dir; base *= aht.height/hyp; position n(dir.y, -dir.x); n *= aht.width/(hyp*2.0); line_type slt = lt; slt.type = line_type::solid; if (aht.solid && out->supports_filled_polygons()) { position v[3]; v[0] = pos; v[1] = pos + base + n; v[2] = pos + base - n; // fill with outline color out->set_color(outline_color_for_fill, outline_color_for_fill); // make stroke thin to avoid arrow sticking slt.thickness = 0.1; out->polygon(v, 3, slt, 1); } else { // use two line segments to avoid arrow sticking out->line(pos + base - n, &pos, 1, slt); out->line(pos + base + n, &pos, 1, slt); } } object::object() : prev(0), next(0) { } object::~object() { } void object::move_by(const position &) { } void object::print() { } void object::print_text() { } int object::blank() { return 0; } struct bounding_box { int blank; position ll; position ur; bounding_box(); void encompass(const position &); }; bounding_box::bounding_box() : blank(1) { } void bounding_box::encompass(const position &pos) { if (blank) { ll = pos; ur = pos; blank = 0; } else { if (pos.x < ll.x) ll.x = pos.x; if (pos.y < ll.y) ll.y = pos.y; if (pos.x > ur.x) ur.x = pos.x; if (pos.y > ur.y) ur.y = pos.y; } } void object::update_bounding_box(bounding_box *) { } position object::origin() { return position(0.0,0.0); } position object::north() { return origin(); } position object::south() { return origin(); } position object::east() { return origin(); } position object::west() { return origin(); } position object::north_east() { return origin(); } position object::north_west() { return origin(); } position object::south_east() { return origin(); } position object::south_west() { return origin(); } position object::start() { return origin(); } position object::end() { return origin(); } position object::center() { return origin(); } double object::width() { return 0.0; } double object::radius() { return 0.0; } double object::height() { return 0.0; } place *object::find_label(const char *) { return 0; } segment::segment(const position &a, int n, segment *p) : is_absolute(n), pos(a), next(p) { } text_item::text_item(char *t, const char *fn, int ln) : next(0), text(t), filename(fn), lineno(ln) { adj.h = CENTER_ADJUST; adj.v = NONE_ADJUST; } text_item::~text_item() { a_delete text; } object_spec::object_spec(object_type t) : type(t) { flags = 0; tbl = 0; segment_list = 0; segment_width = segment_height = 0.0; segment_is_absolute = 0; text = 0; shaded = 0; outlined = 0; with = 0; dir = RIGHT_DIRECTION; } object_spec::~object_spec() { delete tbl; while (segment_list != 0) { segment *tem = segment_list; segment_list = segment_list->next; delete tem; } object *p = oblist.head; while (p != 0) { object *tem = p; p = p->next; delete tem; } while (text != 0) { text_item *tem = text; text = text->next; delete tem; } delete with; a_delete shaded; a_delete outlined; } class command_object : public object { char *s; const char *filename; int lineno; public: command_object(char *, const char *, int); ~command_object(); object_type type() { return OTHER_OBJECT; } void print(); }; command_object::command_object(char *p, const char *fn, int ln) : s(p), filename(fn), lineno(ln) { } command_object::~command_object() { a_delete s; } void command_object::print() { out->command(s, filename, lineno); } object *make_command_object(char *s, const char *fn, int ln) { return new command_object(s, fn, ln); } class mark_object : public object { public: mark_object(); object_type type(); }; object *make_mark_object() { return new mark_object(); } mark_object::mark_object() { } object_type mark_object::type() { return MARK_OBJECT; } object_list::object_list() : head(0), tail(0) { } void object_list::append(object *obj) { if (tail == 0) { obj->next = obj->prev = 0; head = tail = obj; } else { obj->prev = tail; obj->next = 0; tail->next = obj; tail = obj; } } void object_list::wrap_up_block(object_list *ol) { object *p; for (p = tail; p && p->type() != MARK_OBJECT; p = p->prev) ; assert(p != 0); ol->head = p->next; if (ol->head) { ol->tail = tail; ol->head->prev = 0; } else ol->tail = 0; tail = p->prev; if (tail) tail->next = 0; else head = 0; delete p; } text_piece::text_piece() : text(0), filename(0), lineno(-1) { adj.h = CENTER_ADJUST; adj.v = NONE_ADJUST; } text_piece::~text_piece() { a_delete text; } class graphic_object : public object { int ntext; text_piece *text; int aligned; protected: line_type lt; char *outline_color; char *color_fill; public: graphic_object(); ~graphic_object(); object_type type() = 0; void print_text(); void add_text(text_item *, int); void set_dotted(double); void set_dashed(double); void set_thickness(double); void set_invisible(); void set_outline_color(char *); char *get_outline_color(); virtual void set_fill(double); virtual void set_fill_color(char *); }; graphic_object::graphic_object() : ntext(0), text(0), aligned(0), outline_color(0), color_fill(0) { } void graphic_object::set_dotted(double wid) { lt.type = line_type::dotted; lt.dash_width = wid; } void graphic_object::set_dashed(double wid) { lt.type = line_type::dashed; lt.dash_width = wid; } void graphic_object::set_thickness(double th) { lt.thickness = th; } void graphic_object::set_fill(double) { } void graphic_object::set_fill_color(char *c) { color_fill = strsave(c); } void graphic_object::set_outline_color(char *c) { outline_color = strsave(c); } char *graphic_object::get_outline_color() { return outline_color; } void graphic_object::set_invisible() { lt.type = line_type::invisible; } void graphic_object::add_text(text_item *t, int a) { aligned = a; int len = 0; text_item *p; for (p = t; p; p = p->next) len++; if (len == 0) text = 0; else { text = new text_piece[len]; for (p = t, len = 0; p; p = p->next, len++) { text[len].text = p->text; p->text = 0; text[len].adj = p->adj; text[len].filename = p->filename; text[len].lineno = p->lineno; } } ntext = len; } void graphic_object::print_text() { double angle = 0.0; if (aligned) { position d(end() - start()); if (d.x != 0.0 || d.y != 0.0) angle = atan2(d.y, d.x); } if (text != 0) { out->set_color(color_fill, get_outline_color()); out->text(center(), text, ntext, angle); out->reset_color(); } } graphic_object::~graphic_object() { if (text) ad_delete(ntext) text; } class rectangle_object : public graphic_object { protected: position cent; position dim; public: rectangle_object(const position &); double width() { return dim.x; } double height() { return dim.y; } position origin() { return cent; } position center() { return cent; } position north() { return position(cent.x, cent.y + dim.y/2.0); } position south() { return position(cent.x, cent.y - dim.y/2.0); } position east() { return position(cent.x + dim.x/2.0, cent.y); } position west() { return position(cent.x - dim.x/2.0, cent.y); } position north_east() { return position(cent.x + dim.x/2.0, cent.y + dim.y/2.0); } position north_west() { return position(cent.x - dim.x/2.0, cent.y + dim.y/2.0); } position south_east() { return position(cent.x + dim.x/2.0, cent.y - dim.y/2.0); } position south_west() { return position(cent.x - dim.x/2.0, cent.y - dim.y/2.0); } object_type type() = 0; void update_bounding_box(bounding_box *); void move_by(const position &); }; rectangle_object::rectangle_object(const position &d) : dim(d) { } void rectangle_object::update_bounding_box(bounding_box *p) { p->encompass(cent - dim/2.0); p->encompass(cent + dim/2.0); } void rectangle_object::move_by(const position &a) { cent += a; } class closed_object : public rectangle_object { public: closed_object(const position &); object_type type() = 0; void set_fill(double); void set_fill_color(char *fill); protected: double fill; // < 0 if not filled char *color_fill; // = 0 if not colored }; closed_object::closed_object(const position &pos) : rectangle_object(pos), fill(-1.0), color_fill(0) { } void closed_object::set_fill(double f) { assert(f >= 0.0); fill = f; } void closed_object::set_fill_color(char *f) { color_fill = strsave(f); } class box_object : public closed_object { double xrad; double yrad; public: box_object(const position &, double); object_type type() { return BOX_OBJECT; } void print(); position north_east(); position north_west(); position south_east(); position south_west(); }; box_object::box_object(const position &pos, double r) : closed_object(pos), xrad(dim.x > 0 ? r : -r), yrad(dim.y > 0 ? r : -r) { } const double CHOP_FACTOR = 1.0 - 1.0/M_SQRT2; position box_object::north_east() { return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad, cent.y + dim.y/2.0 - CHOP_FACTOR*yrad); } position box_object::north_west() { return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad, cent.y + dim.y/2.0 - CHOP_FACTOR*yrad); } position box_object::south_east() { return position(cent.x + dim.x/2.0 - CHOP_FACTOR*xrad, cent.y - dim.y/2.0 + CHOP_FACTOR*yrad); } position box_object::south_west() { return position(cent.x - dim.x/2.0 + CHOP_FACTOR*xrad, cent.y - dim.y/2.0 + CHOP_FACTOR*yrad); } void box_object::print() { if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0) return; out->set_color(color_fill, graphic_object::get_outline_color()); if (xrad == 0.0) { distance dim2 = dim/2.0; position vec[4]; vec[0] = cent + position(dim2.x, -dim2.y); vec[1] = cent + position(dim2.x, dim2.y); vec[2] = cent + position(-dim2.x, dim2.y); vec[3] = cent + position(-dim2.x, -dim2.y); out->polygon(vec, 4, lt, fill); } else { distance abs_dim(fabs(dim.x), fabs(dim.y)); out->rounded_box(cent, abs_dim, fabs(xrad), lt, fill); } out->reset_color(); } graphic_object *object_spec::make_box(position *curpos, direction *dirp) { static double last_box_height; static double last_box_width; static double last_box_radius; static int have_last_box = 0; if (!(flags & HAS_HEIGHT)) { if ((flags & IS_SAME) && have_last_box) height = last_box_height; else lookup_variable("boxht", &height); } if (!(flags & HAS_WIDTH)) { if ((flags & IS_SAME) && have_last_box) width = last_box_width; else lookup_variable("boxwid", &width); } if (!(flags & HAS_RADIUS)) { if ((flags & IS_SAME) && have_last_box) radius = last_box_radius; else lookup_variable("boxrad", &radius); } last_box_width = width; last_box_height = height; last_box_radius = radius; have_last_box = 1; radius = fabs(radius); if (radius*2.0 > fabs(width)) radius = fabs(width/2.0); if (radius*2.0 > fabs(height)) radius = fabs(height/2.0); box_object *p = new box_object(position(width, height), radius); if (!position_rectangle(p, curpos, dirp)) { delete p; p = 0; } return p; } // return non-zero for success int object_spec::position_rectangle(rectangle_object *p, position *curpos, direction *dirp) { position pos; dir = *dirp; // ignore any direction in attribute list position motion; switch (dir) { case UP_DIRECTION: motion.y = p->height()/2.0; break; case DOWN_DIRECTION: motion.y = -p->height()/2.0; break; case LEFT_DIRECTION: motion.x = -p->width()/2.0; break; case RIGHT_DIRECTION: motion.x = p->width()/2.0; break; default: assert(0); } if (flags & HAS_AT) { pos = at; if (flags & HAS_WITH) { place offset; place here; here.obj = p; if (!with->follow(here, &offset)) return 0; pos -= offset; } } else { pos = *curpos; pos += motion; } p->move_by(pos); pos += motion; *curpos = pos; return 1; } class block_object : public rectangle_object { object_list oblist; PTABLE(place) *tbl; public: block_object(const position &, const object_list &ol, PTABLE(place) *t); ~block_object(); place *find_label(const char *); object_type type(); void move_by(const position &); void print(); }; block_object::block_object(const position &d, const object_list &ol, PTABLE(place) *t) : rectangle_object(d), oblist(ol), tbl(t) { } block_object::~block_object() { delete tbl; object *p = oblist.head; while (p != 0) { object *tem = p; p = p->next; delete tem; } } void block_object::print() { out->begin_block(south_west(), north_east()); print_object_list(oblist.head); out->end_block(); } static void adjust_objectless_places(PTABLE(place) *tbl, const position &a) { // Adjust all the labels that aren't attached to objects. PTABLE_ITERATOR(place) iter(tbl); const char *key; place *pl; while (iter.next(&key, &pl)) if (key && csupper(key[0]) && pl->obj == 0) { pl->x += a.x; pl->y += a.y; } } void block_object::move_by(const position &a) { cent += a; for (object *p = oblist.head; p; p = p->next) p->move_by(a); adjust_objectless_places(tbl, a); } place *block_object::find_label(const char *name) { return tbl->lookup(name); } object_type block_object::type() { return BLOCK_OBJECT; } graphic_object *object_spec::make_block(position *curpos, direction *dirp) { bounding_box bb; for (object *p = oblist.head; p; p = p->next) p->update_bounding_box(&bb); position dim; if (!bb.blank) { position m = -(bb.ll + bb.ur)/2.0; for (object *p = oblist.head; p; p = p->next) p->move_by(m); adjust_objectless_places(tbl, m); dim = bb.ur - bb.ll; } if (flags & HAS_WIDTH) dim.x = width; if (flags & HAS_HEIGHT) dim.y = height; block_object *block = new block_object(dim, oblist, tbl); if (!position_rectangle(block, curpos, dirp)) { delete block; block = 0; } tbl = 0; oblist.head = oblist.tail = 0; return block; } class text_object : public rectangle_object { public: text_object(const position &); object_type type() { return TEXT_OBJECT; } }; text_object::text_object(const position &d) : rectangle_object(d) { } graphic_object *object_spec::make_text(position *curpos, direction *dirp) { if (!(flags & HAS_HEIGHT)) { lookup_variable("textht", &height); int nitems = 0; for (text_item *t = text; t; t = t->next) nitems++; height *= nitems; } if (!(flags & HAS_WIDTH)) lookup_variable("textwid", &width); text_object *p = new text_object(position(width, height)); if (!position_rectangle(p, curpos, dirp)) { delete p; p = 0; } return p; } class ellipse_object : public closed_object { public: ellipse_object(const position &); position north_east() { return position(cent.x + dim.x/(M_SQRT2*2.0), cent.y + dim.y/(M_SQRT2*2.0)); } position north_west() { return position(cent.x - dim.x/(M_SQRT2*2.0), cent.y + dim.y/(M_SQRT2*2.0)); } position south_east() { return position(cent.x + dim.x/(M_SQRT2*2.0), cent.y - dim.y/(M_SQRT2*2.0)); } position south_west() { return position(cent.x - dim.x/(M_SQRT2*2.0), cent.y - dim.y/(M_SQRT2*2.0)); } double radius() { return dim.x/2.0; } object_type type() { return ELLIPSE_OBJECT; } void print(); }; ellipse_object::ellipse_object(const position &d) : closed_object(d) { } void ellipse_object::print() { if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0) return; out->set_color(color_fill, graphic_object::get_outline_color()); out->ellipse(cent, dim, lt, fill); out->reset_color(); } graphic_object *object_spec::make_ellipse(position *curpos, direction *dirp) { static double last_ellipse_height; static double last_ellipse_width; static int have_last_ellipse = 0; if (!(flags & HAS_HEIGHT)) { if ((flags & IS_SAME) && have_last_ellipse) height = last_ellipse_height; else lookup_variable("ellipseht", &height); } if (!(flags & HAS_WIDTH)) { if ((flags & IS_SAME) && have_last_ellipse) width = last_ellipse_width; else lookup_variable("ellipsewid", &width); } last_ellipse_width = width; last_ellipse_height = height; have_last_ellipse = 1; ellipse_object *p = new ellipse_object(position(width, height)); if (!position_rectangle(p, curpos, dirp)) { delete p; return 0; } return p; } class circle_object : public ellipse_object { public: circle_object(double); object_type type() { return CIRCLE_OBJECT; } void print(); }; circle_object::circle_object(double diam) : ellipse_object(position(diam, diam)) { } void circle_object::print() { if (lt.type == line_type::invisible && fill < 0.0 && color_fill == 0) return; out->set_color(color_fill, graphic_object::get_outline_color()); out->circle(cent, dim.x/2.0, lt, fill); out->reset_color(); } graphic_object *object_spec::make_circle(position *curpos, direction *dirp) { static double last_circle_radius; static int have_last_circle = 0; if (!(flags & HAS_RADIUS)) { if ((flags & IS_SAME) && have_last_circle) radius = last_circle_radius; else lookup_variable("circlerad", &radius); } last_circle_radius = radius; have_last_circle = 1; circle_object *p = new circle_object(radius*2.0); if (!position_rectangle(p, curpos, dirp)) { delete p; return 0; } return p; } class move_object : public graphic_object { position strt; position en; public: move_object(const position &s, const position &e); position origin() { return en; } object_type type() { return MOVE_OBJECT; } void update_bounding_box(bounding_box *); void move_by(const position &); }; move_object::move_object(const position &s, const position &e) : strt(s), en(e) { } void move_object::update_bounding_box(bounding_box *p) { p->encompass(strt); p->encompass(en); } void move_object::move_by(const position &a) { strt += a; en += a; } graphic_object *object_spec::make_move(position *curpos, direction *dirp) { static position last_move; static int have_last_move = 0; *dirp = dir; // No need to look at at since `at' attribute sets `from' attribute. position startpos = (flags & HAS_FROM) ? from : *curpos; if (!(flags & HAS_SEGMENT)) { if ((flags & IS_SAME) && have_last_move) segment_pos = last_move; else { switch (dir) { case UP_DIRECTION: segment_pos.y = segment_height; break; case DOWN_DIRECTION: segment_pos.y = -segment_height; break; case LEFT_DIRECTION: segment_pos.x = -segment_width; break; case RIGHT_DIRECTION: segment_pos.x = segment_width; break; default: assert(0); } } } segment_list = new segment(segment_pos, segment_is_absolute, segment_list); // Reverse the segment_list so that it's in forward order. segment *old = segment_list; segment_list = 0; while (old != 0) { segment *tem = old->next; old->next = segment_list; segment_list = old; old = tem; } // Compute the end position. position endpos = startpos; for (segment *s = segment_list; s; s = s->next) if (s->is_absolute) endpos = s->pos; else endpos += s->pos; have_last_move = 1; last_move = endpos - startpos; move_object *p = new move_object(startpos, endpos); *curpos = endpos; return p; } class linear_object : public graphic_object { protected: char arrow_at_start; char arrow_at_end; arrow_head_type aht; position strt; position en; public: linear_object(const position &s, const position &e); position start() { return strt; } position end() { return en; } void move_by(const position &); void update_bounding_box(bounding_box *) = 0; object_type type() = 0; void add_arrows(int at_start, int at_end, const arrow_head_type &); }; class line_object : public linear_object { protected: position *v; int n; public: line_object(const position &s, const position &e, position *, int); ~line_object(); position origin() { return strt; } position center() { return (strt + en)/2.0; } position north() { return (en.y - strt.y) > 0 ? en : strt; } position south() { return (en.y - strt.y) < 0 ? en : strt; } position east() { return (en.x - strt.x) > 0 ? en : strt; } position west() { return (en.x - strt.x) < 0 ? en : strt; } object_type type() { return LINE_OBJECT; } void update_bounding_box(bounding_box *); void print(); void move_by(const position &); }; class arrow_object : public line_object { public: arrow_object(const position &, const position &, position *, int); object_type type() { return ARROW_OBJECT; } }; class spline_object : public line_object { public: spline_object(const position &, const position &, position *, int); object_type type() { return SPLINE_OBJECT; } void print(); void update_bounding_box(bounding_box *); }; linear_object::linear_object(const position &s, const position &e) : arrow_at_start(0), arrow_at_end(0), strt(s), en(e) { } void linear_object::move_by(const position &a) { strt += a; en += a; } void linear_object::add_arrows(int at_start, int at_end, const arrow_head_type &a) { arrow_at_start = at_start; arrow_at_end = at_end; aht = a; } line_object::line_object(const position &s, const position &e, position *p, int i) : linear_object(s, e), v(p), n(i) { } void line_object::print() { if (lt.type == line_type::invisible) return; out->set_color(0, graphic_object::get_outline_color()); // shorten line length to avoid arrow sticking. position sp = strt; if (arrow_at_start) { position base = v[0] - strt; double hyp = hypot(base); if (hyp == 0.0) { error("cannot draw arrow on object with zero length"); return; } if (aht.solid && out->supports_filled_polygons()) { base *= aht.height / hyp; draw_arrow(strt, strt - v[0], aht, lt, graphic_object::get_outline_color()); sp = strt + base; } else { base *= fabs(lt.thickness) / hyp / 72 / 4; sp = strt + base; draw_arrow(sp, sp - v[0], aht, lt, graphic_object::get_outline_color()); } } if (arrow_at_end) { position base = v[n-1] - (n > 1 ? v[n-2] : strt); double hyp = hypot(base); if (hyp == 0.0) { error("cannot draw arrow on object with zero length"); return; } if (aht.solid && out->supports_filled_polygons()) { base *= aht.height / hyp; draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, graphic_object::get_outline_color()); v[n-1] = en - base; } else { base *= fabs(lt.thickness) / hyp / 72 / 4; v[n-1] = en - base; draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, graphic_object::get_outline_color()); } } out->line(sp, v, n, lt); out->reset_color(); } void line_object::update_bounding_box(bounding_box *p) { p->encompass(strt); for (int i = 0; i < n; i++) p->encompass(v[i]); } void line_object::move_by(const position &pos) { linear_object::move_by(pos); for (int i = 0; i < n; i++) v[i] += pos; } void spline_object::update_bounding_box(bounding_box *p) { p->encompass(strt); p->encompass(en); /* If p1 = q1/2 + q2/2 p2 = q1/6 + q2*5/6 p3 = q2*5/6 + q3/6 p4 = q2/2 + q3/2 [ the points for the Bezier cubic ] and t = .5 then (1-t)^3*p1 + 3*t*(t - 1)^2*p2 + 3*t^2*(1-t)*p3 + t^3*p4 [ the equation for the Bezier cubic ] = .125*q1 + .75*q2 + .125*q3 */ for (int i = 1; i < n; i++) p->encompass((i == 1 ? strt : v[i-2])*.125 + v[i-1]*.75 + v[i]*.125); } arrow_object::arrow_object(const position &s, const position &e, position *p, int i) : line_object(s, e, p, i) { } spline_object::spline_object(const position &s, const position &e, position *p, int i) : line_object(s, e, p, i) { } void spline_object::print() { if (lt.type == line_type::invisible) return; out->set_color(0, graphic_object::get_outline_color()); // shorten line length for spline to avoid arrow sticking position sp = strt; if (arrow_at_start) { position base = v[0] - strt; double hyp = hypot(base); if (hyp == 0.0) { error("cannot draw arrow on object with zero length"); return; } if (aht.solid && out->supports_filled_polygons()) { base *= aht.height / hyp; draw_arrow(strt, strt - v[0], aht, lt, graphic_object::get_outline_color()); sp = strt + base*0.1; // to reserve spline shape } else { base *= fabs(lt.thickness) / hyp / 72 / 4; sp = strt + base; draw_arrow(sp, sp - v[0], aht, lt, graphic_object::get_outline_color()); } } if (arrow_at_end) { position base = v[n-1] - (n > 1 ? v[n-2] : strt); double hyp = hypot(base); if (hyp == 0.0) { error("cannot draw arrow on object with zero length"); return; } if (aht.solid && out->supports_filled_polygons()) { base *= aht.height / hyp; draw_arrow(en, v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, graphic_object::get_outline_color()); v[n-1] = en - base*0.1; // to reserve spline shape } else { base *= fabs(lt.thickness) / hyp / 72 / 4; v[n-1] = en - base; draw_arrow(v[n-1], v[n-1] - (n > 1 ? v[n-2] : strt), aht, lt, graphic_object::get_outline_color()); } } out->spline(sp, v, n, lt); out->reset_color(); } line_object::~line_object() { a_delete v; } linear_object *object_spec::make_line(position *curpos, direction *dirp) { static position last_line; static int have_last_line = 0; *dirp = dir; // No need to look at at since `at' attribute sets `from' attribute. position startpos = (flags & HAS_FROM) ? from : *curpos; if (!(flags & HAS_SEGMENT)) { if ((flags & IS_SAME) && (type == LINE_OBJECT || type == ARROW_OBJECT) && have_last_line) segment_pos = last_line; else switch (dir) { case UP_DIRECTION: segment_pos.y = segment_height; break; case DOWN_DIRECTION: segment_pos.y = -segment_height; break; case LEFT_DIRECTION: segment_pos.x = -segment_width; break; case RIGHT_DIRECTION: segment_pos.x = segment_width; break; default: assert(0); } } segment_list = new segment(segment_pos, segment_is_absolute, segment_list); // reverse the segment_list so that it's in forward order segment *old = segment_list; segment_list = 0; while (old != 0) { segment *tem = old->next; old->next = segment_list; segment_list = old; old = tem; } // Absolutise all movements position endpos = startpos; int nsegments = 0; segment *s; for (s = segment_list; s; s = s->next, nsegments++) if (s->is_absolute) endpos = s->pos; else { endpos += s->pos; s->pos = endpos; s->is_absolute = 1; // to avoid confusion } // handle chop line_object *p = 0; position *v = new position[nsegments]; int i = 0; for (s = segment_list; s; s = s->next, i++) v[i] = s->pos; if (flags & IS_DEFAULT_CHOPPED) { lookup_variable("circlerad", &start_chop); end_chop = start_chop; flags |= IS_CHOPPED; } if (flags & IS_CHOPPED) { position start_chop_vec, end_chop_vec; if (start_chop != 0.0) { start_chop_vec = v[0] - startpos; start_chop_vec *= start_chop / hypot(start_chop_vec); } if (end_chop != 0.0) { end_chop_vec = (v[nsegments - 1] - (nsegments > 1 ? v[nsegments - 2] : startpos)); end_chop_vec *= end_chop / hypot(end_chop_vec); } startpos += start_chop_vec; v[nsegments - 1] -= end_chop_vec; endpos -= end_chop_vec; } switch (type) { case SPLINE_OBJECT: p = new spline_object(startpos, endpos, v, nsegments); break; case ARROW_OBJECT: p = new arrow_object(startpos, endpos, v, nsegments); break; case LINE_OBJECT: p = new line_object(startpos, endpos, v, nsegments); break; default: assert(0); } have_last_line = 1; last_line = endpos - startpos; *curpos = endpos; return p; } class arc_object : public linear_object { int clockwise; position cent; double rad; public: arc_object(int, const position &, const position &, const position &); position origin() { return cent; } position center() { return cent; } double radius() { return rad; } position north(); position south(); position east(); position west(); position north_east(); position north_west(); position south_east(); position south_west(); void update_bounding_box(bounding_box *); object_type type() { return ARC_OBJECT; } void print(); void move_by(const position &pos); }; arc_object::arc_object(int cw, const position &s, const position &e, const position &c) : linear_object(s, e), clockwise(cw), cent(c) { rad = hypot(c - s); } void arc_object::move_by(const position &pos) { linear_object::move_by(pos); cent += pos; } // we get arc corners from the corresponding circle position arc_object::north() { position result(cent); result.y += rad; return result; } position arc_object::south() { position result(cent); result.y -= rad; return result; } position arc_object::east() { position result(cent); result.x += rad; return result; } position arc_object::west() { position result(cent); result.x -= rad; return result; } position arc_object::north_east() { position result(cent); result.x += rad/M_SQRT2; result.y += rad/M_SQRT2; return result; } position arc_object::north_west() { position result(cent); result.x -= rad/M_SQRT2; result.y += rad/M_SQRT2; return result; } position arc_object::south_east() { position result(cent); result.x += rad/M_SQRT2; result.y -= rad/M_SQRT2; return result; } position arc_object::south_west() { position result(cent); result.x -= rad/M_SQRT2; result.y -= rad/M_SQRT2; return result; } void arc_object::print() { if (lt.type == line_type::invisible) return; out->set_color(0, graphic_object::get_outline_color()); // handle arrow direction; make shorter line for arc position sp, ep, b; if (clockwise) { sp = en; ep = strt; } else { sp = strt; ep = en; } if (arrow_at_start) { double theta = aht.height / rad; if (clockwise) theta = - theta; b = strt - cent; b = position(b.x*cos(theta) - b.y*sin(theta), b.x*sin(theta) + b.y*cos(theta)) + cent; if (clockwise) ep = b; else sp = b; if (aht.solid && out->supports_filled_polygons()) { draw_arrow(strt, strt - b, aht, lt, graphic_object::get_outline_color()); } else { position v = b; theta = fabs(lt.thickness) / 72 / 4 / rad; if (clockwise) theta = - theta; b = strt - cent; b = position(b.x*cos(theta) - b.y*sin(theta), b.x*sin(theta) + b.y*cos(theta)) + cent; draw_arrow(b, b - v, aht, lt, graphic_object::get_outline_color()); out->line(b, &v, 1, lt); } } if (arrow_at_end) { double theta = aht.height / rad; if (!clockwise) theta = - theta; b = en - cent; b = position(b.x*cos(theta) - b.y*sin(theta), b.x*sin(theta) + b.y*cos(theta)) + cent; if (clockwise) sp = b; else ep = b; if (aht.solid && out->supports_filled_polygons()) { draw_arrow(en, en - b, aht, lt, graphic_object::get_outline_color()); } else { position v = b; theta = fabs(lt.thickness) / 72 / 4 / rad; if (!clockwise) theta = - theta; b = en - cent; b = position(b.x*cos(theta) - b.y*sin(theta), b.x*sin(theta) + b.y*cos(theta)) + cent; draw_arrow(b, b - v, aht, lt, graphic_object::get_outline_color()); out->line(b, &v, 1, lt); } } out->arc(sp, cent, ep, lt); out->reset_color(); } inline double max(double a, double b) { return a > b ? a : b; } void arc_object::update_bounding_box(bounding_box *p) { p->encompass(strt); p->encompass(en); position start_offset = strt - cent; if (start_offset.x == 0.0 && start_offset.y == 0.0) return; position end_offset = en - cent; if (end_offset.x == 0.0 && end_offset.y == 0.0) return; double start_quad = atan2(start_offset.y, start_offset.x)/(M_PI/2.0); double end_quad = atan2(end_offset.y, end_offset.x)/(M_PI/2.0); if (clockwise) { double temp = start_quad; start_quad = end_quad; end_quad = temp; } if (start_quad < 0.0) start_quad += 4.0; while (end_quad <= start_quad) end_quad += 4.0; double r = max(hypot(start_offset), hypot(end_offset)); for (int q = int(start_quad) + 1; q < end_quad; q++) { position offset; switch (q % 4) { case 0: offset.x = r; break; case 1: offset.y = r; break; case 2: offset.x = -r; break; case 3: offset.y = -r; break; } p->encompass(cent + offset); } } // We ignore the with attribute. The at attribute always refers to the center. linear_object *object_spec::make_arc(position *curpos, direction *dirp) { *dirp = dir; int cw = (flags & IS_CLOCKWISE) != 0; // compute the start position startpos; if (flags & HAS_FROM) startpos = from; else startpos = *curpos; if (!(flags & HAS_RADIUS)) lookup_variable("arcrad", &radius); // compute the end position endpos; if (flags & HAS_TO) endpos = to; else { position m(radius, radius); // Adjust the signs. if (cw) { if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION) m.x = -m.x; if (dir == DOWN_DIRECTION || dir == RIGHT_DIRECTION) m.y = -m.y; *dirp = direction((dir + 3) % 4); } else { if (dir == UP_DIRECTION || dir == LEFT_DIRECTION) m.x = -m.x; if (dir == DOWN_DIRECTION || dir == LEFT_DIRECTION) m.y = -m.y; *dirp = direction((dir + 1) % 4); } endpos = startpos + m; } // compute the center position centerpos; if (flags & HAS_AT) centerpos = at; else if (startpos == endpos) centerpos = startpos; else { position h = (endpos - startpos)/2.0; double d = hypot(h); if (radius <= 0) radius = .25; // make the radius big enough while (radius < d) radius *= 2.0; double alpha = acos(d/radius); double theta = atan2(h.y, h.x); if (cw) theta -= alpha; else theta += alpha; centerpos = position(cos(theta), sin(theta))*radius + startpos; } arc_object *p = new arc_object(cw, startpos, endpos, centerpos); *curpos = endpos; return p; } graphic_object *object_spec::make_linear(position *curpos, direction *dirp) { linear_object *obj; if (type == ARC_OBJECT) obj = make_arc(curpos, dirp); else obj = make_line(curpos, dirp); if (type == ARROW_OBJECT && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD)) == 0) flags |= HAS_RIGHT_ARROW_HEAD; if (obj && (flags & (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD))) { arrow_head_type a; int at_start = (flags & HAS_LEFT_ARROW_HEAD) != 0; int at_end = (flags & HAS_RIGHT_ARROW_HEAD) != 0; if (flags & HAS_HEIGHT) a.height = height; else lookup_variable("arrowht", &a.height); if (flags & HAS_WIDTH) a.width = width; else lookup_variable("arrowwid", &a.width); double solid; lookup_variable("arrowhead", &solid); a.solid = solid != 0.0; obj->add_arrows(at_start, at_end, a); } return obj; } object *object_spec::make_object(position *curpos, direction *dirp) { graphic_object *obj = 0; switch (type) { case BLOCK_OBJECT: obj = make_block(curpos, dirp); break; case BOX_OBJECT: obj = make_box(curpos, dirp); break; case TEXT_OBJECT: obj = make_text(curpos, dirp); break; case ELLIPSE_OBJECT: obj = make_ellipse(curpos, dirp); break; case CIRCLE_OBJECT: obj = make_circle(curpos, dirp); break; case MOVE_OBJECT: obj = make_move(curpos, dirp); break; case ARC_OBJECT: case LINE_OBJECT: case SPLINE_OBJECT: case ARROW_OBJECT: obj = make_linear(curpos, dirp); break; case MARK_OBJECT: case OTHER_OBJECT: default: assert(0); break; } if (obj) { if (flags & IS_INVISIBLE) obj->set_invisible(); if (text != 0) obj->add_text(text, (flags & IS_ALIGNED) != 0); if (flags & IS_DOTTED) obj->set_dotted(dash_width); else if (flags & IS_DASHED) obj->set_dashed(dash_width); double th; if (flags & HAS_THICKNESS) th = thickness; else lookup_variable("linethick", &th); obj->set_thickness(th); if (flags & IS_OUTLINED) obj->set_outline_color(outlined); if (flags & (IS_DEFAULT_FILLED | IS_FILLED)) { if (flags & IS_SHADED) obj->set_fill_color(shaded); else { if (flags & IS_DEFAULT_FILLED) lookup_variable("fillval", &fill); if (fill < 0.0) error("bad fill value %1", fill); else obj->set_fill(fill); } } } return obj; } struct string_list { string_list *next; char *str; string_list(char *); ~string_list(); }; string_list::string_list(char *s) : next(0), str(s) { } string_list::~string_list() { a_delete str; } /* A path is used to hold the argument to the `with' attribute. For example, `.nw' or `.A.s' or `.A'. The major operation on a path is to take a place and follow the path through the place to place within the place. Note that `.A.B.C.sw' will work. For compatibility with DWB pic, `with' accepts positions also (this is incorrectly documented in CSTR 116). */ path::path(corner c) : crn(c), label_list(0), ypath(0), is_position(0) { } path::path(position p) : crn(0), label_list(0), ypath(0), is_position(1) { pos.x = p.x; pos.y = p.y; } path::path(char *l, corner c) : crn(c), ypath(0), is_position(0) { label_list = new string_list(l); } path::~path() { while (label_list) { string_list *tem = label_list; label_list = label_list->next; delete tem; } delete ypath; } void path::append(corner c) { assert(crn == 0); crn = c; } void path::append(char *s) { string_list **p; for (p = &label_list; *p; p = &(*p)->next) ; *p = new string_list(s); } void path::set_ypath(path *p) { ypath = p; } // return non-zero for success int path::follow(const place &pl, place *result) const { if (is_position) { result->x = pos.x; result->y = pos.y; result->obj = 0; return 1; } const place *p = &pl; for (string_list *lb = label_list; lb; lb = lb->next) if (p->obj == 0 || (p = p->obj->find_label(lb->str)) == 0) { lex_error("object does not contain a place `%1'", lb->str); return 0; } if (crn == 0 || p->obj == 0) *result = *p; else { position ps = ((p->obj)->*(crn))(); result->x = ps.x; result->y = ps.y; result->obj = 0; } if (ypath) { place tem; if (!ypath->follow(pl, &tem)) return 0; result->y = tem.y; if (result->obj != tem.obj) result->obj = 0; } return 1; } void print_object_list(object *p) { for (; p; p = p->next) { p->print(); p->print_text(); } } void print_picture(object *obj) { bounding_box bb; for (object *p = obj; p; p = p->next) p->update_bounding_box(&bb); double scale; lookup_variable("scale", &scale); out->start_picture(scale, bb.ll, bb.ur); print_object_list(obj); out->finish_picture(); }