summaryrefslogtreecommitdiffstats
path: root/src/preproc/pic/object.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/preproc/pic/object.cpp')
-rw-r--r--src/preproc/pic/object.cpp2079
1 files changed, 2079 insertions, 0 deletions
diff --git a/src/preproc/pic/object.cpp b/src/preproc/pic/object.cpp
new file mode 100644
index 0000000..e207fb1
--- /dev/null
+++ b/src/preproc/pic/object.cpp
@@ -0,0 +1,2079 @@
+// -*- C++ -*-
+/* 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/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#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()
+{
+ 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)
+{
+ 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 &lt,
+ 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()
+{
+ 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;
+ xslanted = 0;
+ yslanted = 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;
+ delete[] shaded;
+ 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()
+{
+ 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()
+{
+ free(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_xslanted(double);
+ virtual void set_yslanted(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_xslanted(double)
+{
+}
+
+void graphic_object::set_yslanted(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)
+ delete[] 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_xslanted(double);
+ void set_yslanted(double);
+ void set_fill_color(char *fill);
+protected:
+ double fill; // < 0 if not filled
+ double xslanted; // !=0 if x is slanted
+ double yslanted; // !=0 if y is slanted
+ char *color_fill; // = 0 if not colored
+};
+
+closed_object::closed_object(const position &pos)
+: rectangle_object(pos), fill(-1.0), xslanted(0), yslanted(0), color_fill(0)
+{
+}
+
+void closed_object::set_fill(double f)
+{
+ assert(f >= 0.0);
+ fill = f;
+}
+
+/* accept positive and negative values */
+void closed_object::set_xslanted(double s)
+{
+ //assert(s >= 0.0);
+ xslanted = s;
+}
+/* accept positive and negative values */
+void closed_object::set_yslanted(double s)
+{
+ //assert(s >= 0.0);
+ yslanted = s;
+}
+
+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];
+ /* error("x slanted %1", xslanted); */
+ /* error("y slanted %1", yslanted); */
+ vec[0] = cent + position(dim2.x, -(dim2.y - yslanted)); /* lr */
+ vec[1] = cent + position(dim2.x + xslanted, dim2.y + yslanted); /* ur */
+ vec[2] = cent + position(-(dim2.x - xslanted), dim2.y); /* ul */
+ vec[3] = cent + position(-(dim2.x), -dim2.y); /* ll */
+ 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, color_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()
+{
+ delete[] v;
+}
+
+linear_object *object_spec::make_line(position *curpos, direction *dirp)
+{
+ static position last_line;
+ static int have_last_line = 0;
+ *dirp = dir;
+ // We handle 'at' only in conjunction with 'with', otherwise it is
+ // the same as the 'from' attribute.
+ position startpos;
+ if ((flags & HAS_AT) && (flags & HAS_WITH))
+ // handled later -- we need the end position
+ startpos = *curpos;
+ else if (flags & HAS_FROM)
+ startpos = from;
+ else
+ startpos = *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
+ }
+ if ((flags & HAS_AT) && (flags & HAS_WITH)) {
+ // 'tmpobj' works for arrows and splines too -- we only need positions
+ line_object tmpobj(startpos, endpos, 0, 0);
+ position pos = at;
+ place offset;
+ place here;
+ here.obj = &tmpobj;
+ if (!with->follow(here, &offset))
+ return 0;
+ pos -= offset;
+ for (s = segment_list; s; s = s->next)
+ s->pos += pos;
+ startpos += pos;
+ endpos += pos;
+ }
+ // 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
+ if (radius < d)
+ radius = d;
+ 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_XSLANTED)
+ obj->set_xslanted(xslanted);
+ if (flags & IS_YSLANTED)
+ obj->set_yslanted(yslanted);
+ 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()
+{
+ free(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();
+}
+