/* This file is part of the GNU plotutils package. Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2005, Free Software Foundation, Inc. The GNU plotutils package is free software. You may 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. The GNU plotutils package 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 the GNU plotutils package; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301, USA. */ // This is almost the same as the object.cc file included in gpic, with // a lot of reformatting and commenting. // The only substantive change is to the arc_object::print method. // Previously, it distinguished clockwise arcs from counterclockwise simply // by interchanging the `start' and `end' points. This interfered with the // libplot driver, in particular with libplot's incremental path // constrution algorithm. Search for `PIC2PLOT' to see the alteration. #define PIC2PLOT #include "pic.h" #include "object.h" #include "output.h" // forward references static void print_object_list(object *p); line_type::line_type() : type(solid), thickness(1.0) { } ////////////////////////////////////////////////////////////////////// // The base OUTPUT class, declared in output.h ////////////////////////////////////////////////////////////////////// output::output() : args(0), desired_height(0.0), desired_width(0.0) { } output::~output() { a_delete args; } void output::command(const char *, const char *, int) { } void output::set_location(const char *, int) { } int output::supports_filled_polygons() { return 0; } void output::begin_block(const position &, const position &) { } void output::end_block() { } 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); } 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 // user didn't specify desired width or height on .PS line { 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; } } ////////////////////////////////////////////////////////////////////// // The POSITION class, declared in libgroff/position.h ////////////////////////////////////////////////////////////////////// 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 hypot(a.x, a.y); } struct arrow_head_type { double height; double width; int solid; }; static void draw_arrow(const position &pos, const distance &dir, const arrow_head_type &aht, const line_type <) { 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; // A value > 1 means fill with the current color. out->polygon(v, 3, slt, 2.0); } else { position v[2]; v[0] = pos; v[1] = pos + base + n; out->line(pos + base - n, v, 2, slt); } } ////////////////////////////////////////////////////////////////////// // The base OBJECT class, declared in object.h ////////////////////////////////////////////////////////////////////// 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; } class bounding_box { public: // ctor bounding_box(); // public functions void encompass(const position &); // public data int blank; position ll; position ur; }; 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; } ////////////////////////////////////////////////////////////////////// // Derived OBJECT classes, declared in object.h ////////////////////////////////////////////////////////////////////// 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; 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; } class command_object : public object { public: // ctor, dtor command_object(char *, const char *, int); ~command_object(); // public functions object_type type() { return OTHER_OBJECT; } void print(); private: char *s; const char *filename; int lineno; }; 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 { public: // ctor, dtor graphic_object(); ~graphic_object(); // public functions 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(); virtual void set_fill(double); protected: line_type lt; private: int ntext; text_piece *text; int aligned; }; graphic_object::graphic_object() : ntext(0), text(0), aligned(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_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->text(center(), text, ntext, angle); } graphic_object::~graphic_object() { if (text) ad_delete(ntext) text; } class rectangle_object : public graphic_object { 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 &); protected: position cent; position dim; }; 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); protected: double fill; // < 0 if not filled }; closed_object::closed_object(const position &pos) : rectangle_object(pos), fill(-1.0) { } void closed_object::set_fill(double f) { assert(f >= 0.0); fill = f; } class box_object : public closed_object { 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(); private: double xrad; double yrad; }; 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) return; 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); } } 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 { public: // ctor, dtor block_object(const position &, const object_list &ol, PTABLE(place) *t); ~block_object(); // public functions place *find_label(const char *); object_type type(); void move_by(const position &); void print(); private: object_list oblist; PTABLE(place) *tbl; }; block_object::block_object(const position &d, const object_list &ol, PTABLE(place) *t) : oblist(ol), tbl(t), rectangle_object(d) { } 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) return; out->ellipse(cent, dim, lt, fill); } 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: // ctor circle_object(double); // public functions 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) return; out->circle(cent, dim.x/2.0, lt, fill); } 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 { public: // ctor linear_object(const position &s, const position &e); // public functions 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 &); protected: char arrow_at_start; char arrow_at_end; arrow_head_type aht; position strt; position en; }; class line_object : public linear_object { public: // ctor, dtor line_object(const position &s, const position &e, position *, int); ~line_object(); // public functions 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 &); protected: position *v; int n; }; 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) : v(p), n(i), linear_object(s, e) { } void line_object::print() { if (lt.type == line_type::invisible) return; out->line(strt, v, n, lt); if (arrow_at_start) draw_arrow(strt, strt-v[0], aht, lt); if (arrow_at_end) draw_arrow(en, v[n-1] - (n > 1 ? v[n - 2] : strt), aht, lt); } 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->spline(strt, v, n, lt); if (arrow_at_start) draw_arrow(strt, strt-v[0], aht, lt); if (arrow_at_end) draw_arrow(en, v[n-1] - (n > 1 ? v[n - 2] : strt), aht, lt); } 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 { 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); private: int clockwise; position cent; double rad; }; 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; #ifndef PIC2PLOT if (clockwise) out->arc(en, cent, strt, lt); else out->arc(strt, cent, en, lt); #else out->arc(strt, cent, en, lt); #endif if (arrow_at_start) { position c = cent - strt; draw_arrow(strt, (clockwise ? position(c.y, -c.x) : position(-c.y, c.x)), aht, lt); } if (arrow_at_end) { position e = en - cent; draw_arrow(en, (clockwise ? position(e.y, -e.x) : position(-e.y, e.x)), aht, lt); } } 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 radius = 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 = radius; break; case 1: offset.y = radius; break; case 2: offset.x = -radius; break; case 3: offset.y = -radius; 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_DEFAULT_FILLED|IS_FILLED)) { 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; } class string_list { public: // ctor, dtor string_list(char *); ~string_list(); // public data string_list *next; char *str; }; string_list::string_list(char *s) : next(0), str(s) { } string_list::~string_list() { a_delete str; } ////////////////////////////////////////////////////////////////////// // The PATH class, declared in object.h ////////////////////////////////////////////////////////////////////// // 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. path::path(corner c) : crn(c), label_list(0), ypath(0) { } path::path(char *l, corner c) : crn(c), ypath(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 { 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 pos = ((p->obj)->*(crn))(); result->x = pos.x; result->y = pos.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; } static void print_object_list(object *p) { for (; p; p = p->next) { p->print(); p->print_text(); } } void print_picture(object *obj) { bounding_box bb; double scale; for (object *p = obj; p; p = p->next) p->update_bounding_box (&bb); lookup_variable ("scale", &scale); out->start_picture (scale, bb.ll, bb.ur); print_object_list (obj); out->finish_picture (); }